Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4a882000a9
commit
3c3b7c12a0
6
Gemfile
6
Gemfile
|
@ -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'
|
||||
|
||||
|
|
15
Gemfile.lock
15
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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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? ||
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class ProjectGroupLink < ApplicationRecord
|
||||
include Expirable
|
||||
include EachBatch
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :group
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Geo: Remove released feature flag `geo_package_file_verification`'
|
||||
merge_request: 61568
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Managed Prometheus deprecation warning
|
||||
merge_request: 60560
|
||||
author:
|
||||
type: deprecated
|
|
@ -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
|
|
@ -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. |
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
|
@ -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
|
||||
|
|
|
@ -7,7 +7,8 @@ module Gitlab
|
|||
def self.all_observers
|
||||
[
|
||||
TotalDatabaseSizeChange.new,
|
||||
QueryStatistics.new
|
||||
QueryStatistics.new,
|
||||
QueryLog.new
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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({
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -28,6 +28,7 @@ describe('dashboard invalid url parameters', () => {
|
|||
},
|
||||
},
|
||||
options,
|
||||
provide: { hasManagedPrometheus: false },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ describe('dashboard invalid url parameters', () => {
|
|||
store,
|
||||
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
|
||||
...options,
|
||||
provide: { hasManagedPrometheus: false },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ describe('Dashboard', () => {
|
|||
showPanels: true,
|
||||
},
|
||||
store,
|
||||
provide: { hasManagedPrometheus: false },
|
||||
});
|
||||
|
||||
setupStoreWithData(component.$store);
|
||||
|
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue