diff --git a/app/assets/javascripts/pages/projects/tags/index/index.js b/app/assets/javascripts/pages/projects/tags/index/index.js index 98560c1193b..9e48dd9e463 100644 --- a/app/assets/javascripts/pages/projects/tags/index/index.js +++ b/app/assets/javascripts/pages/projects/tags/index/index.js @@ -1,3 +1,4 @@ +import TagSortDropdown from '~/tags'; import { initRemoveTag } from '../remove_tag'; initRemoveTag({ @@ -5,3 +6,4 @@ initRemoveTag({ document.querySelector(`[data-path="${path}"]`).closest('.js-tag-list').remove(); }, }); +TagSortDropdown(); diff --git a/app/assets/javascripts/tags/components/sort_dropdown.vue b/app/assets/javascripts/tags/components/sort_dropdown.vue new file mode 100644 index 00000000000..036ce2cca78 --- /dev/null +++ b/app/assets/javascripts/tags/components/sort_dropdown.vue @@ -0,0 +1,77 @@ + + diff --git a/app/assets/javascripts/tags/index.js b/app/assets/javascripts/tags/index.js new file mode 100644 index 00000000000..68510f3fe3a --- /dev/null +++ b/app/assets/javascripts/tags/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import SortDropdown from './components/sort_dropdown.vue'; + +const mountDropdownApp = (el) => { + const { sortOptions, filterTagsPath } = el.dataset; + + return new Vue({ + el, + name: 'SortTagsDropdownApp', + components: { + SortDropdown, + }, + provide: { + sortOptions: JSON.parse(sortOptions), + filterTagsPath, + }, + render: (createElement) => createElement(SortDropdown), + }); +}; + +export default () => { + const el = document.getElementById('js-tags-sort-dropdown'); + return el ? mountDropdownApp(el) : null; +}; diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 94b0473e1f3..3bf9988ca22 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -9,6 +9,9 @@ class Projects::TagsController < Projects::ApplicationController before_action :require_non_empty_project before_action :authorize_download_code! before_action :authorize_admin_tag!, only: [:new, :create, :destroy] + before_action do + push_frontend_feature_flag(:gldropdown_tags, default_enabled: :yaml) + end feature_category :source_code_management, [:index, :show, :new, :destroy] feature_category :release_evidence, [:create] diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 43094f09605..b9c136abab4 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Prometheus < ApplicationRecord - include PrometheusAdapter + include ::Clusters::Concerns::PrometheusClient VERSION = '10.4.1' @@ -58,14 +58,6 @@ module Clusters 'https://gitlab-org.gitlab.io/cluster-integration/helm-stable-archive' end - def service_name - 'prometheus-prometheus-server' - end - - def service_port - 80 - end - def install_command helm_command_module::InstallCommand.new( name: name, @@ -106,29 +98,6 @@ module Clusters files.merge('values.yaml': replaced_values) end - def prometheus_client - return unless kube_client - - proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE) - - # ensures headers containing auth data are appended to original k8s client options - options = kube_client.rest_client.options - .merge(prometheus_client_default_options) - .merge(headers: kube_client.headers) - Gitlab::PrometheusClient.new(proxy_url, options) - rescue Kubeclient::HttpError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ENETUNREACH - # If users have mistakenly set parameters or removed the depended clusters, - # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. - # Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab, - # we need to silence the exceptions - end - - def configured? - kube_client.present? && available? - rescue Gitlab::UrlBlocker::BlockedUrlError - false - end - def generate_alert_manager_token! unless alert_manager_token.present? update!(alert_manager_token: generate_token) @@ -146,10 +115,6 @@ module Clusters .perform_async(cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass end - def kube_client - cluster&.kubeclient&.core_client - end - def install_knative_metrics return [] unless cluster.application_knative_available? diff --git a/app/models/clusters/concerns/prometheus_client.rb b/app/models/clusters/concerns/prometheus_client.rb new file mode 100644 index 00000000000..10cb307addd --- /dev/null +++ b/app/models/clusters/concerns/prometheus_client.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Clusters + module Concerns + module PrometheusClient + extend ActiveSupport::Concern + + included do + include PrometheusAdapter + + def service_name + 'prometheus-prometheus-server' + end + + def service_port + 80 + end + + def prometheus_client + return unless kube_client + + proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE) + + # ensures headers containing auth data are appended to original k8s client options + options = kube_client.rest_client.options + .merge(prometheus_client_default_options) + .merge(headers: kube_client.headers) + Gitlab::PrometheusClient.new(proxy_url, options) + rescue Kubeclient::HttpError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ENETUNREACH + # If users have mistakenly set parameters or removed the depended clusters, + # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. + # Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab, + # we need to silence the exceptions + end + + def configured? + kube_client.present? && available? + rescue Gitlab::UrlBlocker::BlockedUrlError + false + end + + private + + def kube_client + cluster&.kubeclient&.core_client + end + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index eaf0b7ef0d6..a1035168dba 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -127,7 +127,7 @@ class User < ApplicationRecord # Groups has_many :members - has_many :group_members, -> { where(requested_at: nil).where("access_level >= ?", Gitlab::Access::GUEST) }, source: 'GroupMember' + has_many :group_members, -> { where(requested_at: nil).where("access_level >= ?", Gitlab::Access::GUEST) }, class_name: 'GroupMember' has_many :groups, through: :group_members has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group @@ -141,7 +141,7 @@ class User < ApplicationRecord -> { where(members: { access_level: [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, through: :group_members, source: :group - has_many :minimal_access_group_members, -> { where(access_level: [Gitlab::Access::MINIMAL_ACCESS]) }, source: 'GroupMember', class_name: 'GroupMember' + has_many :minimal_access_group_members, -> { where(access_level: [Gitlab::Access::MINIMAL_ACCESS]) }, class_name: 'GroupMember' has_many :minimal_access_groups, through: :minimal_access_group_members, source: :group # Projects diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index b58ff4ae958..cee24098049 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -38,7 +38,7 @@ module Issues user_agent_detail_service.create resolve_discussions_with_issue(issue) - if Feature.disabled?(:issue_perform_after_creation_tasks_async, issue.project) + if Feature.disabled?(:issue_perform_after_creation_tasks_async, issue.project, default_enabled: :yaml) Issues::AfterCreateService .new(issue.project, current_user) .execute(issue) diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml index 5e70e80cff7..61af7535c1e 100644 --- a/app/views/admin/hook_logs/_index.html.haml +++ b/app/views/admin/hook_logs/_index.html.haml @@ -1,18 +1,18 @@ .row.gl-mt-3.gl-mb-3 .col-lg-3 %h4.gl-mt-0 - Recent Deliveries - %p When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong. + = _('Recent Deliveries') + %p= _('When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.') .col-lg-9 - if hook_logs.any? %table.table %thead %tr - %th Status - %th Trigger - %th URL - %th Elapsed time - %th Request time + %th= _('Status') + %th= _('Trigger') + %th= _('URL') + %th= _('Elapsed time') + %th= _('Request time') %th - hook_logs.each do |hook_log| %tr @@ -28,10 +28,10 @@ %td.light = time_ago_with_tooltip(hook_log.created_at) %td - = link_to 'View details', admin_hook_hook_log_path(hook, hook_log) + = link_to _('View details'), admin_hook_hook_log_path(hook, hook_log) = paginate hook_logs, theme: 'gitlab' - else .settings-message.text-center - You don't have any webhooks deliveries + = _("You don't have any webhooks deliveries") diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index c2e40413a14..7e505729213 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -4,7 +4,7 @@ - @projects.each_with_index do |project| %li.project-row{ class: ('no-description' if project.description.blank?) } .controls - = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn gl-button btn-default" + = link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn gl-button btn-default" %button.delete-project-button.gl-button.btn.btn-danger{ data: { delete_project_url: admin_project_path(project), project_name: project.name } } = s_('AdminProjects|Delete') @@ -31,6 +31,6 @@ = paginate @projects, theme: 'gitlab' - else - .nothing-here-block No projects found + .nothing-here-block= _('No projects found') #delete-project-modal diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 26ab9b8c176..8882bab2e57 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -3,15 +3,15 @@ .table-mobile-header{ role: 'rowheader' }= _('Type') .table-mobile-content - if runner.instance_type? - %span.badge.badge-pill.gl-badge.sm.badge-success shared + %span.badge.badge-pill.gl-badge.sm.badge-success= _("shared") - elsif runner.group_type? - %span.badge.badge-pill.gl-badge.sm.badge-success group + %span.badge.badge-pill.gl-badge.sm.badge-success= _("group") - else - %span.badge.badge-pill.gl-badge.sm.badge-info specific + %span.badge.badge-pill.gl-badge.sm.badge-info= _("specific") - if runner.locked? - %span.badge.badge-pill.gl-badge.sm.badge-warning locked + %span.badge.badge-pill.gl-badge.sm.badge-warning= _("locked") - unless runner.active? - %span.badge.badge-pill.gl-badge.sm.badge-danger paused + %span.badge.badge-pill.gl-badge.sm.badge-danger= _("paused") .table-section.section-10 .table-mobile-header{ role: 'rowheader' }= _('Runner token') diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index b5b394723ab..229f13d0ff3 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -9,20 +9,23 @@ = s_('TagsPage|Tags give the ability to mark specific points in history as being important') .nav-controls - = form_tag(filter_tags_path, method: :get) do - = search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } + - unless Gitlab::Ci::Features.gldropdown_tags_enabled? + = form_tag(filter_tags_path, method: :get) do + = search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } - .dropdown - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown'} } - %span.light - = tags_sort_options_hash[@sort] - = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable - %li.dropdown-header - = s_('TagsPage|Sort by') - - tags_sort_options_hash.each do |value, title| - %li - = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value) + .dropdown + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown'} } + %span.light + = tags_sort_options_hash[@sort] + = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable + %li.dropdown-header + = s_('TagsPage|Sort by') + - tags_sort_options_hash.each do |value, title| + %li + = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value) + - else + #js-tags-sort-dropdown{ data: { filter_tags_path: filter_tags_path, sort_options: tags_sort_options_hash.to_json } } - if can?(current_user, :admin_tag, @project) = link_to new_project_tag_path(@project), class: 'btn gl-button btn-confirm', data: { qa_selector: "new_tag_button" } do = s_('TagsPage|New tag') diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb index 2f99da334e8..03a8af30c6f 100644 --- a/app/workers/new_issue_worker.rb +++ b/app/workers/new_issue_worker.rb @@ -17,7 +17,7 @@ class NewIssueWorker # rubocop:disable Scalability/IdempotentWorker issuable.create_cross_references!(user) - if Feature.enabled?(:issue_perform_after_creation_tasks_async, issuable.project) + if Feature.enabled?(:issue_perform_after_creation_tasks_async, issuable.project, default_enabled: :yaml) Issues::AfterCreateService .new(issuable.project, user) .execute(issuable) diff --git a/changelogs/unreleased/Externalize-strings-in-hook_logs-_index-html-haml.yml b/changelogs/unreleased/Externalize-strings-in-hook_logs-_index-html-haml.yml new file mode 100644 index 00000000000..a0765255593 --- /dev/null +++ b/changelogs/unreleased/Externalize-strings-in-hook_logs-_index-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Externalize strings in hook_logs/_index.html.haml +merge_request: 58155 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/Externalize-strings-in-projects-_projects-html-haml.yml b/changelogs/unreleased/Externalize-strings-in-projects-_projects-html-haml.yml new file mode 100644 index 00000000000..d35d04923a3 --- /dev/null +++ b/changelogs/unreleased/Externalize-strings-in-projects-_projects-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Externalize strings in projects/_projects.html.haml +merge_request: 58158 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/Externalize-strings-in-runners-_runner-html-haml.yml b/changelogs/unreleased/Externalize-strings-in-runners-_runner-html-haml.yml new file mode 100644 index 00000000000..db1282ff8b6 --- /dev/null +++ b/changelogs/unreleased/Externalize-strings-in-runners-_runner-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Externalise strings in runners/_runner.html.haml +merge_request: 58168 +author: nuwe1 +type: other diff --git a/config/feature_flags/development/gldropdown_tags.yml b/config/feature_flags/development/gldropdown_tags.yml new file mode 100644 index 00000000000..704f276ac37 --- /dev/null +++ b/config/feature_flags/development/gldropdown_tags.yml @@ -0,0 +1,8 @@ +--- +name: gldropdown_tags +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58589 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327055 +milestone: '13.11' +type: development +group: group::continuous integration +default_enabled: false diff --git a/config/feature_flags/development/issue_perform_after_creation_tasks_async.yml b/config/feature_flags/development/issue_perform_after_creation_tasks_async.yml index 7184d742002..0efef586022 100644 --- a/config/feature_flags/development/issue_perform_after_creation_tasks_async.yml +++ b/config/feature_flags/development/issue_perform_after_creation_tasks_async.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/21140 milestone: '13.11' type: development group: group::geo -default_enabled: false +default_enabled: true diff --git a/config/metrics/counts_all/20210216181205_confidential_epics.yml b/config/metrics/counts_all/20210216181205_confidential_epics.yml deleted file mode 100644 index f2941af6bd2..00000000000 --- a/config/metrics/counts_all/20210216181205_confidential_epics.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -key_path: counts.confidential_epics -description: -product_section: dev -product_stage: plan -product_group: group::portfolio management -product_category: -value_type: number -status: data_available -time_frame: all -data_source: database -distribution: -- ce -tier: -- free -skip_validation: true diff --git a/config/metrics/counts_all/20210216181206_epics.yml b/config/metrics/counts_all/20210216181206_epics.yml deleted file mode 100644 index 97452db6f85..00000000000 --- a/config/metrics/counts_all/20210216181206_epics.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -key_path: counts.epics -description: -product_section: dev -product_stage: plan -product_group: group::portfolio management -product_category: -value_type: number -status: data_available -time_frame: all -data_source: database -distribution: -- ce -tier: -- free -skip_validation: true diff --git a/config/metrics/counts_all/20210216181210_issues_with_health_status.yml b/config/metrics/counts_all/20210216181210_issues_with_health_status.yml deleted file mode 100644 index 1df3a5d1a38..00000000000 --- a/config/metrics/counts_all/20210216181210_issues_with_health_status.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -key_path: counts.issues_with_health_status -description: -product_section: dev -product_stage: plan -product_group: group::portfolio management -product_category: -value_type: number -status: data_available -time_frame: all -data_source: database -distribution: -- ce -tier: -- free -skip_validation: true diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 9ed439cb861..1a8df1cea92 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -921,6 +921,7 @@ cluster. > - [Made generally available and enabled by default](https://gitlab.com/gitlab-org/gitaly/-/issues/2951) in GitLab 13.3. > - [Disabled by default](https://gitlab.com/gitlab-org/gitaly/-/issues/3178) in GitLab 13.5. > - [Enabled by default](https://gitlab.com/gitlab-org/gitaly/-/issues/3334) in GitLab 13.8. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitaly/-/issues/3383) in GitLab 13.11. Praefect supports distribution of read operations across Gitaly nodes that are configured for the virtual node. diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 850443d0694..278c379fa48 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -790,15 +790,15 @@ Tiers: `free` ### `counts.confidential_epics` -Missing description +Count of confidential epics -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181205_confidential_epics.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181205_confidential_epics.yml) -Group: `group::portfolio management` +Group: `group::product planning` Status: `data_available` -Tiers: `free` +Tiers: `premium`, `ultimate` ### `counts.container_scanning_jobs` @@ -962,7 +962,7 @@ Count of issues that are assigned to an epic [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181208_epic_issues.yml) -Group: `group::portfolio management` +Group: `group::product planning` Status: `data_available` @@ -970,27 +970,27 @@ Tiers: `premium`, `ultimate` ### `counts.epics` -Missing description +Count of all epics -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181206_epics.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181206_epics.yml) -Group: `group::portfolio management` +Group: `group::product planning` Status: `data_available` -Tiers: `free` +Tiers: `premium`, `ultimate` ### `counts.epics_deepest_relationship_level` -Missing description +Count of the deepest relationship level for epics [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181212_epics_deepest_relationship_level.yml) -Group: `group::portfolio management` +Group: `group::product planning` Status: `data_available` -Tiers: +Tiers: `ultimate` ### `counts.failed_deployments` @@ -2698,15 +2698,15 @@ Tiers: `free`, `premium`, `ultimate` ### `counts.issues_with_health_status` -Missing description +Count of issues with health status -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181210_issues_with_health_status.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181210_issues_with_health_status.yml) -Group: `group::portfolio management` +Group: `group::product planning` Status: `data_available` -Tiers: `free` +Tiers: `ultimate` ### `counts.jira_imports_projects_count` diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 1317d47a232..1ba2161362c 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -571,9 +571,8 @@ Instructions are available in the [legacy template project](https://gitlab.com/g This is the current default behavior, because the job's status indicates success or failure of the analyzer itself. Analyzer results are displayed in the [job logs](../../ci/jobs/index.md#expand-and-collapse-job-log-sections), -[Merge Request widget](sast/index.md) +[Merge Request widget](#view-security-scan-information-in-merge-requests) or [Security Dashboard](security_dashboard/index.md). -There is [an open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/235772) in which changes to this behavior are being discussed. ### Error: job `is used for configuration only, and its script should not be executed` diff --git a/doc/user/group/index.md b/doc/user/group/index.md index b980777ec08..d3b5a51c756 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -262,6 +262,9 @@ To view the activity feed in Atom format, select the > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18328) in GitLab 12.7. +NOTE: +In GitLab 13.11, you can [replace this form with a modal window](#share-a-group-modal-window). + Similar to how you [share a project with a group](../project/members/share_project_with_groups.md), you can share a group with another group. Members get direct access to the shared group. This is not valid for inherited members. @@ -278,6 +281,27 @@ To share a given group, for example, `Frontend` with another group, for example, All the members of the `Engineering` group are added to the `Frontend` group. +### Share a group modal window + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11. +> - [Deployed behind a feature flag](../feature_flags.md), disabled by default. +> - Enabled on GitLab.com. +> - Recommended for production use. +> - Replaces the existing form with buttons to open a modal window. +> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](../project/members/index.md#enable-or-disable-modal-window). **(FREE SELF)** + +WARNING: +This feature might not be available to you. Check the **version history** note above for details. + +In GitLab 13.11, you can optionally replace the sharing form with a modal window. +To share a group after enabling this feature: + +1. Go to your group's page. +1. In the left sidebar, go to **Members**, and then select **Invite a group**. +1. Select a group, and select a **Max access level**. +1. (Optional) Select an **Access expiration date**. +1. Select **Invite**. + ## Manage group memberships via LDAP **(PREMIUM SELF)** Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that will be associated with GitLab groups. diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md index d9c21c8e3c4..2993849e0e6 100644 --- a/doc/user/project/members/index.md +++ b/doc/user/project/members/index.md @@ -109,6 +109,9 @@ had on the project you imported from are retained. ## Invite people using their e-mail address +NOTE: +In GitLab 13.11, you can [replace this form with a modal window](#add-a-member-modal-window). + If a user you want to give access to doesn't have an account on your GitLab instance, you can invite them just by typing their e-mail address in the user search field. @@ -135,6 +138,46 @@ GitLab account using the same e-mail address the invitation was sent to. NOTE: Unaccepted invites are automatically deleted after 90 days. +### Add a member modal window + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11. +> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default. +> - Enabled on GitLab.com. +> - Recommended for production use. +> - Replaces the existing form with buttons to open a modal window. +> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-modal-window). **(FREE SELF)** + +WARNING: +This feature might not be available to you. Check the **version history** note above for details. + +In GitLab 13.11, you can optionally replace the form to add a member with a modal window. +To add a member after enabling this feature: + +1. Go to your project's page. +1. In the left sidebar, go to **Members**, and then select **Invite members**. +1. Enter an email address, and select a role permission for this user. +1. (Optional) Select an **Access expiration date**. +1. Select **Invite**. + +### Enable or disable modal window **(FREE SELF)** + +The modal window for adding a member is under development and is ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) +can enable it. + +To enable it: + +```ruby +Feature.enable(:invite_members_group_modal) +``` + +To disable it: + +```ruby +Feature.disable(:invite_members_group_modal) +``` + ## Project membership and requesting access Project owners can : diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md index 8ca403783cb..8338c17f4ba 100644 --- a/doc/user/project/members/share_project_with_groups.md +++ b/doc/user/project/members/share_project_with_groups.md @@ -17,6 +17,9 @@ members. ## Sharing a project with a group of users +NOTE: +In GitLab 13.11, you can [replace this form with a modal window](#share-a-project-modal-window). + The primary mechanism to give a group of users, say 'Engineering', access to a project, say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'? @@ -48,6 +51,46 @@ Note that you can only share a project with: Administrators are able to share projects with any group in the system. +### Share a project modal window + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11. +> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default. +> - Enabled on GitLab.com. +> - Recommended for production use. +> - Replaces the existing form with buttons to open a modal window. +> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-modal-window). **(FREE SELF)** + +WARNING: +This feature might not be available to you. Check the **version history** note above for details. + +In GitLab 13.11, you can optionally replace the sharing form with a modal window. +To share a project after enabling this feature: + +1. Go to your project's page. +1. In the left sidebar, go to **Members**, and then select **Invite a group**. +1. Select a group, and select a **Max access level**. +1. (Optional) Select an **Access expiration date**. +1. Select **Invite**. + +### Enable or disable modal window **(FREE SELF)** + +The modal window for sharing a project is under development and is ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) +can enable it. + +To enable it: + +```ruby +Feature.enable(:invite_members_group_modal) +``` + +To disable it: + +```ruby +Feature.disable(:invite_members_group_modal) +``` + ## Maximum access level In the example above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Maintainer' or 'Owner') only have 'Developer' access to 'Project Acme'. diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index 320f3a959fa..a8610967708 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -63,6 +63,10 @@ module Gitlab def self.multiple_cache_per_job? ::Feature.enabled?(:multiple_cache_per_job, default_enabled: :yaml) end + + def self.gldropdown_tags_enabled? + ::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml) + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 32b700534a5..27d4d4f33bd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -11547,6 +11547,9 @@ msgstr "" msgid "Editor Lite instance is required to set up an extension." msgstr "" +msgid "Elapsed time" +msgstr "" + msgid "Elasticsearch AWS IAM credentials" msgstr "" @@ -21356,6 +21359,9 @@ msgstr "" msgid "No prioritized labels with such name or description" msgstr "" +msgid "No projects found" +msgstr "" + msgid "No public groups" msgstr "" @@ -25767,6 +25773,9 @@ msgstr "" msgid "Recent Activity" msgstr "" +msgid "Recent Deliveries" +msgstr "" + msgid "Recent Project Activity" msgstr "" @@ -26580,6 +26589,9 @@ msgstr "" msgid "Request review from" msgstr "" +msgid "Request time" +msgstr "" + msgid "Request to link SAML account must be authorized" msgstr "" @@ -35018,6 +35030,9 @@ msgstr "" msgid "When a runner is locked, it cannot be assigned to other projects" msgstr "" +msgid "When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong." +msgstr "" + msgid "When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled." msgstr "" @@ -35647,6 +35662,9 @@ msgstr "" msgid "You don't have any recent searches" msgstr "" +msgid "You don't have any webhooks deliveries" +msgstr "" + msgid "You don't have sufficient permission to perform this action." msgstr "" @@ -37556,6 +37574,9 @@ msgstr "" msgid "severity|Unknown" msgstr "" +msgid "shared" +msgstr "" + msgid "should be an array of %{object_name} objects" msgstr "" diff --git a/spec/frontend/tags/components/sort_dropdown_spec.js b/spec/frontend/tags/components/sort_dropdown_spec.js new file mode 100644 index 00000000000..b0fd98ec68e --- /dev/null +++ b/spec/frontend/tags/components/sort_dropdown_spec.js @@ -0,0 +1,81 @@ +import { GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import * as urlUtils from '~/lib/utils/url_utility'; +import SortDropdown from '~/tags/components/sort_dropdown.vue'; + +describe('Tags sort dropdown', () => { + let wrapper; + + const createWrapper = (props = {}) => { + return extendedWrapper( + mount(SortDropdown, { + provide: { + filterTagsPath: '/root/ci-cd-project-demo/-/tags', + sortOptions: { + name_asc: 'Name', + updated_asc: 'Oldest updated', + updated_desc: 'Last updated', + }, + ...props, + }, + }), + ); + }; + + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByClick); + const findTagsDropdown = () => wrapper.findByTestId('tags-dropdown'); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('default state', () => { + beforeEach(() => { + wrapper = createWrapper(); + }); + + it('should have a search box with a placeholder', () => { + const searchBox = findSearchBox(); + + expect(searchBox.exists()).toBe(true); + expect(searchBox.find('input').attributes('placeholder')).toBe('Filter by tag name'); + }); + + it('should have a sort order dropdown', () => { + const branchesDropdown = findTagsDropdown(); + + expect(branchesDropdown.exists()).toBe(true); + }); + }); + + describe('when submitting a search term', () => { + beforeEach(() => { + urlUtils.visitUrl = jest.fn(); + + wrapper = createWrapper(); + }); + + it('should call visitUrl', () => { + const searchBox = findSearchBox(); + + searchBox.vm.$emit('submit'); + + expect(urlUtils.visitUrl).toHaveBeenCalledWith( + '/root/ci-cd-project-demo/-/tags?sort=updated_desc', + ); + }); + + it('should send a sort parameter', () => { + const sortDropdownItems = findTagsDropdown().findAllComponents(GlDropdownItem).at(0); + + sortDropdownItems.vm.$emit('click'); + + expect(urlUtils.visitUrl).toHaveBeenCalledWith( + '/root/ci-cd-project-demo/-/tags?sort=name_asc', + ); + }); + }); +}); diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index c359b1863ed..5a0ccabd467 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -74,85 +74,8 @@ RSpec.describe Clusters::Applications::Prometheus do end describe '#prometheus_client' do - shared_examples 'exception caught for prometheus client' do - before do - allow(kube_client).to receive(:proxy_url).and_raise(exception) - end - - it 'returns nil' do - expect(subject.prometheus_client).to be_nil - end - end - - context 'cluster is nil' do - it 'returns nil' do - expect(subject.cluster).to be_nil - expect(subject.prometheus_client).to be_nil - end - end - - context "cluster doesn't have kubeclient" do - let(:cluster) { create(:cluster) } - - subject { create(:clusters_applications_prometheus, cluster: cluster) } - - it 'returns nil' do - expect(subject.prometheus_client).to be_nil - end - end - - context 'cluster has kubeclient' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url } - let(:kube_client) { subject.cluster.kubeclient.core_client } - - subject { create(:clusters_applications_prometheus, cluster: cluster) } - - before do - subject.cluster.platform_kubernetes.namespace = 'a-namespace' - stub_kubeclient_discover(cluster.platform_kubernetes.api_url) - - create(:cluster_kubernetes_namespace, - cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) - end - - it 'creates proxy prometheus_client' do - expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient) - end - - it 'merges proxy_url, options and headers from kube client with prometheus_client options' do - expect(Gitlab::PrometheusClient) - .to(receive(:new)) - .with(a_valid_url, kube_client.rest_client.options.merge({ - headers: kube_client.headers, - timeout: PrometheusAdapter::DEFAULT_PROMETHEUS_REQUEST_TIMEOUT_SEC - })) - subject.prometheus_client - end - - context 'when cluster is not reachable' do - it_behaves_like 'exception caught for prometheus client' do - let(:exception) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) } - end - end - - context 'when there is a socket error while contacting cluster' do - it_behaves_like 'exception caught for prometheus client' do - let(:exception) { Errno::ECONNREFUSED } - end - - it_behaves_like 'exception caught for prometheus client' do - let(:exception) { Errno::ECONNRESET } - end - end - - context 'when the network is unreachable' do - it_behaves_like 'exception caught for prometheus client' do - let(:exception) { Errno::ENETUNREACH } - end - end + include_examples '#prometheus_client shared' do + let(:factory) { :clusters_applications_prometheus } end end diff --git a/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb b/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb new file mode 100644 index 00000000000..8d6dcfef925 --- /dev/null +++ b/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +# Input +# - factory: [:clusters_applications_prometheus, :clusters_integrations_prometheus] +RSpec.shared_examples '#prometheus_client shared' do + shared_examples 'exception caught for prometheus client' do + before do + allow(kube_client).to receive(:proxy_url).and_raise(exception) + end + + it 'returns nil' do + expect(subject.prometheus_client).to be_nil + end + end + + context 'cluster is nil' do + it 'returns nil' do + expect(subject.cluster).to be_nil + expect(subject.prometheus_client).to be_nil + end + end + + context "cluster doesn't have kubeclient" do + let(:cluster) { create(:cluster) } + + subject { create(factory, cluster: cluster) } + + it 'returns nil' do + expect(subject.prometheus_client).to be_nil + end + end + + context 'cluster has kubeclient' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url } + let(:kube_client) { subject.cluster.kubeclient.core_client } + + subject { create(factory, cluster: cluster) } + + before do + subject.cluster.platform_kubernetes.namespace = 'a-namespace' + stub_kubeclient_discover(cluster.platform_kubernetes.api_url) + + create(:cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: cluster.cluster_project.project) + end + + it 'creates proxy prometheus_client' do + expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient) + end + + it 'merges proxy_url, options and headers from kube client with prometheus_client options' do + expect(Gitlab::PrometheusClient) + .to(receive(:new)) + .with(a_valid_url, kube_client.rest_client.options.merge({ + headers: kube_client.headers, + timeout: PrometheusAdapter::DEFAULT_PROMETHEUS_REQUEST_TIMEOUT_SEC + })) + subject.prometheus_client + end + + context 'when cluster is not reachable' do + it_behaves_like 'exception caught for prometheus client' do + let(:exception) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) } + end + end + + context 'when there is a socket error while contacting cluster' do + it_behaves_like 'exception caught for prometheus client' do + let(:exception) { Errno::ECONNREFUSED } + end + + it_behaves_like 'exception caught for prometheus client' do + let(:exception) { Errno::ECONNRESET } + end + end + + context 'when the network is unreachable' do + it_behaves_like 'exception caught for prometheus client' do + let(:exception) { Errno::ENETUNREACH } + end + end + end +end diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb index dc008875062..18b42f98e0b 100644 --- a/spec/views/projects/tags/index.html.haml_spec.rb +++ b/spec/views/projects/tags/index.html.haml_spec.rb @@ -21,6 +21,7 @@ RSpec.describe 'projects/tags/index.html.haml' do end it 'defaults sort dropdown toggle to last updated' do + stub_feature_flags(gldropdown_tags: false) render expect(rendered).to have_button('Last updated') end