diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-12 21:10:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-12 21:10:35 +0300 |
commit | 3c3b7c12a0955679626545248aa2aa347d6d001d (patch) | |
tree | c46d2ad91033821457181b5c748df997fe579545 | |
parent | 4a882000a94d1043b536e078a0e3571bdb0077d3 (diff) |
Add latest changes from gitlab-org/gitlab@master
49 files changed, 959 insertions, 55 deletions
@@ -309,12 +309,12 @@ gem 'rack-attack', '~> 6.3.0' gem 'sentry-raven', '~> 3.0' # PostgreSQL query parsing -gem 'pg_query', '~> 1.3.0' +gem 'pg_query', '~> 2.0.3' gem 'premailer-rails', '~> 1.10.3' # LabKit: Tracing and Correlation -gem 'gitlab-labkit', '~> 0.16.2' +gem 'gitlab-labkit', '~> 0.17.1' # Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0 # because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900 gem 'thrift', '>= 0.14.0' @@ -485,7 +485,7 @@ gem 'gitaly', '~> 13.12.0.pre.rc1' gem 'grpc', '~> 1.30.2' -gem 'google-protobuf', '~> 3.14.0' +gem 'google-protobuf', '~> 3.15.8' gem 'toml-rb', '~> 1.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 0efe172d134..806ee347fdc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -467,13 +467,13 @@ GEM fog-xml (~> 0.1.0) google-api-client (>= 0.44.2, < 0.51) google-cloud-env (~> 1.2) - gitlab-labkit (0.16.2) + gitlab-labkit (0.17.1) actionpack (>= 5.0.0, < 7.0.0) activesupport (>= 5.0.0, < 7.0.0) grpc (~> 1.19) jaeger-client (~> 1.1) opentracing (~> 0.4) - pg_query (~> 1.3) + pg_query (~> 2.0) redis (> 3.0.0, < 5.0.0) gitlab-license (1.5.0) gitlab-mail_room (0.0.9) @@ -519,7 +519,7 @@ GEM signet (~> 0.12) google-cloud-env (1.4.0) faraday (>= 0.17.3, < 2.0) - google-protobuf (3.14.0) + google-protobuf (3.15.8) googleapis-common-protos-types (1.0.6) google-protobuf (~> 3.14) googleauth (0.14.0) @@ -904,7 +904,8 @@ GEM peek (1.1.0) railties (>= 4.0.0) pg (1.2.3) - pg_query (1.3.0) + pg_query (2.0.3) + google-protobuf (~> 3.15.5) plist (3.6.0) png_quantizator (0.2.1) po_to_json (1.0.1) @@ -1454,7 +1455,7 @@ DEPENDENCIES gitlab-experiment (~> 0.5.4) gitlab-fog-azure-rm (~> 1.0.1) gitlab-fog-google (~> 1.13) - gitlab-labkit (~> 0.16.2) + gitlab-labkit (~> 0.17.1) gitlab-license (~> 1.5) gitlab-mail_room (~> 0.0.9) gitlab-markup (~> 1.7.1) @@ -1467,7 +1468,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.4.0) google-api-client (~> 0.33) - google-protobuf (~> 3.14.0) + google-protobuf (~> 3.15.8) gpgme (~> 2.0.19) grape (~> 1.5.2) grape-entity (~> 0.7.1) @@ -1548,7 +1549,7 @@ DEPENDENCIES parslet (~> 1.8) peek (~> 1.1) pg (~> 1.1) - pg_query (~> 1.3.0) + pg_query (~> 2.0.3) png_quantizator (~> 0.2.1) premailer-rails (~> 1.10.3) prometheus-client-mmap (~> 0.12.0) diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue index 79a6bac3ba7..8ea977698e1 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_table.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue @@ -17,6 +17,7 @@ import { convertToSnakeCase } from '~/lib/utils/text_utility'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { s__, __ } from '~/locale'; import AlertStatus from '~/vue_shared/alert_details/components/alert_status.vue'; +import AlertsDeprecationWarning from '~/vue_shared/components/alerts_deprecation_warning.vue'; import { tdClass, thClass, @@ -96,6 +97,7 @@ export default { severityLabels: SEVERITY_LEVELS, statusTabs: ALERTS_STATUS_TABS, components: { + AlertsDeprecationWarning, GlAlert, GlLoadingIcon, GlTable, @@ -273,6 +275,8 @@ export default { </gl-sprintf> </gl-alert> + <alerts-deprecation-warning /> + <paginated-table-with-search-and-tabs :show-error-msg="showErrorMsg" :i18n="$options.i18n" diff --git a/app/assets/javascripts/alert_management/list.js b/app/assets/javascripts/alert_management/list.js index b23f8a8eba4..e9d19f18ab5 100644 --- a/app/assets/javascripts/alert_management/list.js +++ b/app/assets/javascripts/alert_management/list.js @@ -23,6 +23,7 @@ export default () => { assigneeUsernameQuery, alertManagementEnabled, userCanEnableAlertManagement, + hasManagedPrometheus, } = domEl.dataset; const apolloProvider = new VueApollo({ @@ -64,6 +65,7 @@ export default () => { alertManagementEnabled: parseBoolean(alertManagementEnabled), trackAlertStatusUpdateOptions: PAGE_CONFIG.OPERATIONS.TRACK_ALERT_STATUS_UPDATE_OPTIONS, userCanEnableAlertManagement: parseBoolean(userCanEnableAlertManagement), + hasManagedPrometheus: parseBoolean(hasManagedPrometheus), }, apolloProvider, render(createElement) { diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index bfb18206b62..05e7fb7a3e9 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -8,6 +8,7 @@ import invalidUrl from '~/lib/utils/invalid_url'; import { ESC_KEY } from '~/lib/utils/keys'; import { mergeUrlParams, updateHistory } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; +import AlertsDeprecationWarning from '~/vue_shared/components/alerts_deprecation_warning.vue'; import { defaultTimeRange } from '~/vue_shared/constants'; import TrackEventDirective from '~/vue_shared/directives/track_event'; import { metricStates, keyboardShortcutKeys } from '../constants'; @@ -28,6 +29,7 @@ import VariablesSection from './variables_section.vue'; export default { components: { + AlertsDeprecationWarning, VueDraggable, DashboardHeader, DashboardPanel, @@ -394,6 +396,8 @@ export default { <template> <div class="prometheus-graphs" data-qa-selector="prometheus_graphs"> + <alerts-deprecation-warning /> + <dashboard-header v-if="showHeader" ref="prometheusGraphsHeader" diff --git a/app/assets/javascripts/monitoring/monitoring_app.js b/app/assets/javascripts/monitoring/monitoring_app.js index ee67e5dd827..cf79e71b9e0 100644 --- a/app/assets/javascripts/monitoring/monitoring_app.js +++ b/app/assets/javascripts/monitoring/monitoring_app.js @@ -12,7 +12,10 @@ export default (props = {}) => { if (el && el.dataset) { const { metricsDashboardBasePath, ...dataset } = el.dataset; - const { initState, dataProps } = stateAndPropsFromDataset(dataset); + const { + initState, + dataProps: { hasManagedPrometheus, ...dataProps }, + } = stateAndPropsFromDataset(dataset); const store = createStore(initState); const router = createRouter(metricsDashboardBasePath); @@ -21,6 +24,7 @@ export default (props = {}) => { el, store, router, + provide: { hasManagedPrometheus }, data() { return { dashboardProps: { ...dataProps, ...props }, diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 6306415a8b9..8adf1862af2 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -41,6 +41,7 @@ export const stateAndPropsFromDataset = (dataset = {}) => { dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics); dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable); dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable); + dataProps.hasManagedPrometheus = parseBoolean(dataProps.hasManagedPrometheus); return { initState: { diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue index 5c6c8b40af5..4bef6f66be2 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue @@ -111,7 +111,12 @@ export default { this.reportFailure({ type: LOAD_FAILURE, skipSentry: true }); reportToSentry( this.$options.name, - `type: ${LOAD_FAILURE}, info: ${serializeLoadErrors(err)}`, + `| type: ${LOAD_FAILURE} | + | rawError: ${JSON.stringify(err)} | + | info: ${serializeLoadErrors(err)} | + | graphqlResourceEtag: ${this.graphqlResourceEtag} | + | projectPath: ${this.projectPath} | + | iid: ${this.pipelineIid} |`, ); }, result({ error }) { diff --git a/app/assets/javascripts/vue_shared/components/alerts_deprecation_warning.vue b/app/assets/javascripts/vue_shared/components/alerts_deprecation_warning.vue new file mode 100644 index 00000000000..1f293b2150f --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/alerts_deprecation_warning.vue @@ -0,0 +1,41 @@ +<script> +import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { s__ } from '~/locale'; + +export default { + components: { + GlAlert, + GlLink, + GlSprintf, + }, + inject: ['hasManagedPrometheus'], + i18n: { + alertsDeprecationText: s__( + 'Metrics|GitLab-managed Prometheus is deprecated and %{linkStart}scheduled for removal%{linkEnd}. Following this removal, your existing alerts will continue to function as part of the new cluster integration. However, you will no longer be able to add new alerts or edit existing alerts from the metrics dashboard.', + ), + }, + methods: { + helpPagePath, + }, +}; +</script> + +<template> + <gl-alert v-if="hasManagedPrometheus" variant="warning" class="my-2"> + <gl-sprintf :message="$options.i18n.alertsDeprecationText"> + <template #link="{ content }"> + <gl-link + :href=" + helpPagePath('operations/metrics/alerts.html', { + anchor: 'managed-prometheus-instances', + }) + " + target="_blank" + > + <span>{{ content }}</span> + </gl-link> + </template> + </gl-sprintf> + </gl-alert> +</template> diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb index 01105f6cec4..d7c4d2fcda3 100644 --- a/app/experiments/application_experiment.rb +++ b/app/experiments/application_experiment.rb @@ -36,6 +36,10 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp @excluded = true end + def control_behavior + # define a default nil control behavior so we can omit it when not needed + end + private def feature_flag_name diff --git a/app/finders/projects/members/effective_access_level_finder.rb b/app/finders/projects/members/effective_access_level_finder.rb new file mode 100644 index 00000000000..2880d6667ce --- /dev/null +++ b/app/finders/projects/members/effective_access_level_finder.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +module Projects + module Members + class EffectiveAccessLevelFinder + include Gitlab::Utils::StrongMemoize + + USER_ID_AND_ACCESS_LEVEL = [:user_id, :access_level].freeze + BATCH_SIZE = 5 + + def initialize(project) + @project = project + end + + def execute + return Member.none if no_members? + + # rubocop: disable CodeReuse/ActiveRecord + Member.from(generate_from_statement(user_ids_and_access_levels_from_all_memberships)) + .select([:user_id, 'MAX(access_level) AS access_level']) + .group(:user_id) + # rubocop: enable CodeReuse/ActiveRecord + end + + private + + attr_reader :project + + def generate_from_statement(user_ids_and_access_levels) + "(VALUES #{generate_values_expression(user_ids_and_access_levels)}) members (user_id, access_level)" + end + + def generate_values_expression(user_ids_and_access_levels) + user_ids_and_access_levels.map do |user_id, access_level| + "(#{user_id}, #{access_level})" + end.join(",") + end + + def no_members? + user_ids_and_access_levels_from_all_memberships.blank? + end + + def all_possible_avenues_of_membership + avenues = [authorizable_project_members] + + avenues << if project.personal? + project_owner_acting_as_maintainer + else + authorizable_group_members + end + + if include_membership_from_project_group_shares? + avenues << members_from_project_group_shares + end + + avenues + end + + # @return [Array<[user_id, access_level]>] + def user_ids_and_access_levels_from_all_memberships + strong_memoize(:user_ids_and_access_levels_from_all_memberships) do + all_possible_avenues_of_membership.flat_map do |relation| + relation.pluck(*USER_ID_AND_ACCESS_LEVEL) # rubocop: disable CodeReuse/ActiveRecord + end + end + end + + def authorizable_project_members + project.members.authorizable + end + + def authorizable_group_members + project.group.authorizable_members_with_parents + end + + def members_from_project_group_shares + members = [] + + project.project_group_links.each_batch(of: BATCH_SIZE) do |relation| + members_per_batch = [] + + relation.includes(:group).each do |link| # rubocop: disable CodeReuse/ActiveRecord + members_per_batch << link.group.authorizable_members_with_parents.select(*user_id_and_access_level_for_project_group_shares(link)) + end + + members << Member.from_union(members_per_batch) + end + + members.flatten + end + + def project_owner_acting_as_maintainer + user_id = project.namespace.owner.id + access_level = Gitlab::Access::MAINTAINER + + Member + .from(generate_from_statement([[user_id, access_level]])) # rubocop: disable CodeReuse/ActiveRecord + .limit(1) + end + + def include_membership_from_project_group_shares? + project.allowed_to_share_with_group? && project.project_group_links.any? + end + + # methods for `select` options + + def user_id_and_access_level_for_project_group_shares(link) + least_access_level_among_group_membership_and_project_share = + smallest_value_arel([link.group_access, GroupMember.arel_table[:access_level]], 'access_level') + + [ + :user_id, + least_access_level_among_group_membership_and_project_share + ] + end + + def smallest_value_arel(args, column_alias) + Arel::Nodes::As.new( + Arel::Nodes::NamedFunction.new('LEAST', args), + Arel.sql(column_alias) + ) + end + end + end +end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index aa1c3786596..5add24500e2 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -62,7 +62,8 @@ module EnvironmentsHelper 'validate_query_path' => validate_query_project_prometheus_metrics_path(project), 'custom_metrics_available' => "#{custom_metrics_available?(project)}", 'prometheus_alerts_available' => "#{can?(current_user, :read_prometheus_alerts, project)}", - 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase + 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase, + 'has_managed_prometheus' => has_managed_prometheus?(project).to_s } end @@ -78,6 +79,10 @@ module EnvironmentsHelper } end + def has_managed_prometheus?(project) + project.prometheus_service&.prometheus_available? == true + end + def metrics_dashboard_base_path(environment, project) # This is needed to support our transition from environment scoped metric paths to project scoped. if project diff --git a/app/helpers/projects/alert_management_helper.rb b/app/helpers/projects/alert_management_helper.rb index b705258f133..b46e3eb3bc3 100644 --- a/app/helpers/projects/alert_management_helper.rb +++ b/app/helpers/projects/alert_management_helper.rb @@ -10,6 +10,7 @@ module Projects::AlertManagementHelper 'empty-alert-svg-path' => image_path('illustrations/alert-management-empty-state.svg'), 'user-can-enable-alert-management' => can?(current_user, :admin_operations, project).to_s, 'alert-management-enabled' => alert_management_enabled?(project).to_s, + 'has-managed-prometheus' => has_managed_prometheus?(project).to_s, 'text-query': params[:search], 'assignee-username-query': params[:assignee_username] } @@ -27,6 +28,10 @@ module Projects::AlertManagementHelper private + def has_managed_prometheus?(project) + project.prometheus_service&.prometheus_available? == true + end + def alert_management_enabled?(project) !!( project.alert_management_alerts.any? || diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index f39f0a64708..8c877c2b818 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -39,7 +39,7 @@ module Ci AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze AVAILABLE_TYPES = runner_types.keys.freeze - AVAILABLE_STATUSES = %w[active paused online offline].freeze + AVAILABLE_STATUSES = %w[active paused online offline not_connected].freeze AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze @@ -65,6 +65,7 @@ module Ci # did `contacted_at <= ?` the query would effectively have to do a seq # scan. scope :offline, -> { where.not(id: online) } + scope :not_connected, -> { where(contacted_at: nil) } scope :ordered, -> { order(id: :desc) } scope :with_recent_runner_queue, -> { where('contacted_at > ?', recent_queue_deadline) } diff --git a/app/models/group.rb b/app/models/group.rb index e407436d72a..c6faf2ff28e 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -450,6 +450,20 @@ class Group < Namespace .where(source_id: id) end + def authorizable_members_with_parents + source_ids = + if has_parent? + self_and_ancestors.reorder(nil).select(:id) + else + id + end + + group_hierarchy_members = GroupMember.where(source_id: source_ids) + + GroupMember.from_union([group_hierarchy_members, + members_from_self_and_ancestor_group_shares]).authorizable + end + def members_with_parents # Avoids an unnecessary SELECT when the group has no parents source_ids = diff --git a/app/models/member.rb b/app/models/member.rb index ebe125c4770..0986ef16c47 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -92,6 +92,15 @@ class Member < ApplicationRecord .reorder(nil) end + # This scope is exclusively used to get the members + # that can possibly have project_authorization records + # to projects/groups. + scope :authorizable, -> do + where.not(user_id: nil) + .non_request + .non_minimal_access + end + # Like active, but without invites. For when a User is required. scope :active_without_invites_and_requests, -> do left_join_users diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index ec4aea18486..d704f4c2c87 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -2,6 +2,7 @@ class ProjectGroupLink < ApplicationRecord include Expirable + include EachBatch belongs_to :project belongs_to :group diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml index bf5e35a1224..e85ce1ba6ac 100644 --- a/app/views/registrations/welcome/show.html.haml +++ b/app/views/registrations/welcome/show.html.haml @@ -25,6 +25,7 @@ = f.text_field :other_role, class: 'form-control' = render_if_exists "registrations/welcome/setup_for_company", f: f = render 'devise/shared/email_opted_in', f: f + = render_if_exists "registrations/welcome/jobs_to_be_done", f: f .row .form-group.col-sm-12.gl-mb-0 - if partial_exists? "registrations/welcome/button" diff --git a/changelogs/unreleased/323309-geo-remove-released-feature-flag-geo_package_file_verification.yml b/changelogs/unreleased/323309-geo-remove-released-feature-flag-geo_package_file_verification.yml new file mode 100644 index 00000000000..42cdd178575 --- /dev/null +++ b/changelogs/unreleased/323309-geo-remove-released-feature-flag-geo_package_file_verification.yml @@ -0,0 +1,5 @@ +--- +title: 'Geo: Remove released feature flag `geo_package_file_verification`' +merge_request: 61568 +author: +type: other diff --git a/changelogs/unreleased/tr-metrics-deprecation-warning.yml b/changelogs/unreleased/tr-metrics-deprecation-warning.yml new file mode 100644 index 00000000000..200609276a8 --- /dev/null +++ b/changelogs/unreleased/tr-metrics-deprecation-warning.yml @@ -0,0 +1,5 @@ +--- +title: Add Managed Prometheus deprecation warning +merge_request: 60560 +author: +type: deprecated diff --git a/config/feature_flags/experiment/jobs_to_be_done.yml b/config/feature_flags/experiment/jobs_to_be_done.yml new file mode 100644 index 00000000000..5589d33a3c3 --- /dev/null +++ b/config/feature_flags/experiment/jobs_to_be_done.yml @@ -0,0 +1,8 @@ +--- +name: jobs_to_be_done +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60038 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285564 +milestone: '13.12' +type: experiment +group: group::adoption +default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2ce89bbf9b8..f0189971b56 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -13585,6 +13585,7 @@ Values for YAML processor result. | Value | Description | | ----- | ----------- | | <a id="cirunnerstatusactive"></a>`ACTIVE` | A runner that is active. | +| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` | A runner that is not connected. | | <a id="cirunnerstatusoffline"></a>`OFFLINE` | A runner that is offline. | | <a id="cirunnerstatusonline"></a>`ONLINE` | A runner that is online. | | <a id="cirunnerstatuspaused"></a>`PAUSED` | A runner that is paused. | diff --git a/doc/api/members.md b/doc/api/members.md index 6098a80d0dd..2a70e35b287 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -289,7 +289,8 @@ Example response: "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", "last_activity_on": "2021-01-27", - "membership_type": "group_member" + "membership_type": "group_member", + "removable": true }, { "id": 2, @@ -300,7 +301,8 @@ Example response: "web_url": "http://192.168.1.8:3000/root", "email": "john@example.com", "last_activity_on": "2021-01-25", - "membership_type": "group_member" + "membership_type": "group_member", + "removable": true }, { "id": 3, @@ -310,7 +312,8 @@ Example response: "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", "last_activity_on": "2021-01-20", - "membership_type": "group_invite" + "membership_type": "group_invite", + "removable": false } ] ``` diff --git a/doc/operations/incident_management/oncall_schedules.md b/doc/operations/incident_management/oncall_schedules.md index 87745639c69..695b42f7d1a 100644 --- a/doc/operations/incident_management/oncall_schedules.md +++ b/doc/operations/incident_management/oncall_schedules.md @@ -107,3 +107,10 @@ Hover over any rotation shift participants in the schedule to view their individ When an alert is created in a project, GitLab sends an email to the on-call responder(s) in the on-call schedule for that project. If there is no schedule or no one on-call in that schedule at the time the alert is triggered, no email is sent. + +## Removal or deletion of on-call user + +If an on-call user is removed from the project or group, or their account is deleted, the +confirmation modal displays the list of that user's on-call schedules. If the user's removal or +deletion is confirmed, GitLab recalculates the on-call rotation and sends an email to the project +owners and the rotation's participants. diff --git a/doc/user/admin_area/settings/sign_in_restrictions.md b/doc/user/admin_area/settings/sign_in_restrictions.md index 09a9e4dbca3..647f9332119 100644 --- a/doc/user/admin_area/settings/sign_in_restrictions.md +++ b/doc/user/admin_area/settings/sign_in_restrictions.md @@ -20,7 +20,7 @@ To access sign-in restriction settings: You can restrict the password authentication for web interface and Git over HTTP(S): -- **Web interface**: When this feature is disabled, an [external authentication provider](../../../administration/auth/README.md) must be used. +- **Web interface**: When this feature is disabled, the **Standard** sign-in tab is removed and an [external authentication provider](../../../administration/auth/README.md) must be used. - **Git over HTTP(S)**: When this feature is disabled, a [Personal Access Token](../../profile/personal_access_tokens.md) must be used to authenticate. ## Admin Mode diff --git a/doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.png b/doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.png Binary files differnew file mode 100644 index 00000000000..7d47003972c --- /dev/null +++ b/doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.png diff --git a/doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.png b/doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.png Binary files differnew file mode 100644 index 00000000000..4ad7d2e353e --- /dev/null +++ b/doc/user/group/value_stream_analytics/img/vsa_stage_table_v13_12.png diff --git a/doc/user/group/value_stream_analytics/index.md b/doc/user/group/value_stream_analytics/index.md index dc6ffd8fe23..132cf3b17d2 100644 --- a/doc/user/group/value_stream_analytics/index.md +++ b/doc/user/group/value_stream_analytics/index.md @@ -227,6 +227,50 @@ Hovering over a stage item displays a popover with the following information: - Start event description for the given stage - End event description +### Stream overview + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321438) in GitLab 13.11. + +![Value Stream Analytics Overview](img/vsa_overview_stage_v13_11.png "VSA overview") + +The stream overview provides access to key metrics and charts summarizing all the stages in the value stream +based on selected filters. + +Shown metrics and charts includes: + +- [Lead time](#how-metrics-are-measured) +- [Cycle time](#how-metrics-are-measured) +- [Days to completion chart](#days-to-completion-chart) +- [Tasks by type chart](#type-of-work---tasks-by-type-chart) + +### Stage table + +> Sorting the stage table [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/301082) in GitLab 13.12. + +![Value Stream Analytics Stage table](img/vsa_stage_table_v13_12.png "VSA stage table") + +The stage table shows a list of related workflow items for the selected stage. This can include: + +- CI/CD jobs +- Issues +- Merge requests +- Pipelines + +The stage table also includes the **Time** column, which shows how long it takes each item to pass +through the selected value stream stage. + +The stage table is not displayed on the stream [Overview](#stream-overview). +The workflow item column (first column) is ordered by end event. + +To sort the stage table by a table column, select the table header. +You can sort in ascending or descending order. To find items that spent the most time in a stage, +potentially causing bottlenecks in your value stream, sort the table by the **Time** column. +From there, select individual items to drill in and investigate how delays are happening. +To see which items the stage most recently, sort by the work item column on the left. + +The table displays up to 20 items at a time. If there are more than 20 items, you can use the +**Prev** and **Next** buttons to navigate through the pages. + ### Adding a stage In the following example we're creating a new stage that measures and tracks issues from creation diff --git a/lib/gitlab/database/migrations/observers.rb b/lib/gitlab/database/migrations/observers.rb index 592993aeac5..b65a303ef30 100644 --- a/lib/gitlab/database/migrations/observers.rb +++ b/lib/gitlab/database/migrations/observers.rb @@ -7,7 +7,8 @@ module Gitlab def self.all_observers [ TotalDatabaseSizeChange.new, - QueryStatistics.new + QueryStatistics.new, + QueryLog.new ] end end diff --git a/lib/gitlab/database/migrations/observers/query_log.rb b/lib/gitlab/database/migrations/observers/query_log.rb new file mode 100644 index 00000000000..45df07fe391 --- /dev/null +++ b/lib/gitlab/database/migrations/observers/query_log.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module Database + module Migrations + module Observers + class QueryLog < MigrationObserver + def before + @logger_was = ActiveRecord::Base.logger + @log_file_path = File.join(Instrumentation::RESULT_DIR, 'current.log') + @logger = Logger.new(@log_file_path) + ActiveRecord::Base.logger = @logger + end + + def after + ActiveRecord::Base.logger = @logger_was + @logger.close + end + + def record(observation) + File.rename(@log_file_path, File.join(Instrumentation::RESULT_DIR, "#{observation.migration}.log")) + end + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e4dd687f022..0d27e11c80e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1394,6 +1394,9 @@ msgstr "" msgid "A description is required" msgstr "" +msgid "A different reason" +msgstr "" + msgid "A file has been changed." msgstr "" @@ -5052,6 +5055,9 @@ msgstr "" msgid "Billings|As a user on a free or trial namespace, you'll need to verify your account with a credit card to run pipelines. This is required to help prevent cryptomining attacks on GitLab infrastructure. %{strongStart}GitLab will not charge or store your credit card, it will only be used for validation.%{strongEnd}" msgstr "" +msgid "Billings|To discourage and reduce abuse GitLab will require some users to provide a valid credit card to use free pipeline minutes on GitLab.com. To use free pipeline minutes, you will need to validate your account with a credit card. %{strongStart}GitLab will not add permanent charges to your credit card as we will only use it for validation.%{strongEnd}" +msgstr "" + msgid "Billings|User Verification Required" msgstr "" @@ -5064,9 +5070,6 @@ msgstr "" msgid "Billings|Verify account" msgstr "" -msgid "Billings|Your user account has been flagged for potential abuse for running a large number of concurrent pipelines. To continue to run a large number of concurrent pipelines, you'll need to validate your account with a credit card. %{strongStart}GitLab will not charge your credit card, it will only be used for validation.%{strongEnd}" -msgstr "" - msgid "Billings|Your user account has been successfully verified. You will now be able to run pipelines on any free or trial namespace." msgstr "" @@ -16420,9 +16423,24 @@ msgstr "" msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)" msgstr "" +msgid "I want to explore GitLab to see if it’s worth switching to" +msgstr "" + +msgid "I want to learn the basics of Git" +msgstr "" + +msgid "I want to move my repository to GitLab from somewhere else" +msgstr "" + +msgid "I want to use GitLab CI with my existing repository" +msgstr "" + msgid "I'd like to receive updates about GitLab via email" msgstr "" +msgid "I'm signing up for GitLab because:" +msgstr "" + msgid "ID" msgstr "" @@ -18435,6 +18453,9 @@ msgstr "" msgid "I’m familiar with the basics of DevOps." msgstr "" +msgid "I’m joining my team who’s already on GitLab" +msgstr "" + msgid "I’m not familiar with the basics of DevOps." msgstr "" @@ -20886,6 +20907,9 @@ msgstr "" msgid "Metrics|For grouping similar metrics" msgstr "" +msgid "Metrics|GitLab-managed Prometheus is deprecated and %{linkStart}scheduled for removal%{linkEnd}. Following this removal, your existing alerts will continue to function as part of the new cluster integration. However, you will no longer be able to add new alerts or edit existing alerts from the metrics dashboard." +msgstr "" + msgid "Metrics|Invalid time range, please verify." msgstr "" @@ -24532,6 +24556,9 @@ msgstr "" msgid "Please select what should be included in each exported requirement." msgstr "" +msgid "Please select..." +msgstr "" + msgid "Please set a new password before proceeding." msgstr "" @@ -36500,6 +36527,9 @@ msgstr "" msgid "Who will be using this GitLab trial?" msgstr "" +msgid "Why are you signing up? (Optional)" +msgstr "" + msgid "Wiki" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb index 519777b32d9..c1625f1e679 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb @@ -5,34 +5,29 @@ require 'faker' module QA RSpec.describe 'Verify', :runner do describe 'Pass dotenv variables to downstream via bridge' do - let(:executor_1) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" } - let(:executor_2) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" } + let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" } + let(:upstream_var) { Faker::Alphanumeric.alphanumeric(8) } + let(:group) { Resource::Group.fabricate_via_api! } let(:upstream_project) do Resource::Project.fabricate_via_api! do |project| - project.name = 'project-with-pipeline-1' + project.group = group + project.name = 'upstream-project-with-bridge' end end let(:downstream_project) do Resource::Project.fabricate_via_api! do |project| - project.name = 'project-with-pipeline-2' + project.group = group + project.name = 'downstream-project-with-bridge' end end - let!(:runner_1) do + let!(:runner) do Resource::Runner.fabricate! do |runner| - runner.project = upstream_project - runner.name = executor_1 - runner.tags = [executor_1] - end - end - - let!(:runner_2) do - Resource::Runner.fabricate! do |runner| - runner.project = downstream_project - runner.name = executor_2 - runner.tags = [executor_2] + runner.name = executor + runner.tags = [executor] + runner.token = group.sandbox.runners_token end end @@ -45,8 +40,8 @@ module QA end after do - runner_1.remove_via_api! - runner_2.remove_via_api! + runner.remove_via_api! + group.remove_via_api! end it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1086' do @@ -58,6 +53,7 @@ module QA Page::Project::Job::Show.perform do |show| expect(show).to have_passed(timeout: 360) + expect(show.output).to have_content(upstream_var) end end @@ -77,8 +73,9 @@ module QA content: <<~YAML build: stage: build - tags: ["#{executor_1}"] - script: echo "MY_VAR=hello" >> variables.env + tags: ["#{executor}"] + script: + - echo "DYNAMIC_ENVIRONMENT_VAR=#{upstream_var}" >> variables.env artifacts: reports: dotenv: variables.env @@ -86,7 +83,7 @@ module QA trigger: stage: deploy variables: - PASSED_MY_VAR: $MY_VAR + PASSED_MY_VAR: $DYNAMIC_ENVIRONMENT_VAR trigger: #{downstream_project.full_path} YAML } @@ -98,8 +95,9 @@ module QA content: <<~YAML downstream_test: stage: test - tags: ["#{executor_2}"] - script: '[ "$PASSED_MY_VAR" = hello ]; exit "$?"' + tags: ["#{executor}"] + script: + - echo $PASSED_MY_VAR YAML } end diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index 424a3af20a3..2ff16604c33 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -25,6 +25,12 @@ RSpec.describe ApplicationExperiment, :experiment do described_class.new('namespaced/stub') end + it "doesn't raise an exception without a defined control" do + # because we have a default behavior defined + + expect { experiment('namespaced/stub') { } }.not_to raise_error + end + describe "enabled" do before do allow(subject).to receive(:enabled?).and_call_original diff --git a/spec/finders/ci/runners_finder_spec.rb b/spec/finders/ci/runners_finder_spec.rb index d4795d786bc..34639f9b7bd 100644 --- a/spec/finders/ci/runners_finder_spec.rb +++ b/spec/finders/ci/runners_finder_spec.rb @@ -25,10 +25,12 @@ RSpec.describe Ci::RunnersFinder do end context 'filter by status' do - it 'calls the corresponding scope on Ci::Runner' do - expect(Ci::Runner).to receive(:paused).and_call_original + Ci::Runner::AVAILABLE_STATUSES.each do |status| + it "calls the corresponding :#{status} scope on Ci::Runner" do + expect(Ci::Runner).to receive(status.to_sym).and_call_original - described_class.new(current_user: admin, params: { status_status: 'paused' }).execute + described_class.new(current_user: admin, params: { status_status: status }).execute + end end end diff --git a/spec/finders/projects/members/effective_access_level_finder_spec.rb b/spec/finders/projects/members/effective_access_level_finder_spec.rb new file mode 100644 index 00000000000..1112dbd0d6e --- /dev/null +++ b/spec/finders/projects/members/effective_access_level_finder_spec.rb @@ -0,0 +1,257 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + + # The result set is being converted to json just for the ease of testing. + subject { described_class.new(project).execute.as_json } + + context 'for a personal project' do + let_it_be(:project) { create(:project) } + + shared_examples_for 'includes access level of the owner of the project as Maintainer' do + it 'includes access level of the owner of the project as Maintainer' do + expect(subject).to( + contain_exactly( + hash_including( + 'user_id' => project.namespace.owner.id, + 'access_level' => Gitlab::Access::MAINTAINER + ) + ) + ) + end + end + + context 'when the project owner is a member of the project' do + it_behaves_like 'includes access level of the owner of the project as Maintainer' + end + + context 'when the project owner is not explicitly a member of the project' do + before do + project.members.find_by(user_id: project.namespace.owner.id).destroy! + end + + it_behaves_like 'includes access level of the owner of the project as Maintainer' + end + end + + context 'direct members of the project' do + it 'includes access levels of the direct members of the project' do + developer = create(:project_member, :developer, source: project) + maintainer = create(:project_member, :maintainer, source: project) + + expect(subject).to( + include( + hash_including( + 'user_id' => developer.user.id, + 'access_level' => Gitlab::Access::DEVELOPER + ), + hash_including( + 'user_id' => maintainer.user.id, + 'access_level' => Gitlab::Access::MAINTAINER + ) + ) + ) + end + + it 'does not include access levels of users who have requested access to the project' do + member_with_access_request = create(:project_member, :access_request, :developer, source: project) + + expect(subject).not_to( + include( + hash_including( + 'user_id' => member_with_access_request.user.id + ) + ) + ) + end + + it 'includes access levels of users who are in non-active state' do + blocked_member = create(:project_member, :blocked, :developer, source: project) + + expect(subject).to( + include( + hash_including( + 'user_id' => blocked_member.user.id, + 'access_level' => Gitlab::Access::DEVELOPER + ) + ) + ) + end + end + + context 'for a project within a group' do + context 'project in a root group' do + it 'includes access levels of users who are direct members of the parent group' do + group_member = create(:group_member, :developer, source: group) + + expect(subject).to( + include( + hash_including( + 'user_id' => group_member.user.id, + 'access_level' => Gitlab::Access::DEVELOPER + ) + ) + ) + end + end + + context 'project in a subgroup' do + let_it_be(:project) { create(:project, group: create(:group, :nested)) } + + it 'includes access levels of users who are members of the ancestors of the parent group' do + group_member = create(:group_member, :maintainer, source: project.group.parent) + + expect(subject).to( + include( + hash_including( + 'user_id' => group_member.user.id, + 'access_level' => Gitlab::Access::MAINTAINER + ) + ) + ) + end + end + + context 'user is both a member of the project and a member of the parent group' do + let_it_be(:user) { create(:user) } + + before do + group.add_developer(user) + project.add_maintainer(user) + end + + it 'includes the maximum access level among project and group membership' do + expect(subject).to( + include( + hash_including( + 'user_id' => user.id, + 'access_level' => Gitlab::Access::MAINTAINER + ) + ) + ) + end + end + + context 'members from group share' do + let_it_be(:shared_with_group) { create(:group) } + let_it_be(:user_from_shared_with_group) { create(:user) } + + before do + shared_with_group.add_guest(user_from_shared_with_group) + create(:group_group_link, :developer, shared_group: project.group, shared_with_group: shared_with_group) + end + + it 'includes the user from the group share with the right access level' do + expect(subject).to( + include( + hash_including( + 'user_id' => user_from_shared_with_group.id, + 'access_level' => Gitlab::Access::GUEST + ) + ) + ) + end + + context 'when the project also has the same user as a member, but with a different access level' do + before do + project.add_maintainer(user_from_shared_with_group) + end + + it 'includes the maximum access level among project and group membership' do + expect(subject).to( + include( + hash_including( + 'user_id' => user_from_shared_with_group.id, + 'access_level' => Gitlab::Access::MAINTAINER + ) + ) + ) + end + end + + context "when the project's ancestor also has the same user as a member, but with a different access level" do + before do + project.group.add_maintainer(user_from_shared_with_group) + end + + it 'includes the maximum access level among project and group membership' do + expect(subject).to( + include( + hash_including( + 'user_id' => user_from_shared_with_group.id, + 'access_level' => Gitlab::Access::MAINTAINER + ) + ) + ) + end + end + end + end + + context 'for a project that is shared with other group(s)' do + let_it_be(:shared_with_group) { create(:group) } + let_it_be(:user_from_shared_with_group) { create(:user) } + + before do + create(:project_group_link, :developer, project: project, group: shared_with_group) + shared_with_group.add_maintainer(user_from_shared_with_group) + end + + it 'includes the least among the specified access levels' do + expect(subject).to( + include( + hash_including( + 'user_id' => user_from_shared_with_group.id, + 'access_level' => Gitlab::Access::DEVELOPER + ) + ) + ) + end + + context 'when the group containing the project has forbidden group shares for any of its projects' do + let_it_be(:project) { create(:project, group: create(:group)) } + + before do + project.namespace.update!(share_with_group_lock: true) + end + + it 'does not include the users from any group shares' do + expect(subject).not_to( + include( + hash_including( + 'user_id' => user_from_shared_with_group.id + ) + ) + ) + end + end + end + + context 'a combination of all possible avenues of membership' do + let_it_be(:user) { create(:user) } + let_it_be(:shared_with_group) { create(:group) } + + before do + create(:project_group_link, :maintainer, project: project, group: shared_with_group) + create(:group_group_link, :reporter, shared_group: project.group, shared_with_group: shared_with_group) + + shared_with_group.add_maintainer(user) + group.add_guest(user) + project.add_developer(user) + end + + it 'includes the highest access level from all avenues of memberships' do + expect(subject).to( + include( + hash_including( + 'user_id' => user.id, + 'access_level' => Gitlab::Access::MAINTAINER # From project_group_link + ) + ) + ) + end + end +end diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js index dece3dfbe5f..826fb820d9b 100644 --- a/spec/frontend/alert_management/components/alert_management_table_spec.js +++ b/spec/frontend/alert_management/components/alert_management_table_spec.js @@ -7,6 +7,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import mockAlerts from 'jest/vue_shared/alert_details/mocks/alerts.json'; import AlertManagementTable from '~/alert_management/components/alert_management_table.vue'; import { visitUrl } from '~/lib/utils/url_utility'; +import AlertDeprecationWarning from '~/vue_shared/components/alerts_deprecation_warning.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import defaultProvideValues from '../mocks/alerts_provide_config.json'; @@ -14,6 +15,7 @@ import defaultProvideValues from '../mocks/alerts_provide_config.json'; jest.mock('~/lib/utils/url_utility', () => ({ visitUrl: jest.fn().mockName('visitUrlMock'), joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths, + setUrlFragment: jest.requireActual('~/lib/utils/url_utility').setUrlFragment, })); describe('AlertManagementTable', () => { @@ -39,6 +41,8 @@ describe('AlertManagementTable', () => { resolved: 11, all: 26, }; + const findDeprecationNotice = () => + wrapper.findComponent(AlertDeprecationWarning).findComponent(GlAlert); function mountComponent({ provide = {}, data = {}, loading = false, stubs = {} } = {}) { wrapper = extendedWrapper( @@ -47,6 +51,7 @@ describe('AlertManagementTable', () => { ...defaultProvideValues, alertManagementEnabled: true, userCanEnableAlertManagement: true, + hasManagedPrometheus: false, ...provide, }, data() { @@ -234,6 +239,20 @@ describe('AlertManagementTable', () => { expect(visitUrl).toHaveBeenCalledWith('/1527542/details', true); }); + describe('deprecation notice', () => { + it('shows the deprecation notice when available', () => { + mountComponent({ provide: { hasManagedPrometheus: true } }); + + expect(findDeprecationNotice().exists()).toBe(true); + }); + + it('hides the deprecation notice when not available', () => { + mountComponent(); + + expect(findDeprecationNotice().exists()).toBe(false); + }); + }); + describe('alert issue links', () => { beforeEach(() => { mountComponent({ diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap index e873edaad3b..98503636d33 100644 --- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap @@ -9,6 +9,8 @@ exports[`Dashboard template matches the default snapshot 1`] = ` metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json" prometheusstatus="" > + <alerts-deprecation-warning-stub /> + <div class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light" > diff --git a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js index 400ac2e8f85..8af6075a416 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js @@ -28,6 +28,7 @@ describe('dashboard invalid url parameters', () => { }, }, options, + provide: { hasManagedPrometheus: false }, }); }; diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 5c7042d4cb5..0c2f85c7298 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -1,3 +1,4 @@ +import { GlAlert } from '@gitlab/ui'; import { shallowMount, mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import VueDraggable from 'vuedraggable'; @@ -7,7 +8,6 @@ import axios from '~/lib/utils/axios_utils'; import { ESC_KEY } from '~/lib/utils/keys'; import { objectToQuery } from '~/lib/utils/url_utility'; import Dashboard from '~/monitoring/components/dashboard.vue'; - import DashboardHeader from '~/monitoring/components/dashboard_header.vue'; import DashboardPanel from '~/monitoring/components/dashboard_panel.vue'; import EmptyState from '~/monitoring/components/empty_state.vue'; @@ -17,6 +17,7 @@ import LinksSection from '~/monitoring/components/links_section.vue'; import { dashboardEmptyStates, metricStates } from '~/monitoring/constants'; import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; +import AlertDeprecationWarning from '~/vue_shared/components/alerts_deprecation_warning.vue'; import { metricsDashboardViewModel, metricsDashboardPanelCount, @@ -46,6 +47,7 @@ describe('Dashboard', () => { stubs: { DashboardHeader, }, + provide: { hasManagedPrometheus: false }, ...options, }); }; @@ -59,6 +61,9 @@ describe('Dashboard', () => { 'dashboard-panel': true, 'dashboard-header': DashboardHeader, }, + provide: { + hasManagedPrometheus: false, + }, ...options, }); }; @@ -812,4 +817,25 @@ describe('Dashboard', () => { expect(dashboardPanel.exists()).toBe(true); }); }); + + describe('deprecation notice', () => { + beforeEach(() => { + setupStoreWithData(store); + }); + + const findDeprecationNotice = () => + wrapper.find(AlertDeprecationWarning).findComponent(GlAlert); + + it('shows the deprecation notice when available', () => { + createMountedWrapper({}, { provide: { hasManagedPrometheus: true } }); + + expect(findDeprecationNotice().exists()).toBe(true); + }); + + it('hides the deprecation notice when not available', () => { + createMountedWrapper(); + + expect(findDeprecationNotice().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js index 9830b6d047f..090613b0f1e 100644 --- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js +++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js @@ -31,6 +31,7 @@ describe('dashboard invalid url parameters', () => { store, stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader }, ...options, + provide: { hasManagedPrometheus: false }, }); }; diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js index b027d60f61e..2a712d4361f 100644 --- a/spec/frontend/monitoring/router_spec.js +++ b/spec/frontend/monitoring/router_spec.js @@ -20,6 +20,8 @@ const MockApp = { template: `<router-view :dashboard-props="dashboardProps"/>`, }; +const provide = { hasManagedPrometheus: false }; + describe('Monitoring router', () => { let router; let store; @@ -37,6 +39,7 @@ describe('Monitoring router', () => { localVue, store, router, + provide, }); }; diff --git a/spec/frontend/vue_shared/components/alerts_deprecation_warning_spec.js b/spec/frontend/vue_shared/components/alerts_deprecation_warning_spec.js new file mode 100644 index 00000000000..b73f4d6a396 --- /dev/null +++ b/spec/frontend/vue_shared/components/alerts_deprecation_warning_spec.js @@ -0,0 +1,48 @@ +import { GlAlert, GlLink } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import AlertDeprecationWarning from '~/vue_shared/components/alerts_deprecation_warning.vue'; + +describe('AlertDetails', () => { + let wrapper; + + function mountComponent(hasManagedPrometheus = false) { + wrapper = mount(AlertDeprecationWarning, { + provide: { + hasManagedPrometheus, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findAlert = () => wrapper.findComponent(GlAlert); + const findLink = () => wrapper.findComponent(GlLink); + + describe('Alert details', () => { + describe('with no manual prometheus', () => { + beforeEach(() => { + mountComponent(); + }); + + it('renders nothing', () => { + expect(findAlert().exists()).toBe(false); + }); + }); + + describe('with manual prometheus', () => { + beforeEach(() => { + mountComponent(true); + }); + + it('renders a deprecation notice', () => { + expect(findAlert().text()).toContain('GitLab-managed Prometheus is deprecated'); + expect(findLink().attributes('href')).toContain( + 'operations/metrics/alerts.html#managed-prometheus-instances', + ); + }); + }); + }); +}); diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb index d316f2b0a0a..8adb329e730 100644 --- a/spec/helpers/environments_helper_spec.rb +++ b/spec/helpers/environments_helper_spec.rb @@ -45,7 +45,8 @@ RSpec.describe EnvironmentsHelper do 'custom_dashboard_base_path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT, 'operations_settings_path' => project_settings_operations_path(project), 'can_access_operations_settings' => 'true', - 'panel_preview_endpoint' => project_metrics_dashboards_builder_path(project, format: :json) + 'panel_preview_endpoint' => project_metrics_dashboards_builder_path(project, format: :json), + 'has_managed_prometheus' => 'false' ) end @@ -120,6 +121,52 @@ RSpec.describe EnvironmentsHelper do end end end + + context 'has_managed_prometheus' do + context 'without prometheus service' do + it "doesn't have managed prometheus" do + expect(metrics_data).to include( + 'has_managed_prometheus' => 'false' + ) + end + end + + context 'with prometheus service' do + let_it_be(:prometheus_service) { create(:prometheus_service, project: project) } + + context 'when manual prometheus service is active' do + it "doesn't have managed prometheus" do + prometheus_service.update!(manual_configuration: true) + + expect(metrics_data).to include( + 'has_managed_prometheus' => 'false' + ) + end + end + + context 'when prometheus service is inactive' do + it "doesn't have managed prometheus" do + prometheus_service.update!(manual_configuration: false) + + expect(metrics_data).to include( + 'has_managed_prometheus' => 'false' + ) + end + end + + context 'when a cluster prometheus is available' do + let(:cluster) { create(:cluster, projects: [project]) } + + it 'has managed prometheus' do + create(:clusters_applications_prometheus, :installed, cluster: cluster) + + expect(metrics_data).to include( + 'has_managed_prometheus' => 'true' + ) + end + end + end + end end describe '#custom_metrics_available?' do diff --git a/spec/helpers/projects/alert_management_helper_spec.rb b/spec/helpers/projects/alert_management_helper_spec.rb index e836461b099..6f66a93b9ec 100644 --- a/spec/helpers/projects/alert_management_helper_spec.rb +++ b/spec/helpers/projects/alert_management_helper_spec.rb @@ -34,6 +34,7 @@ RSpec.describe Projects::AlertManagementHelper do 'empty-alert-svg-path' => match_asset_path('/assets/illustrations/alert-management-empty-state.svg'), 'user-can-enable-alert-management' => 'true', 'alert-management-enabled' => 'false', + 'has-managed-prometheus' => 'false', 'text-query': nil, 'assignee-username-query': nil ) @@ -43,25 +44,53 @@ RSpec.describe Projects::AlertManagementHelper do context 'with prometheus service' do let_it_be(:prometheus_service) { create(:prometheus_service, project: project) } - context 'when prometheus service is active' do - it 'enables alert management' do + context 'when manual prometheus service is active' do + it "enables alert management and doesn't show managed prometheus" do + prometheus_service.update!(manual_configuration: true) + expect(data).to include( 'alert-management-enabled' => 'true' ) + expect(data).to include( + 'has-managed-prometheus' => 'false' + ) + end + end + + context 'when a cluster prometheus is available' do + let(:cluster) { create(:cluster, projects: [project]) } + + it 'has managed prometheus' do + create(:clusters_applications_prometheus, :installed, cluster: cluster) + + expect(data).to include( + 'has-managed-prometheus' => 'true' + ) end end context 'when prometheus service is inactive' do - it 'disables alert management' do + it 'disables alert management and hides managed prometheus' do prometheus_service.update!(manual_configuration: false) expect(data).to include( 'alert-management-enabled' => 'false' ) + expect(data).to include( + 'has-managed-prometheus' => 'false' + ) end end end + context 'without prometheus service' do + it "doesn't have managed prometheus" do + expect(data).to include( + 'has-managed-prometheus' => 'false' + ) + end + end + context 'with http integration' do let_it_be(:integration) { create(:alert_management_http_integration, project: project) } diff --git a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js index ec8d2778c1f..b85f50ec998 100644 --- a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js @@ -61,6 +61,7 @@ describe('Dashboard', () => { showPanels: true, }, store, + provide: { hasManagedPrometheus: false }, }); setupStoreWithData(component.$store); diff --git a/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb b/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb new file mode 100644 index 00000000000..195e7114582 --- /dev/null +++ b/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Gitlab::Database::Migrations::Observers::QueryLog do + subject { described_class.new } + + let(:observation) { Gitlab::Database::Migrations::Observation.new(migration) } + let(:connection) { ActiveRecord::Base.connection } + let(:query) { 'select 1' } + let(:directory_path) { Dir.mktmpdir } + let(:log_file) { "#{directory_path}/current.log" } + let(:migration) { 20210422152437 } + + before do + stub_const('Gitlab::Database::Migrations::Instrumentation::RESULT_DIR', directory_path) + end + + after do + FileUtils.remove_entry(directory_path) + end + + it 'writes a file with the query log' do + observe + + expect(File.read("#{directory_path}/#{migration}.log")).to include(query) + end + + it 'does not change the default logger' do + expect { observe }.not_to change { ActiveRecord::Base.logger } + end + + def observe + subject.before + connection.execute(query) + subject.after + subject.record(observation) + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index db782d16070..dc3649e2945 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1285,7 +1285,7 @@ RSpec.describe Group do end end - describe '#members_with_parents' do + shared_examples_for 'members_with_parents' do let!(:group) { create(:group, :nested) } let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) } let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) } @@ -1309,6 +1309,50 @@ RSpec.describe Group do end end + describe '#members_with_parents' do + it_behaves_like 'members_with_parents' + end + + describe '#authorizable_members_with_parents' do + let(:group) { create(:group) } + + it_behaves_like 'members_with_parents' + + context 'members with associated user but also having invite_token' do + let!(:member) { create(:group_member, :developer, :invited, user: create(:user), group: group) } + + it 'includes such members in the result' do + expect(group.authorizable_members_with_parents).to include(member) + end + end + + context 'invited members' do + let!(:member) { create(:group_member, :developer, :invited, group: group) } + + it 'does not include such members in the result' do + expect(group.authorizable_members_with_parents).not_to include(member) + end + end + + context 'members from group shares' do + let(:shared_group) { group } + let(:shared_with_group) { create(:group) } + + before do + create(:group_group_link, shared_group: shared_group, shared_with_group: shared_with_group) + end + + context 'an invited member that is part of the shared_with_group' do + let!(:member) { create(:group_member, :developer, :invited, group: shared_with_group) } + + it 'does not include such members in the result' do + expect(shared_group.authorizable_members_with_parents).not_to( + include(member)) + end + end + end + end + describe '#members_from_self_and_ancestors_with_effective_access_level' do let!(:group_parent) { create(:group, :private) } let!(:group) { create(:group, :private, parent: group_parent) } diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index b57f8576a53..e74127ffc34 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -408,6 +408,30 @@ RSpec.describe Member do it { is_expected.not_to include @member_with_minimal_access } end + describe '.authorizable' do + subject { described_class.authorizable.to_a } + + it 'includes the member who has an associated user record,'\ + 'but also having an invite_token' do + member = create(:project_member, + :developer, + :invited, + user: create(:user)) + + expect(subject).to include(member) + end + + it { is_expected.to include @owner } + it { is_expected.to include @maintainer } + it { is_expected.to include @accepted_invite_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.to include @blocked_maintainer } + it { is_expected.to include @blocked_developer } + it { is_expected.not_to include @invited_member } + it { is_expected.not_to include @requested_member } + it { is_expected.not_to include @member_with_minimal_access } + end + describe '.distinct_on_user_with_max_access_level' do let_it_be(:other_group) { create(:group) } let_it_be(:member_with_lower_access_level) { create(:group_member, :developer, group: other_group, user: @owner_user) } diff --git a/spec/views/registrations/welcome/show.html.haml_spec.rb b/spec/views/registrations/welcome/show.html.haml_spec.rb index 639759ae095..ecdef7918de 100644 --- a/spec/views/registrations/welcome/show.html.haml_spec.rb +++ b/spec/views/registrations/welcome/show.html.haml_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'registrations/welcome/show' do let(:is_gitlab_com) { false } - let_it_be(:user) { User.new } + let_it_be(:user) { create(:user) } before do allow(view).to receive(:current_user).and_return(user) |