Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-12 18:10:35 +00:00
parent 4a882000a9
commit 3c3b7c12a0
49 changed files with 959 additions and 55 deletions

View File

@ -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'

View File

@ -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)

View File

@ -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"

View File

@ -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) {

View File

@ -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"

View File

@ -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 },

View File

@ -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: {

View File

@ -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 }) {

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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? ||

View File

@ -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) }

View File

@ -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 =

View File

@ -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

View File

@ -2,6 +2,7 @@
class ProjectGroupLink < ApplicationRecord
include Expirable
include EachBatch
belongs_to :project
belongs_to :group

View File

@ -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"

View File

@ -0,0 +1,5 @@
---
title: 'Geo: Remove released feature flag `geo_package_file_verification`'
merge_request: 61568
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Add Managed Prometheus deprecation warning
merge_request: 60560
author:
type: deprecated

View File

@ -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

View File

@ -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. |

View File

@ -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
}
]
```

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -7,7 +7,8 @@ module Gitlab
def self.all_observers
[
TotalDatabaseSizeChange.new,
QueryStatistics.new
QueryStatistics.new,
QueryLog.new
]
end
end

View File

@ -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

View File

@ -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 its 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 "Im familiar with the basics of DevOps."
msgstr ""
msgid "Im joining my team whos already on GitLab"
msgstr ""
msgid "Im 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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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({

View File

@ -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"
>

View File

@ -28,6 +28,7 @@ describe('dashboard invalid url parameters', () => {
},
},
options,
provide: { hasManagedPrometheus: false },
});
};

View File

@ -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);
});
});
});

View File

@ -31,6 +31,7 @@ describe('dashboard invalid url parameters', () => {
store,
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
...options,
provide: { hasManagedPrometheus: false },
});
};

View File

@ -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,
});
};

View File

@ -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',
);
});
});
});
});

View File

@ -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

View File

@ -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) }

View File

@ -61,6 +61,7 @@ describe('Dashboard', () => {
showPanels: true,
},
store,
provide: { hasManagedPrometheus: false },
});
setupStoreWithData(component.$store);

View File

@ -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

View File

@ -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) }

View File

@ -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) }

View File

@ -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)