Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-29 15:09:30 +00:00
parent 1838e24407
commit 09ff71d425
76 changed files with 1410 additions and 852 deletions

View File

@ -1,49 +1,122 @@
<!-- Title suggestion: [Feature flag] Enable description of feature -->
## What
## Feature
Remove the `:feature_name` feature flag ...
This feature uses the `:feature_name` feature flag!
<!-- Short description of what the feature is about and link to relevant other issues. -->
- [Issue Name](ISSUE LINK)
## Owners
- Team: NAME_OF_TEAM
- Most appropriate slack channel to reach out to: `#g_TEAM_NAME`
- Best individual to reach out to: NAME
- PM: NAME
## Expectations
## Stakeholders
### What are we expecting to happen?
<!--
Are there any other stages or teams involved that need to be kept in the loop?
### What might happen if this goes wrong?
- Name of a PM
- The Support Team
- The Delivery Team
-->
### What can we monitor to detect problems with this?
## The Rollout Plan
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can also be useful to review -->
<!-- Describe how the feature should be rolled out, and check the right boxes. You can check multiple if applicable -->
- [ ] Partial Rollout on GitLab.com with beta groups
- [ ] Rollout on GitLab.com for a certain period (How long)
- [ ] Percentage Rollout on GitLab.com - XX%
If it is possible to perform an incremental rollout, this should be preferred. Proposed increments are: `10%`, `50%`, `100%`. Proposed minimum time between increments is 15 minutes.
- [ ] Rollout Feature for everyone as soon as it's ready
## Beta groups/projects
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can alse be useful to review -->
If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example.
**Beta Groups/Projects:**
<!-- If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example. -->
- `gitlab-org/gitlab` project
- `gitlab-org`/`gitlab-com` groups
- ...
## Roll Out Steps
## Expectations
### What are we expecting to happen?
<!-- Describe the expected outcome when rolling out this feature -->
### What might happen if this goes wrong?
<!-- Should the feature flag be turned off? Any MRs that need to be rolled back? Communication that needs to happen? What are some things you can think of that could go wrong - data loss or broken pages? -->
### What can we monitor to detect problems with this?
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? -->
## Rollout Timeline
<!-- Please check which steps are needed and remove those which don't apply -->
**Initial Rollout**
*Preperation Phase*
- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
- [ ] Test on staging
- [ ] Ensure that documentation has been updated
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
- [ ] If it is possible to perform an incremental rollout, this should be preferred. Proposed increments are: `10%`, `50%`, `100%`. Proposed minimum time between increments is 15 minutes.
- [ ] Ensure that documentation has been updated ([More info](https://docs.gitlab.com/ee/development/documentation/feature_flags.html#features-that-became-enabled-by-default))
- [ ] Coordinate a time to enable the flag with the SRE oncall and release managers
- In `#production` mention `@sre-oncall` and `@release-managers`. Once an SRE on call and Release Manager on call confirm, you can proceed with the rollout
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com. **Note**: Once a feature rollout has started, it is not necessary to inform `@sre-oncall`/`@release-managers` at each stage of the gradual rollout.
- In `#production` by pinging `@sre-oncall`
- In `#g_delivery` by pinging `@release-managers`
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
*Partial Rollout Phase*
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
- [ ] Verify behaviour (See Beta Groups) and add details with screenshots as a comment on this issue
**Global Availability** ([More Info](https://docs.gitlab.com/ee/development/feature_flags/controls.html#communicate-the-change))
*(Please Note that Beta,Alpha and General Availability (GA) are handled on a product level and not the feature-flag)*
<!-- The next checkboxes are probably only needed for high visibility and/or critical rollouts. Please refer to the official documentation linked above for more clarification -->
- [ ] Coordinate a time to enable the flag with `#production` and `#g_delivery` on slack.
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
- [ ] Make the feature flag enabled by default i.e. Change `default_enabled` to `true`
- [ ] Enable on GitLab.com by running chatops command in `#production` (`/chatops run feature set feature_name true`)
- [ ] Cross post chatops Slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel
- [ ] Announce on the issue that the flag has been enabled
- [ ] Remove feature flag and add changelog entry. Ensure that the feature flag definition YAML file has been removed in the **same MR** that is removing the feature flag from the code
- [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
- [ ] Cross post chatops slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel
**Cleanup**
This is an __important__ phase, that should be either done in the next Milestone or as soon as possible. For the cleanup phase, please follow our documentation on how to [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up).
<!-- The checklist here is to keep track of it's status for stakeholders -->
- [ ] Announce on the issue that the flag has been enabled
- [ ] Remove `:feature_name` feature flag
- [ ] Remove all references to the feature flag from the codebase
- [ ] Remove the YAML definitions for the feature from the repository
- [ ] Create a Changelog Entry
- [ ] Clean up the feature flag from all environments by running this chatops command in `#production` channel `/chatops run feature delete some_feature`.
**Final Step**
- [ ] Close this rollout issue for the feature flag after the feature flag is removed from the codebase.
## Rollback Steps
@ -54,3 +127,4 @@ If applicable, any groups/projects that are happy to have this feature turned on
```
/label ~"feature flag"
/assign DRI

View File

@ -161,7 +161,11 @@ export default {
>
<gl-link
v-if="hasDetails"
v-gl-tooltip="{ boundary, placement: 'bottom', customClass: 'gl-pointer-events-none' }"
v-gl-tooltip="{
boundary: 'viewport',
placement: 'bottom',
customClass: 'gl-pointer-events-none',
}"
:href="detailsPath"
:title="tooltipText"
:class="jobClasses"

View File

@ -150,7 +150,11 @@ export default {
>
<gl-link
v-if="hasDetails"
v-gl-tooltip="{ boundary, placement: 'bottom', customClass: 'gl-pointer-events-none' }"
v-gl-tooltip="{
boundary: 'viewport',
placement: 'bottom',
customClass: 'gl-pointer-events-none',
}"
:href="detailsPath"
:title="tooltipText"
:class="jobClasses"

View File

@ -1,6 +1,7 @@
query getDagVisData($projectPath: ID!, $iid: ID!) {
project(fullPath: $projectPath) {
pipeline(iid: $iid) {
id
stages {
nodes {
name

View File

@ -82,11 +82,16 @@ module Types
argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
end
field :instance_statistics_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
field :instance_statistics_measurements,
type: Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true,
description: 'Get statistics on the instance.',
deprecated: { reason: 'This field was renamed. Use the `usageTrendsMeasurements` field instead', milestone: '13.10' },
resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver
resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver,
deprecated: {
reason: :renamed,
replacement: 'Query.usageTrendsMeasurements',
milestone: '13.10'
}
field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type,
null: true,

View File

@ -7,10 +7,34 @@ module Types
# Deprecated, as we prefer uppercase enums
# https://gitlab.com/groups/gitlab-org/-/epics/1838
value 'updated_desc', 'Updated at descending order.', value: :updated_desc, deprecated: { reason: 'Use UPDATED_DESC', milestone: '13.5' }
value 'updated_asc', 'Updated at ascending order.', value: :updated_asc, deprecated: { reason: 'Use UPDATED_ASC', milestone: '13.5' }
value 'created_desc', 'Created at descending order.', value: :created_desc, deprecated: { reason: 'Use CREATED_DESC', milestone: '13.5' }
value 'created_asc', 'Created at ascending order.', value: :created_asc, deprecated: { reason: 'Use CREATED_ASC', milestone: '13.5' }
value 'updated_desc', 'Updated at descending order.',
value: :updated_desc,
deprecated: {
reason: :renamed,
replacement: 'UPDATED_DESC',
milestone: '13.5'
}
value 'updated_asc', 'Updated at ascending order.',
value: :updated_asc,
deprecated: {
reason: :renamed,
replacement: 'UPDATED_ASC',
milestone: '13.5'
}
value 'created_desc', 'Created at descending order.',
value: :created_desc,
deprecated: {
reason: :renamed,
replacement: 'CREATED_DESC',
milestone: '13.5'
}
value 'created_asc', 'Created at ascending order.',
value: :created_asc,
deprecated: {
reason: :renamed,
replacement: 'CREATED_ASC',
milestone: '13.5'
}
value 'UPDATED_DESC', 'Updated at descending order.', value: :updated_desc
value 'UPDATED_ASC', 'Updated at ascending order.', value: :updated_asc

View File

@ -24,7 +24,7 @@ module Types
description: 'State of the user.'
field :email, GraphQL::STRING_TYPE, null: true,
description: 'User email.', method: :public_email,
deprecated: { reason: 'Use public_email', milestone: '13.7' }
deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' }
field :public_email, GraphQL::STRING_TYPE, null: true,
description: "User's public email."
field :avatar_url, GraphQL::STRING_TYPE, null: true,

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module SidebarsHelper
def sidebar_tracking_attributes_by_object(object)
case object
when Project
sidebar_project_tracking_attrs
when Group
sidebar_group_tracking_attrs
when User
sidebar_user_profile_tracking_attrs
else
{}
end
end
private
def sidebar_project_tracking_attrs
tracking_attrs('projects_side_navigation', 'render', 'projects_side_navigation')
end
def sidebar_group_tracking_attrs
tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation')
end
def sidebar_user_profile_tracking_attrs
tracking_attrs('user_side_navigation', 'render', 'user_side_navigation')
end
end

View File

@ -135,6 +135,7 @@ class Note < ApplicationRecord
project: [:project_members, :namespace, { group: [:group_members] }])
end
scope :with_metadata, -> { includes(:system_note_metadata) }
scope :with_web_entity_associations, -> { preload(:project, :author, :noteable) }
scope :for_note_or_capitalized_note, ->(text) { where(note: [text, text.capitalize]) }
scope :like_note_or_capitalized_note, ->(text) { where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) }

View File

@ -142,7 +142,7 @@ class Packages::Package < ApplicationRecord
end
def self.only_maven_packages_with_path(path, use_cte: false)
if use_cte && Feature.enabled?(:maven_metadata_by_path_with_optimization_fence)
if use_cte && Feature.enabled?(:maven_metadata_by_path_with_optimization_fence, default_enabled: :yaml)
# This is an optimization fence which assumes that looking up the Metadatum record by path (globally)
# and then filter down the packages (by project or by group and subgroups) will be cheaper than
# looking up all packages within a project or group and filter them by path.

View File

@ -30,7 +30,7 @@ class ProtectedBranch < ApplicationRecord
end
def self.allow_force_push?(project, ref_name)
return false unless ::Feature.enabled?(:allow_force_push_to_protected_branches, project)
return false unless ::Feature.enabled?(:allow_force_push_to_protected_branches, project, default_enabled: :yaml)
project.protected_branches.allowing_force_push.matching(ref_name).any?
end

View File

@ -21,8 +21,19 @@ module Users
team: 3
}, _suffix: true
scope :without_track_or_series, -> (track, series) do
where.not(track: track).or(where.not(series: series))
scope :without_track_and_series, -> (track, series) do
users = User.arel_table
product_emails = arel_table
join_condition = users[:id].eq(product_emails[:user_id])
.and(product_emails[:track]).eq(tracks[track])
.and(product_emails[:series]).eq(series)
arel_join = users.join(product_emails, Arel::Nodes::OuterJoin).on(join_condition)
joins(arel_join.join_sources)
.where(in_product_marketing_emails: { id: nil })
.select(Arel.sql("DISTINCT ON(#{users.table_name}.id) #{users.table_name}.*"))
end
end
end

View File

@ -9,7 +9,8 @@ class SearchServicePresenter < Gitlab::View::Presenter::Delegated
projects: :with_web_entity_associations,
issues: :with_web_entity_associations,
merge_requests: :with_web_entity_associations,
epics: :with_web_entity_associations
epics: :with_web_entity_associations,
notes: :with_web_entity_associations
}.freeze
SORT_ENABLED_SCOPES = %w(issues merge_requests).freeze

View File

@ -6,8 +6,10 @@ module Ci
def execute
if trigger_from_token
set_application_context_from_trigger(trigger_from_token)
create_pipeline_from_trigger(trigger_from_token)
elsif job_from_token
set_application_context_from_job(job_from_token)
create_pipeline_from_job(job_from_token)
end
@ -87,5 +89,20 @@ module Ci
value: params.except(*PAYLOAD_VARIABLE_HIDDEN_PARAMS).to_json,
variable_type: :file }
end
def set_application_context_from_trigger(trigger)
Gitlab::ApplicationContext.push(
user: trigger.owner,
project: trigger.project
)
end
def set_application_context_from_job(job)
Gitlab::ApplicationContext.push(
user: job.user,
project: job.project,
runner: job.runner
)
end
end
end

View File

@ -23,7 +23,6 @@ module Namespaces
def initialize(track, interval)
@track = track
@interval = interval
@current_batch_user_ids = []
@in_product_marketing_email_records = []
end
@ -35,13 +34,11 @@ module Namespaces
send_email_for_group(group)
end
end
record_sent_emails
end
private
attr_reader :track, :interval, :current_batch_user_ids, :in_product_marketing_email_records
attr_reader :track, :interval, :in_product_marketing_email_records
def send_email_for_group(group)
experiment_enabled_for_group = experiment_enabled_for_group?(group)
@ -49,8 +46,13 @@ module Namespaces
return unless experiment_enabled_for_group
users_for_group(group).each do |user|
send_email(user, group) if can_perform_action?(user, group)
if can_perform_action?(user, group)
send_email(user, group)
track_sent_email(user, track, series)
end
end
save_tracked_emails!
end
def experiment_enabled_for_group?(group)
@ -75,13 +77,9 @@ module Namespaces
end
def users_for_group(group)
group.users.where(email_opted_in: true)
.where.not(id: current_batch_user_ids)
.left_outer_joins(:in_product_marketing_emails)
.merge(
Users::InProductMarketingEmail.without_track_or_series(track, series)
.or(Users::InProductMarketingEmail.where(id: nil))
)
group.users
.where(email_opted_in: true)
.merge(Users::InProductMarketingEmail.without_track_and_series(track, series))
end
# rubocop: enable CodeReuse/ActiveRecord
@ -100,8 +98,6 @@ module Namespaces
def send_email(user, group)
NotificationService.new.in_product_marketing(user.id, group.id, track, series)
track_sent_email(user, group, track, series)
end
def completed_actions
@ -122,13 +118,12 @@ module Namespaces
INTERVAL_DAYS.index(interval)
end
def record_sent_emails
def save_tracked_emails!
Users::InProductMarketingEmail.bulk_insert!(in_product_marketing_email_records)
@in_product_marketing_email_records = []
end
def track_sent_email(user, group, track, series)
current_batch_user_ids << user.id
def track_sent_email(user, track, series)
in_product_marketing_email_records << Users::InProductMarketingEmail.new(
user: user,
track: track,

View File

@ -1,11 +1,11 @@
- @hide_top_links = true
- page_title _("Merge Requests")
- page_title _("Merge requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username)
= render_dashboard_ultimate_trial(current_user)
.page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center
%h1.page-title= _('Merge Requests')
%h1.page-title= _('Merge requests')
- if current_user
.page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0

View File

@ -3,7 +3,7 @@
- aside_title = @group.subgroup? ? _('Subgroup navigation') : _('Group navigation')
- overview_title = @group.subgroup? ? _('Subgroup overview') : _('Group overview')
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation'), 'aria-label': aside_title }
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(@group), 'aria-label': aside_title }
.nav-sidebar-inner-scroll
.context-header
= link_to group_path(@group), title: @group.name do

View File

@ -1,4 +1,4 @@
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('user_side_navigation', 'render', 'user_side_navigation'), 'aria-label': _('User settings') }
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(current_user), 'aria-label': _('User settings') }
.nav-sidebar-inner-scroll
.context-header
= link_to profile_path, title: _('Profile Settings') do

View File

@ -1,4 +1,4 @@
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('projects_side_navigation', 'render', 'projects_side_navigation'), 'aria-label': _('Project navigation') }
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(@project), 'aria-label': _('Project navigation') }
.nav-sidebar-inner-scroll
.context-header
= link_to project_path(@project), title: @project.name do

View File

@ -23,7 +23,7 @@
%th
= s_("ProtectedBranch|Allowed to push")
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project)
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
%th
= s_("ProtectedBranch|Allow force push")
%span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Allow force push for all users with push access.'), 'aria-hidden': 'true' }

View File

@ -21,7 +21,7 @@
= f.label :push_access_levels_attributes, s_("ProtectedBranch|Allowed to push:"), class: 'col-md-2 text-left text-md-right'
.col-md-10
= yield :push_access_levels
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project)
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
.form-group.row
= f.label :allow_force_push, s_("ProtectedBranch|Allow force push:"), class: 'col-md-2 gl-text-left text-md-right'
.col-md-10

View File

@ -33,6 +33,6 @@
%p.small
= _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence }
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project)
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
%td
= render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name }

View File

@ -0,0 +1,5 @@
---
title: Allow users to enable force push to protected branches
merge_request: 57053
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix N+1 for searching notes (comments) scope
merge_request: 57460
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Change the way deprecation information is presented in GraphQL documentation
merge_request: 56864
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Improve payload format of DORA metrics API
merge_request: 57314
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Sort code quality degradations in MR Widget comparison reports
merge_request: 57258
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Optimize group level Maven package finder query
merge_request: 57692
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Fix tooltip position in mini pipeline chart
merge_request: 57425
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Give better feedback when quick actions have no effect
merge_request: 57570
author: Hilco van der Wilk
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Revert Ignore default_enabled value in Feature.enabled?
merge_request: 57707
author:
type: fixed

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323431
milestone: '13.10'
type: development
group: group::source code
default_enabled: false
default_enabled: true

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/325460
milestone: '13.11'
type: development
group: group::optimize
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: prometheus_metrics_method_instrumentation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4304
rollout_issue_url:
milestone: '10.5'
type: ops
group:
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: prometheus_metrics_view_instrumentation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4304
rollout_issue_url:
milestone: '10.5'
type: ops
group:
default_enabled: false

View File

@ -38,14 +38,14 @@ Example response:
```json
[
{ "2021-03-01": 3 },
{ "2021-03-02": 6 },
{ "2021-03-03": 0 },
{ "2021-03-04": 0 },
{ "2021-03-05": 0 },
{ "2021-03-06": 0 },
{ "2021-03-07": 0 },
{ "2021-03-08": 4 }
{ "2021-03-01": 3, "date": "2021-03-01", "value": 3 },
{ "2021-03-02": 6, "date": "2021-03-02", "value": 6 },
{ "2021-03-03": 0, "date": "2021-03-03", "value": 0 },
{ "2021-03-04": 0, "date": "2021-03-04", "value": 0 },
{ "2021-03-05": 0, "date": "2021-03-05", "value": 0 },
{ "2021-03-06": 0, "date": "2021-03-06", "value": 0 },
{ "2021-03-07": 0, "date": "2021-03-07", "value": 0 },
{ "2021-03-08": 4, "date": "2021-03-08", "value": 4 }
]
```
@ -78,13 +78,13 @@ Example response:
```json
[
{ "2021-03-01": 3 },
{ "2021-03-02": 6 },
{ "2021-03-03": 0 },
{ "2021-03-04": 0 },
{ "2021-03-05": 0 },
{ "2021-03-06": 0 },
{ "2021-03-07": 0 },
{ "2021-03-08": 4 }
{ "2021-03-01": 3, "date": "2021-03-01", "value": 3 },
{ "2021-03-02": 6, "date": "2021-03-02", "value": 6 },
{ "2021-03-03": 0, "date": "2021-03-03", "value": 0 },
{ "2021-03-04": 0, "date": "2021-03-04", "value": 0 },
{ "2021-03-05": 0, "date": "2021-03-05", "value": 0 },
{ "2021-03-06": 0, "date": "2021-03-06", "value": 0 },
{ "2021-03-07": 0, "date": "2021-03-07", "value": 0 },
{ "2021-03-08": 4, "date": "2021-03-08", "value": 4 }
]
```

File diff suppressed because it is too large Load Diff

View File

@ -177,13 +177,12 @@ Deleting a protected branch is allowed only by using the web interface; not from
This means that you can't accidentally delete a protected branch from your
command line or a Git client application.
## Allow force push on protected branches **(FREE SELF)**
## Allow force push on protected branches
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15611) in GitLab 13.10.
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-allow-force-push-on-protected-branches).
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15611) in GitLab 13.10 behind a disabled feature flag.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-allow-force-push-on-protected-branches).
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
@ -253,8 +252,8 @@ for details about the pipelines security model.
## Enable or disable allow force push on protected branches **(FREE SELF)**
Allow force push on protected branches is under development and not ready for
production use. It is deployed behind a feature flag that is **disabled by default**.
Allow force push on protected branches is ready for
production use. It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.

View File

@ -57,7 +57,7 @@ class Feature
# use `default_enabled: true` to default the flag to being `enabled`
# unless set explicitly. The default is `disabled`
# TODO: remove the `default_enabled:` and read it from the `defintion_yaml`
# check: https://gitlab.com/gitlab-org/gitlab/-/issues/271275
# check: https://gitlab.com/gitlab-org/gitlab/-/issues/30228
def enabled?(key, thing = nil, type: :development, default_enabled: false)
if check_feature_flags_definition?
if thing && !thing.respond_to?(:flipper_id)
@ -65,11 +65,11 @@ class Feature
"The thing '#{thing.class.name}' for feature flag '#{key}' needs to include `FeatureGate` or implement `flipper_id`"
end
Feature::Definition.valid_usage!(key, type: type, default_enabled: :yaml)
Feature::Definition.valid_usage!(key, type: type, default_enabled: default_enabled)
end
# TODO: Remove rubocop disable comment once `default_enabled` argument is removed https://gitlab.com/gitlab-org/gitlab/-/issues/271275
default_enabled = Feature::Definition.default_enabled?(key) # rubocop:disable Lint/ShadowedArgument
# If `default_enabled: :yaml` we fetch the value from the YAML definition instead.
default_enabled = Feature::Definition.default_enabled?(key) if default_enabled == :yaml
# During setup the database does not exist yet. So we haven't stored a value
# for the feature yet and return the default.

View File

@ -6,6 +6,7 @@ module Gitlab
class CodequalityReports
attr_reader :degradations, :error_message
SEVERITY_PRIORITIES = %w(blocker critical major minor info).map.with_index.to_h.freeze # { "blocker" => 0, "critical" => 1 ... }
CODECLIMATE_SCHEMA_PATH = Rails.root.join('app', 'validators', 'json_schemas', 'codeclimate.json').to_s
def initialize
@ -29,6 +30,12 @@ module Gitlab
@degradations.values
end
def sort_degradations!
@degradations = @degradations.sort_by do |_fingerprint, degradation|
SEVERITY_PRIORITIES[degradation.dig(:severity)]
end.to_h
end
private
def valid_degradation?(degradation)

View File

@ -7,6 +7,11 @@ module Gitlab
def initialize(base_report, head_report)
@base_report = base_report
@head_report = head_report
unless not_found?
@base_report.sort_degradations!
@head_report.sort_degradations!
end
end
def success?

View File

@ -27,7 +27,10 @@ module Gitlab
MD
end
def render_name_and_description(object, level = 3)
# Template methods:
# Methods that return chunks of Markdown for insertion into the document
def render_name_and_description(object, owner: nil, level: 3)
content = []
content << "#{'#' * level} `#{object[:name]}`"
@ -35,10 +38,22 @@ module Gitlab
if object[:description].present?
desc = object[:description].strip
desc += '.' unless desc.ends_with?('.')
end
if object[:is_deprecated]
owner = Array.wrap(owner)
deprecation = schema_deprecation(owner, object[:name])
content << (deprecation&.original_description || desc)
content << render_deprecation(object, owner, :block)
else
content << desc
end
content.join("\n\n")
content.compact.join("\n\n")
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
end
def sorted_by_name(objects)
@ -47,39 +62,25 @@ module Gitlab
objects.sort_by { |o| o[:name] }
end
def render_field(field)
row(render_name(field), render_field_type(field[:type]), render_description(field))
def render_field(field, owner)
render_row(
render_name(field, owner),
render_field_type(field[:type]),
render_description(field, owner, :inline)
)
end
def render_enum_value(value)
row(render_name(value), render_description(value))
def render_enum_value(enum, value)
render_row(render_name(value, enum[:name]), render_description(value, enum[:name], :inline))
end
def row(*values)
"| #{values.join(' | ')} |"
def render_union_member(member)
"- [`#{member}`](##{member.downcase})"
end
def render_name(object)
rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
rendered_name
end
# QUERIES:
# Returns the object description. If the object has been deprecated,
# the deprecation reason will be returned in place of the description.
def render_description(object)
return object[:description] unless object[:is_deprecated]
"**Deprecated:** #{object[:deprecation_reason]}"
end
def render_field_type(type)
"[`#{type[:info]}`](##{type[:name].downcase})"
end
def render_return_type(query)
"Returns #{render_field_type(query[:type])}.\n"
end
# Methods that return parts of the schema, or related information:
# We are ignoring connections and built in types for now,
# they should be added when queries are generated.
@ -103,6 +104,83 @@ module Gitlab
!enum_type[:name].in?(%w[__DirectiveLocation __TypeKind])
end
end
private # DO NOT CALL THESE METHODS IN TEMPLATES
# Template methods
def render_row(*values)
"| #{values.map { |val| val.to_s.squish }.join(' | ')} |"
end
def render_name(object, owner = nil)
rendered_name = "`#{object[:name]}`"
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
rendered_name
end
# Returns the object description. If the object has been deprecated,
# the deprecation reason will be returned in place of the description.
def render_description(object, owner = nil, context = :block)
owner = Array.wrap(owner)
return render_deprecation(object, owner, context) if object[:is_deprecated]
return if object[:description].blank?
desc = object[:description].strip
desc += '.' unless desc.ends_with?('.')
desc
end
def render_deprecation(object, owner, context)
deprecation = schema_deprecation(owner, object[:name])
return deprecation.markdown(context: context) if deprecation
reason = object[:deprecation_reason] || 'Use of this is deprecated.'
"**Deprecated:** #{reason}"
end
def render_field_type(type)
"[`#{type[:info]}`](##{type[:name].downcase})"
end
# Queries
# returns the deprecation information for a field or argument
# See: Gitlab::Graphql::Deprecation
def schema_deprecation(type_name, field_name)
schema_member(type_name, field_name)&.deprecation
end
# Return a part of the schema.
#
# This queries the Schema by owner and name to find:
#
# - fields (e.g. `schema_member('Query', 'currentUser')`)
# - arguments (e.g. `schema_member(['Query', 'project], 'fullPath')`)
def schema_member(type_name, field_name)
type_name = Array.wrap(type_name)
if type_name.size == 2
arg_name = field_name
type_name, field_name = type_name
else
type_name = type_name.first
arg_name = nil
end
return if type_name.nil? || field_name.nil?
type = schema.types[type_name]
return unless type && type.kind.fields?
field = type.fields[field_name]
return field if arg_name.nil?
args = field.arguments
is_mutation = field.mutation && field.mutation <= ::Mutations::BaseMutation
args = args['input'].type.unwrap.arguments if is_mutation
args[arg_name]
end
end
end
end

View File

@ -10,17 +10,20 @@ module Gitlab
# It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs.
#
# Arguments:
# schema - the GraphQL schema definition. For GitLab should be: GitlabSchema.graphql_definition
# schema - the GraphQL schema definition. For GitLab should be: GitlabSchema
# output_dir: The folder where the markdown files will be saved
# template: The path of the haml template to be parsed
class Renderer
include Gitlab::Graphql::Docs::Helper
attr_reader :schema
def initialize(schema, output_dir:, template:)
@output_dir = output_dir
@template = template
@layout = Haml::Engine.new(File.read(template))
@parsed_schema = GraphQLDocs::Parser.new(schema, {}).parse
@parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse
@schema = schema
end
def contents

View File

@ -27,7 +27,7 @@
\
- sorted_by_name(queries).each do |query|
= render_name_and_description(query)
= render_name_and_description(query, owner: 'Query')
\
= render_return_type(query)
- unless query[:arguments].empty?
@ -35,7 +35,7 @@
~ "| Name | Type | Description |"
~ "| ---- | ---- | ----------- |"
- sorted_by_name(query[:arguments]).each do |argument|
= render_field(argument)
= render_field(argument, query[:type][:name])
\
:plain
@ -58,7 +58,7 @@
~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields]).each do |field|
= render_field(field)
= render_field(field, type[:name])
\
:plain
@ -79,7 +79,7 @@
~ "| Value | Description |"
~ "| ----- | ----------- |"
- sorted_by_name(enum[:values]).each do |value|
= render_enum_value(value)
= render_enum_value(enum, value)
\
:plain
@ -121,12 +121,12 @@
\
- graphql_union_types.each do |type|
= render_name_and_description(type, 4)
= render_name_and_description(type, level: 4)
\
One of:
\
- type[:possible_types].each do |type_name|
~ "- [`#{type_name}`](##{type_name.downcase})"
- type[:possible_types].each do |member|
= render_union_member(member)
\
:plain
@ -134,7 +134,7 @@
\
- graphql_interface_types.each do |type|
= render_name_and_description(type, 4)
= render_name_and_description(type, level: 4)
\
Implementations:
\
@ -144,5 +144,5 @@
~ "| Field | Type | Description |"
~ "| ----- | ---- | ----------- |"
- sorted_by_name(type[:fields] + type[:connections]).each do |field|
= render_field(field)
= render_field(field, type[:name])
\

View File

@ -56,15 +56,18 @@ module Gitlab
end
def execute(context, arg)
return unless executable?(context)
return if noop?
count_commands_executed_in(context)
return unless available?(context)
execute_block(action_block, context, arg)
end
def execute_message(context, arg)
return unless executable?(context)
return if noop?
return _('Could not apply %{name} command.') % { name: name } unless available?(context)
if execution_message.respond_to?(:call)
execute_block(execution_message, context, arg)
@ -101,10 +104,6 @@ module Gitlab
private
def executable?(context)
!noop? && available?(context)
end
def count_commands_executed_in(context)
return unless context.respond_to?(:commands_executed_count=)

View File

@ -110,7 +110,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Generate GraphQL docs'
task compile_docs: [:environment, :enable_feature_flags] do
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options)
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema, render_options)
renderer.write
@ -119,7 +119,7 @@ namespace :gitlab do
desc 'GitLab | GraphQL | Check if GraphQL docs are up to date'
task check_docs: [:environment, :enable_feature_flags] do
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options)
renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema, render_options)
doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md'))

View File

@ -8645,6 +8645,9 @@ msgstr ""
msgid "Could not add admins as members"
msgstr ""
msgid "Could not apply %{name} command."
msgstr ""
msgid "Could not archive %{design}. Please try again."
msgstr ""

View File

@ -95,4 +95,47 @@ FactoryBot.define do
}.with_indifferent_access
end
end
# TODO: Use this in all other specs and remove the previous numbered factories
# https://gitlab.com/gitlab-org/gitlab/-/issues/325886
factory :codequality_degradation, class: Hash do
skip_create
# Feel free to add in more configurable properties here
# as the need arises
fingerprint { SecureRandom.hex }
severity { "major" }
Gitlab::Ci::Reports::CodequalityReports::SEVERITY_PRIORITIES.keys.each do |s|
trait s.to_sym do
severity { s }
end
end
initialize_with do
{
"categories": [
"Complexity"
],
"check_name": "argument_count",
"content": {
"body": ""
},
"description": "Avoid parameter lists longer than 5 parameters. [12/5]",
"fingerprint": fingerprint,
"location": {
"path": "file_a.rb",
"lines": {
"begin": 10,
"end": 10
}
},
"other_locations": [],
"remediation_points": 900000,
"severity": severity,
"type": "issue",
"engine_name": "structure"
}.with_indifferent_access
end
end
end

View File

@ -24,7 +24,7 @@ RSpec.describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'M'])
check_page_title('Merge Requests')
check_page_title('Merge requests')
find('body').send_keys([:shift, 'T'])

View File

@ -0,0 +1,22 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
In-Reply-To: <issue_1@localhost>
References: <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost> <issue_1@localhost>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
/close

View File

@ -15,7 +15,6 @@ RSpec.describe 'Graphql Field feature flags' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
subject { result }

View File

@ -128,7 +128,6 @@ RSpec.describe Types::BaseField do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
it 'returns false if the feature is not enabled' do

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SidebarsHelper do
describe '#sidebar_tracking_attributes_by_object' do
subject { helper.sidebar_tracking_attributes_by_object(object) }
before do
allow(helper).to receive(:tracking_enabled?).and_return(true)
end
context 'when object is a project' do
let(:object) { build(:project) }
it 'returns tracking attrs for project' do
expect(subject[:data]).to eq({ track_label: 'projects_side_navigation', track_property: 'projects_side_navigation', track_action: 'render' })
end
end
context 'when object is a group' do
let(:object) { build(:group) }
it 'returns tracking attrs for group' do
expect(subject[:data]).to eq({ track_label: 'groups_side_navigation', track_property: 'groups_side_navigation', track_action: 'render' })
end
end
context 'when object is a user' do
let(:object) { build(:user) }
it 'returns tracking attrs for user' do
expect(subject[:data]).to eq({ track_label: 'user_side_navigation', track_property: 'user_side_navigation', track_action: 'render' })
end
end
context 'when object is something else' do
let(:object) { build(:ci_pipeline) }
it 'returns no attributes' do
expect(subject).to eq({})
end
end
end
end

View File

@ -183,7 +183,6 @@ RSpec.describe API::Helpers do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
context 'with feature enabled' do

View File

@ -8,7 +8,6 @@ RSpec.describe Feature::Gitaly do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
describe ".enabled?" do

View File

@ -123,35 +123,12 @@ RSpec.describe Feature, stub_feature_flags: false do
end
describe '.enabled?' do
let(:disabled_ff_definition) do
Feature::Definition.new(
'development/disabled_feature_flag.yml',
name: 'disabled_feature_flag',
type: 'development',
default_enabled: false
)
it 'returns false for undefined feature' do
expect(described_class.enabled?(:some_random_feature_flag)).to be_falsey
end
let(:enabled_ff_definition) do
Feature::Definition.new(
'development/enabled_feature_flag.yml',
name: 'enabled_feature_flag',
type: 'development',
default_enabled: true
)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{
disabled_ff_definition.key => disabled_ff_definition,
enabled_ff_definition.key => enabled_ff_definition
}
end
end
it 'raises an exception for undefined feature' do
expect { described_class.enabled?(:some_random_feature_flag) }.to raise_error Feature::InvalidFeatureFlagError
it 'returns true for undefined feature with default_enabled' do
expect(described_class.enabled?(:some_random_feature_flag, default_enabled: true)).to be_truthy
end
it 'returns false for existing disabled feature in the database' do
@ -169,58 +146,39 @@ RSpec.describe Feature, stub_feature_flags: false do
it { expect(described_class.send(:l1_cache_backend)).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.send(:l2_cache_backend)).to eq(Rails.cache) }
it 'caches the status in L1 and L2 caches', :request_store, :use_clean_rails_memory_store_caching, :aggregate_failures do
it 'caches the status in L1 and L2 caches',
:request_store, :use_clean_rails_memory_store_caching do
described_class.enable(:enabled_feature_flag)
flipper_features_key = 'flipper/v1/features'
flipper_feature_key = 'flipper/v1/feature/enabled_feature_flag'
flipper_key = "flipper/v1/feature/enabled_feature_flag"
allow(described_class.send(:l2_cache_backend)).to receive(:fetch).twice.and_call_original
allow(described_class.send(:l1_cache_backend)).to receive(:fetch).twice.and_call_original
expect(described_class.send(:l2_cache_backend))
.to receive(:fetch)
.once
.with(flipper_key, expires_in: 1.hour)
.and_call_original
expect(described_class.send(:l1_cache_backend))
.to receive(:fetch)
.once
.with(flipper_key, expires_in: 1.minute)
.and_call_original
2.times do
expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy
end
expect(described_class.send(:l2_cache_backend)).to have_received(:fetch).with(flipper_features_key, expires_in: 1.hour)
expect(described_class.send(:l2_cache_backend)).to have_received(:fetch).with(flipper_feature_key, expires_in: 1.hour)
expect(described_class.send(:l1_cache_backend)).to have_received(:fetch).with(flipper_features_key, expires_in: 1.minute)
expect(described_class.send(:l1_cache_backend)).to have_received(:fetch).with(flipper_feature_key, expires_in: 1.minute)
end
it 'returns the default value when the database does not exist', :aggregate_falures do
a_feature = Feature::Definition.new(
'development/a_feature.yml',
name: 'a_feature',
type: 'development',
default_enabled: true
)
allow(Feature::Definition).to receive(:definitions) do
{ a_feature.key => a_feature }
end
it 'returns the default value when the database does not exist' do
fake_default = double('fake default')
expect(ActiveRecord::Base).to receive(:connection) { raise ActiveRecord::NoDatabaseError, "No database" }
expect(described_class.enabled?(:a_feature)).to eq(true)
expect(described_class.enabled?(:a_feature, default_enabled: fake_default)).to eq(fake_default)
end
context 'cached feature flag', :request_store, :use_clean_rails_memory_store_caching, :aggregate_failures do
context 'cached feature flag', :request_store do
let(:flag) { :some_feature_flag }
let(:some_feature_flag) do
Feature::Definition.new(
"development/#{flag}.yml",
name: flag.to_s,
type: 'development',
default_enabled: true
)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{ some_feature_flag.key => some_feature_flag }
end
described_class.send(:flipper).memoize = false
described_class.enabled?(flag)
end
@ -315,48 +273,63 @@ RSpec.describe Feature, stub_feature_flags: false do
.to raise_error(/The `type:` of/)
end
it 'reads the default from the YAML definition' do
expect(described_class.enabled?(:my_feature_flag)).to eq(false)
it 'when invalid default_enabled is used' do
expect { described_class.enabled?(:my_feature_flag, default_enabled: true) }
.to raise_error(/The `default_enabled:` of/)
end
context 'when YAML definition does not exist for an optional type' do
let(:optional_type) { described_class::Shared::TYPES.find { |name, attrs| attrs[:optional] }.first }
context 'when `default_enabled: :yaml` is used in code' do
it 'reads the default from the YAML definition' do
expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(false)
end
context 'when in dev or test environment' do
it 'raises an error for dev' do
expect { described_class.enabled?(:non_existent_flag, type: optional_type) }
.to raise_error(
Feature::InvalidFeatureFlagError,
"The feature flag YAML definition for 'non_existent_flag' does not exist")
context 'when default_enabled is true in the YAML definition' do
let(:default_enabled) { true }
it 'reads the default from the YAML definition' do
expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(true)
end
end
context 'when in production' do
before do
allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
end
context 'when YAML definition does not exist for an optional type' do
let(:optional_type) { described_class::Shared::TYPES.find { |name, attrs| attrs[:optional] }.first }
context 'when database exists' do
before do
allow(Gitlab::Database).to receive(:exists?).and_return(true)
end
it 'checks the persisted status and returns false' do
expect(described_class).to receive(:get).with(:non_existent_flag).and_call_original
expect(described_class.enabled?(:non_existent_flag, type: optional_type)).to eq(false)
context 'when in dev or test environment' do
it 'raises an error for dev' do
expect { described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml) }
.to raise_error(
Feature::InvalidFeatureFlagError,
"The feature flag YAML definition for 'non_existent_flag' does not exist")
end
end
context 'when database does not exist' do
context 'when in production' do
before do
allow(Gitlab::Database).to receive(:exists?).and_return(false)
allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
end
it 'returns false without checking the status in the database' do
expect(described_class).not_to receive(:get)
context 'when database exists' do
before do
allow(Gitlab::Database).to receive(:exists?).and_return(true)
end
expect(described_class.enabled?(:non_existent_flag, type: optional_type)).to eq(false)
it 'checks the persisted status and returns false' do
expect(described_class).to receive(:get).with(:non_existent_flag).and_call_original
expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
end
end
context 'when database does not exist' do
before do
allow(Gitlab::Database).to receive(:exists?).and_return(false)
end
it 'returns false without checking the status in the database' do
expect(described_class).not_to receive(:get)
expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
end
end
end
end
@ -364,36 +337,13 @@ RSpec.describe Feature, stub_feature_flags: false do
end
end
describe '.disabled?' do
let(:disabled_ff_definition) do
Feature::Definition.new(
'development/disabled_feature_flag.yml',
name: 'disabled_feature_flag',
type: 'development',
default_enabled: false
)
describe '.disable?' do
it 'returns true for undefined feature' do
expect(described_class.disabled?(:some_random_feature_flag)).to be_truthy
end
let(:enabled_ff_definition) do
Feature::Definition.new(
'development/enabled_feature_flag.yml',
name: 'enabled_feature_flag',
type: 'development',
default_enabled: true
)
end
before do
allow(Feature::Definition).to receive(:definitions) do
{
disabled_ff_definition.key => disabled_ff_definition,
enabled_ff_definition.key => enabled_ff_definition
}
end
end
it 'raises an exception for undefined feature' do
expect { described_class.disabled?(:some_random_feature_flag) }.to raise_error Feature::InvalidFeatureFlagError
it 'returns false for undefined feature with default_enabled' do
expect(described_class.disabled?(:some_random_feature_flag, default_enabled: true)).to be_falsey
end
it 'returns true for existing disabled feature in the database' do

View File

@ -6,15 +6,17 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
let(:comparer) { described_class.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
let(:degradation_1) { build(:codequality_degradation_1) }
let(:degradation_2) { build(:codequality_degradation_2) }
let(:major_degradation) { build(:codequality_degradation, :major) }
let(:minor_degradation) { build(:codequality_degradation, :major) }
let(:critical_degradation) { build(:codequality_degradation, :critical) }
let(:blocker_degradation) { build(:codequality_degradation, :blocker) }
describe '#status' do
subject(:report_status) { comparer.status }
context 'when head report has an error' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'returns status failed' do
@ -50,7 +52,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when head report has an error' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'returns the number of new errors' do
@ -70,8 +72,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has an error and head has a different error' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
head_report.add_degradation(minor_degradation)
end
it 'counts the base report error as resolved' do
@ -81,7 +83,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors head has no errors' do
before do
base_report.add_degradation(degradation_1)
base_report.add_degradation(major_degradation)
end
it 'counts the base report errors as resolved' do
@ -91,8 +93,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has the same error' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_1)
base_report.add_degradation(major_degradation)
head_report.add_degradation(major_degradation)
end
it 'returns zero' do
@ -102,7 +104,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'returns zero' do
@ -124,7 +126,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has an error' do
before do
base_report.add_degradation(degradation_1)
base_report.add_degradation(major_degradation)
end
it 'returns zero' do
@ -134,7 +136,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when head report has an error' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'includes the head report error in the count' do
@ -144,8 +146,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head report has errors' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
head_report.add_degradation(minor_degradation)
end
it 'includes errors in the count' do
@ -155,9 +157,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head report has the same error' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
head_report.add_degradation(major_degradation)
head_report.add_degradation(minor_degradation)
end
it 'includes errors in the count' do
@ -179,20 +181,28 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has the same error' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
base_report.add_degradation(critical_degradation)
base_report.add_degradation(blocker_degradation)
head_report.add_degradation(critical_degradation)
head_report.add_degradation(blocker_degradation)
head_report.add_degradation(major_degradation)
head_report.add_degradation(minor_degradation)
end
it 'includes the base report errors' do
expect(existing_errors).to contain_exactly(degradation_1)
it 'includes the base report errors sorted by severity' do
expect(existing_errors).to eq([
blocker_degradation,
critical_degradation,
major_degradation
])
end
end
context 'when base report has errors and head has a different error' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
head_report.add_degradation(minor_degradation)
end
it 'returns an empty array' do
@ -202,7 +212,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
@ -224,19 +234,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has more errors' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
head_report.add_degradation(critical_degradation)
head_report.add_degradation(minor_degradation)
head_report.add_degradation(blocker_degradation)
head_report.add_degradation(major_degradation)
end
it 'includes errors not found in the base report' do
expect(new_errors).to eq([degradation_2])
it 'includes errors not found in the base report sorted by severity' do
expect(new_errors).to eq([
blocker_degradation,
critical_degradation,
minor_degradation
])
end
end
context 'when base report has an error and head has no errors' do
before do
base_report.add_degradation(degradation_1)
base_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
@ -246,11 +262,11 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'returns the head report error' do
expect(new_errors).to eq([degradation_1])
expect(new_errors).to eq([major_degradation])
end
end
@ -268,9 +284,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report errors are still found in the head report' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
head_report.add_degradation(major_degradation)
head_report.add_degradation(minor_degradation)
end
it 'returns an empty array' do
@ -280,18 +296,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has a different error' do
before do
base_report.add_degradation(degradation_1)
head_report.add_degradation(degradation_2)
base_report.add_degradation(major_degradation)
base_report.add_degradation(minor_degradation)
base_report.add_degradation(critical_degradation)
base_report.add_degradation(blocker_degradation)
head_report.add_degradation(major_degradation)
end
it 'returns the base report error' do
expect(resolved_errors).to eq([degradation_1])
it 'returns the base report errors not found in the head report, sorted by severity' do
expect(resolved_errors).to eq([
blocker_degradation,
critical_degradation,
minor_degradation
])
end
end
context 'when base report does not have errors and head has errors' do
before do
head_report.add_degradation(degradation_1)
head_report.add_degradation(major_degradation)
end
it 'returns an empty array' do

View File

@ -77,4 +77,36 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
end
end
end
describe '#sort_degradations!' do
let(:major) { build(:codequality_degradation, :major) }
let(:minor) { build(:codequality_degradation, :minor) }
let(:blocker) { build(:codequality_degradation, :blocker) }
let(:info) { build(:codequality_degradation, :info) }
let(:major_2) { build(:codequality_degradation, :major) }
let(:critical) { build(:codequality_degradation, :critical) }
let(:codequality_report) { described_class.new }
before do
codequality_report.add_degradation(major)
codequality_report.add_degradation(minor)
codequality_report.add_degradation(blocker)
codequality_report.add_degradation(major_2)
codequality_report.add_degradation(info)
codequality_report.add_degradation(critical)
codequality_report.sort_degradations!
end
it 'sorts degradations based on severity' do
expect(codequality_report.degradations.values).to eq([
blocker,
critical,
major,
major_2,
minor,
info
])
end
end
end

View File

@ -22,7 +22,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteOnIssuableHandler do
it_behaves_like :note_handler_shared_examples, true do
let_it_be(:recipient) { user }
let(:update_commands_only) { email_reply_fixture('emails/update_commands_only_reply.eml') }
let(:update_commands_only) { email_reply_fixture('emails/update_commands_only.eml') }
let(:no_content) { email_reply_fixture('emails/no_content_reply.eml') }
let(:commands_in_reply) { email_reply_fixture('emails/commands_in_reply.eml') }
let(:with_quick_actions) { email_reply_fixture('emails/valid_reply_with_quick_actions.eml') }

View File

@ -12,7 +12,6 @@ RSpec.describe Gitlab::GonHelper do
describe '#push_frontend_feature_flag' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
it 'pushes a feature flag to the frontend' do

View File

@ -1,32 +1,35 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
# Returns a Schema that uses the given `type`
def mock_schema(type, field_description)
query_type = Class.new(Types::BaseObject) do
graphql_name 'Query'
let(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
field :foo, type, null: true do
description field_description
let(:query_type) do
Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
# this keeps type and field_description in scope.
t.field :foo, type, null: true, description: field_description do
argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.'
end
end
GraphQL::Schema.define(
query: query_type,
resolve_type: ->(obj, ctx) { raise 'Not a real schema' }
)
end
let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:mock_schema) do
Class.new(GraphQL::Schema) do
def resolve_type(obj, ctx)
raise 'Not a real schema'
end
end
end
let(:field_description) { 'List of objects.' }
subject(:contents) do
mock_schema.query(query_type)
described_class.new(
mock_schema(type, field_description).graphql_definition,
mock_schema,
output_dir: nil,
template: template
).contents
@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
null: false,
deprecated: { reason: 'This is deprecated', milestone: '1.10' },
description: 'A description.'
field :foo_with_args,
type: GraphQL::STRING_TYPE,
null: false,
deprecated: { reason: 'Do not use', milestone: '1.10' },
description: 'A description.' do
argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X'
end
field :bar,
type: GraphQL::STRING_TYPE,
null: false,
description: 'A description.',
deprecated: {
reason: :renamed,
milestone: '1.10',
replacement: 'Query.boom'
}
end
end
@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
| `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated:** This is deprecated. Deprecated in 1.10. |
| `bar` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This was renamed. Use: `Query.boom`. |
| `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This is deprecated. |
| `fooWithArgs` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. Do not use. |
DOC
is_expected.to include(expectation)
end
end
context 'when a Query.field is deprecated' do
let(:type) { ::GraphQL::INT_TYPE }
before do
query_type.field(
name: :bar,
type: type,
null: true,
description: 'A bar',
deprecated: { reason: :renamed, milestone: '10.11', replacement: 'Query.foo' }
)
end
it 'includes the deprecation' do
expectation = <<~DOC
### `bar`
A bar.
WARNING:
**Deprecated** in 10.11.
This was renamed.
Use: `Query.foo`.
Returns [`Int`](#int).
DOC
is_expected.to include(expectation)

View File

@ -102,11 +102,6 @@ RSpec.describe Gitlab::Metrics::Methods do
let(:feature_name) { :some_metric_feature }
let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) }
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
context 'when feature is enabled' do
before do
stub_feature_flags(feature_name => true)

View File

@ -54,11 +54,6 @@ RSpec.describe Gitlab::Metrics do
end
describe '.measure' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
context 'without a transaction' do
it 'returns the return value of the block' do
val = described_class.measure(:foo) { 10 }

View File

@ -127,10 +127,10 @@ RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject.condition_block = proc { false }
end
it "doesn't execute the command" do
it "counts the command as executed" do
subject.execute(context, nil)
expect(context.commands_executed_count).to be_nil
expect(context.commands_executed_count).to eq(1)
expect(context.run).to be false
end
end
@ -238,8 +238,8 @@ RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject.condition_block = proc { false }
end
it 'returns nil' do
expect(subject.execute_message({}, nil)).to be_nil
it 'returns an error message' do
expect(subject.execute_message({}, nil)).to eq('Could not apply command command.')
end
end

View File

@ -16,28 +16,45 @@ RSpec.describe Users::InProductMarketingEmail, type: :model do
it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:track, :series]).with_message('has already been sent') }
end
describe '.without_track_or_series' do
let(:track) { 0 }
describe '.without_track_and_series' do
let(:track) { :create }
let(:series) { 0 }
let_it_be(:in_product_marketing_email) { create(:in_product_marketing_email, series: 0, track: 0) }
let_it_be(:user) { create(:user) }
subject(:without_track_or_series) { described_class.without_track_or_series(track, series) }
subject(:without_track_and_series) { User.merge(described_class.without_track_and_series(track, series)) }
context 'for the same track and series' do
it { is_expected.to be_empty }
before do
create(:in_product_marketing_email, track: :create, series: 0, user: user)
create(:in_product_marketing_email, track: :create, series: 1, user: user)
create(:in_product_marketing_email, track: :verify, series: 0, user: user)
end
context 'for a different track' do
let(:track) { 1 }
it { is_expected.to eq([in_product_marketing_email])}
context 'when given track and series already exists' do
it { expect(without_track_and_series).to be_empty }
end
context 'for a different series' do
let(:series) { 1 }
context 'when track does not exist' do
let(:track) { :trial }
it { is_expected.to eq([in_product_marketing_email])}
it { expect(without_track_and_series).to eq [user] }
end
context 'when series does not exist' do
let(:series) { 2 }
it { expect(without_track_and_series).to eq [user] }
end
context 'when no track or series for a user exists' do
let(:track) { :create }
let(:series) { 0 }
before do
@other_user = create(:user)
end
it { expect(without_track_and_series).to eq [@other_user] }
end
end
end

View File

@ -438,8 +438,6 @@ RSpec.describe API::Features, stub_feature_flags: false do
context 'when the gate value was set' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
Feature.enable(feature_name)
end

View File

@ -126,6 +126,39 @@ RSpec.describe API::Triggers do
end
end
describe 'adding arguments to the application context' do
subject { subject_proc.call }
let(:expected_params) { { client_id: "user/#{user.id}", project: project.full_path } }
let(:subject_proc) { proc { post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' } } }
context 'when triggering a pipeline from a trigger token' do
it_behaves_like 'storing arguments in the application context'
it_behaves_like 'not executing any extra queries for the application context'
end
context 'when triggered from another running job' do
let!(:trigger) { }
let!(:trigger_request) { }
context 'when other job is triggered by a user' do
let(:trigger_token) { create(:ci_build, :running, project: project, user: user).token }
it_behaves_like 'storing arguments in the application context'
it_behaves_like 'not executing any extra queries for the application context'
end
context 'when other job is triggered by a runner' do
let(:trigger_token) { create(:ci_build, :running, project: project, runner: runner).token }
let(:runner) { create(:ci_runner) }
let(:expected_params) { { client_id: "runner/#{runner.id}", project: project.full_path } }
it_behaves_like 'storing arguments in the application context'
it_behaves_like 'not executing any extra queries for the application context', 1
end
end
end
context 'when is triggered by a pipeline hook' do
it 'does not create a new pipeline' do
expect do

View File

@ -73,7 +73,6 @@ RSpec.describe API::UsageData do
context 'with unknown event' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
it 'returns status ok' do
@ -150,7 +149,6 @@ RSpec.describe API::UsageData do
context 'with unknown event' do
before do
skip_feature_flags_yaml_validation
skip_default_enabled_yaml_check
end
it 'returns status ok' do

View File

@ -138,38 +138,64 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do
it { is_expected.not_to send_in_product_marketing_email }
end
context 'when the user has already received any marketing email in this batch' do
before do
other_group = create(:group)
other_group.add_developer(user)
create(:onboarding_progress, namespace: other_group, created_at: previous_action_completed_at, git_write_at: current_action_completed_at)
describe 'do not send emails twice' do
subject { described_class.send_for_all_tracks_and_intervals }
let(:user) { create(:user, email_opted_in: true) }
context 'when user already got a specific email' do
before do
create(:in_product_marketing_email, user: user, track: track, series: 0)
end
it { is_expected.not_to send_in_product_marketing_email(user.id, anything, track, 0) }
end
# For any group Notify is called exactly once
it { is_expected.to send_in_product_marketing_email(user.id, anything, :create, 0) }
end
context 'when user already got sent the whole track' do
before do
0.upto(2) do |series|
create(:in_product_marketing_email, user: user, track: track, series: series)
end
end
context 'when user has already received a specific series in a track before' do
before do
described_class.new(:create, described_class::INTERVAL_DAYS.index(interval)).execute
it 'does not send any of the emails anymore', :aggregate_failures do
0.upto(2) do |series|
expect(subject).not_to send_in_product_marketing_email(user.id, anything, track, series)
end
end
end
# For any group Notify is called exactly once
it { is_expected.to send_in_product_marketing_email(user.id, anything, :create, described_class::INTERVAL_DAYS.index(interval)) }
context 'when user is in two groups' do
let(:other_group) { create(:group) }
context 'when different series' do
let(:interval) { 5 }
let(:actions_completed) { { created_at: frozen_time - 6.days } }
before do
other_group.add_developer(user)
end
it { is_expected.to send_in_product_marketing_email(user.id, anything, :create, described_class::INTERVAL_DAYS.index(interval)) }
end
context 'when both groups would get the same email' do
before do
create(:onboarding_progress, namespace: other_group, **actions_completed)
end
context 'when different track' do
let(:track) { :verify }
let(:interval) { 1 }
let(:actions_completed) { { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days } }
it 'does not send the same email twice' do
subject
it { is_expected.to send_in_product_marketing_email(user.id, anything, :verify, described_class::INTERVAL_DAYS.index(interval)) }
expect(Notify).to have_received(:in_product_marketing_email).with(user.id, anything, :create, 0).once
end
end
context 'when other group gets a different email' do
before do
create(:onboarding_progress, namespace: other_group, created_at: previous_action_completed_at, git_write_at: frozen_time - 2.days)
end
it 'sends both emails' do
subject
expect(Notify).to have_received(:in_product_marketing_email).with(user.id, group.id, :create, 0)
expect(Notify).to have_received(:in_product_marketing_email).with(user.id, other_group.id, :verify, 0)
end
end
end
end

View File

@ -345,6 +345,24 @@ RSpec.describe Notes::CreateService do
expect(note.errors[:commands_only]).to be_present
end
it 'adds commands failed message to note errors' do
note_text = %(/reopen)
note = described_class.new(project, user, opts.merge(note: note_text)).execute
expect(note.errors[:commands_only]).to contain_exactly('Could not apply reopen command.')
end
it 'generates success and failed error messages' do
note_text = %(/close\n/reopen)
service = double(:service)
allow(Issues::UpdateService).to receive(:new).and_return(service)
expect(service).to receive(:execute)
note = described_class.new(project, user, opts.merge(note: note_text)).execute
expect(note.errors[:commands_only]).to contain_exactly('Closed this issue. Could not apply reopen command.')
end
end
end

View File

@ -478,7 +478,7 @@ RSpec.describe QuickActions::InterpretService do
end
end
shared_examples 'empty command' do |error_msg|
shared_examples 'failed command' do |error_msg|
it 'populates {} if content contains an unsupported command' do
_, updates, _ = service.execute(content, issuable)
@ -607,10 +607,10 @@ RSpec.describe QuickActions::InterpretService do
issuable.update!(confidential: true)
end
it 'does not return the success message' do
it 'returns an error message' do
_, _, message = service.execute(content, issuable)
expect(message).to be_empty
expect(message).to eq('Could not apply confidential command.')
end
it 'is not part of the available commands' do
@ -728,7 +728,7 @@ RSpec.describe QuickActions::InterpretService do
context 'can not be merged when logged user does not have permissions' do
let(:service) { described_class.new(project, create(:user)) }
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply merge command.' do
let(:content) { "/merge" }
let(:issuable) { merge_request }
end
@ -737,7 +737,7 @@ RSpec.describe QuickActions::InterpretService do
context 'can not be merged when sha does not match' do
let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: 'othersha' }) }
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply merge command.' do
let(:content) { "/merge" }
let(:issuable) { merge_request }
end
@ -755,21 +755,21 @@ RSpec.describe QuickActions::InterpretService do
end
context 'issue can not be merged' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply merge command.' do
let(:content) { "/merge" }
let(:issuable) { issue }
end
end
context 'non persisted merge request cant be merged' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply merge command.' do
let(:content) { "/merge" }
let(:issuable) { build(:merge_request) }
end
end
context 'not persisted merge request can not be merged' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply merge command.' do
let(:content) { "/merge" }
let(:issuable) { build(:merge_request, source_project: project) }
end
@ -786,7 +786,7 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/title' }
let(:issuable) { issue }
end
@ -869,12 +869,12 @@ RSpec.describe QuickActions::InterpretService do
end
end
it_behaves_like 'empty command', "Failed to assign a user because no user was found." do
it_behaves_like 'failed command', "Failed to assign a user because no user was found." do
let(:content) { '/assign @abcd1234' }
let(:issuable) { issue }
end
it_behaves_like 'empty command', "Failed to assign a user because no user was found." do
it_behaves_like 'failed command', "Failed to assign a user because no user was found." do
let(:content) { '/assign' }
let(:issuable) { issue }
end
@ -890,7 +890,7 @@ RSpec.describe QuickActions::InterpretService do
context 'with an issue instead of a merge request' do
let(:issuable) { issue }
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply assign_reviewer command.'
end
# CE does not have multiple reviewers
@ -935,7 +935,7 @@ RSpec.describe QuickActions::InterpretService do
context 'with an incorrect user' do
let(:content) { '/assign_reviewer @abcd1234' }
it_behaves_like 'empty command', "Failed to assign a reviewer because no user was found."
it_behaves_like 'failed command', "Failed to assign a reviewer because no user was found."
end
context 'with the "reviewer" alias' do
@ -953,7 +953,7 @@ RSpec.describe QuickActions::InterpretService do
context 'with no user' do
let(:content) { '/assign_reviewer' }
it_behaves_like 'empty command', "Failed to assign a reviewer because no user was found."
it_behaves_like 'failed command', "Failed to assign a reviewer because no user was found."
end
context 'includes only the user reference with extra text' do
@ -977,7 +977,7 @@ RSpec.describe QuickActions::InterpretService do
context 'with an issue instead of a merge request' do
let(:issuable) { issue }
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply unassign_reviewer command.'
end
context 'with anything after the command' do
@ -1035,14 +1035,20 @@ RSpec.describe QuickActions::InterpretService do
end
end
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { issue }
end
context 'project milestones' do
before do
milestone
end
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { merge_request }
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { issue }
end
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { merge_request }
end
end
context 'only group milestones available' do
@ -1181,7 +1187,7 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply due command.' do
let(:content) { '/due 2016-08-28' }
let(:issuable) { merge_request }
end
@ -1211,7 +1217,7 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply remove_due_date command.' do
let(:content) { '/remove_due_date' }
let(:issuable) { merge_request }
end
@ -1221,12 +1227,12 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/estimate' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/estimate abc' }
let(:issuable) { issue }
end
@ -1257,12 +1263,12 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/spend' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/spend abc' }
let(:issuable) { issue }
end
@ -1323,7 +1329,7 @@ RSpec.describe QuickActions::InterpretService do
end
context 'if issuable is a Commit' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply todo command.' do
let(:issuable) { commit }
end
end
@ -1379,7 +1385,7 @@ RSpec.describe QuickActions::InterpretService do
end
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/copy_metadata' }
let(:issuable) { issue }
end
@ -1419,19 +1425,19 @@ RSpec.describe QuickActions::InterpretService do
end
context 'cross project references' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:other_project) { create(:project, :public) }
let(:source_issuable) { create(:labeled_issue, project: other_project, labels: [todo_label, inreview_label]) }
let(:content) { "/copy_metadata #{source_issuable.to_reference(project)}" }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { "/copy_metadata imaginary##{non_existing_record_iid}" }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:other_project) { create(:project, :private) }
let(:source_issuable) { create(:issue, project: other_project) }
@ -1448,7 +1454,7 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/duplicate' }
let(:issuable) { issue }
end
@ -1461,12 +1467,12 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'empty command', _('Failed to mark this issue as a duplicate because referenced issue was not found.') do
it_behaves_like 'failed command', _('Failed to mark this issue as a duplicate because referenced issue was not found.') do
let(:content) { "/duplicate imaginary##{non_existing_record_iid}" }
let(:issuable) { issue }
end
it_behaves_like 'empty command', _('Failed to mark this issue as a duplicate because referenced issue was not found.') do
it_behaves_like 'failed command', _('Failed to mark this issue as a duplicate because referenced issue was not found.') do
let(:other_project) { create(:project, :private) }
let(:issue_duplicate) { create(:issue, project: other_project) }
@ -1481,62 +1487,62 @@ RSpec.describe QuickActions::InterpretService do
let(:issue) { create(:issue, project: project, author: visitor) }
let(:service) { described_class.new(project, visitor) }
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply assign command.' do
let(:content) { "/assign @#{developer.username}" }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply unassign command.' do
let(:content) { '/unassign' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply milestone command.' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply remove_milestone command.' do
let(:content) { '/remove_milestone' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply label command.' do
let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply unlabel command.' do
let(:content) { %(/unlabel ~"#{inprogress.title}") }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply relabel command.' do
let(:content) { %(/relabel ~"#{inprogress.title}") }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply due command.' do
let(:content) { '/due tomorrow' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply remove_due_date command.' do
let(:content) { '/remove_due_date' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply confidential command.' do
let(:content) { '/confidential' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply lock command.' do
let(:content) { '/lock' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply unlock command.' do
let(:content) { '/unlock' }
let(:issuable) { issue }
end
@ -1554,19 +1560,19 @@ RSpec.describe QuickActions::InterpretService do
end
context 'ignores command with no argument' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/award' }
let(:issuable) { issue }
end
end
context 'ignores non-existing / invalid emojis' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/award noop' }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/award :lorem_ipsum:' }
let(:issuable) { issue }
end
@ -1576,7 +1582,7 @@ RSpec.describe QuickActions::InterpretService do
let(:content) { '/award :100:' }
let(:issuable) { commit }
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply award command.'
end
end
@ -1622,14 +1628,14 @@ RSpec.describe QuickActions::InterpretService do
end
context 'ignores command with no argument' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply target_branch command.' do
let(:content) { '/target_branch' }
let(:issuable) { another_merge_request }
end
end
context 'ignores non-existing target branch' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command', 'Could not apply target_branch command.' do
let(:content) { '/target_branch totally_non_existing_branch' }
let(:issuable) { another_merge_request }
end
@ -1697,34 +1703,34 @@ RSpec.describe QuickActions::InterpretService do
create(:board, project: project)
end
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply board_move command.'
end
context 'if the given label does not exist' do
let(:issuable) { issue }
let(:content) { '/board_move ~"Fake Label"' }
it_behaves_like 'empty command', 'Failed to move this issue because label was not found.'
it_behaves_like 'failed command', 'Failed to move this issue because label was not found.'
end
context 'if multiple labels are given' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{inreview.title}" ~"#{todo.title}"} }
it_behaves_like 'empty command', 'Failed to move this issue because only a single label can be provided.'
it_behaves_like 'failed command', 'Failed to move this issue because only a single label can be provided.'
end
context 'if the given label is not a list on the board' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{bug.title}"} }
it_behaves_like 'empty command', 'Failed to move this issue because label was not found.'
it_behaves_like 'failed command', 'Failed to move this issue because label was not found.'
end
context 'if issuable is not an Issue' do
let(:issuable) { merge_request }
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply board_move command.'
end
end
@ -1732,7 +1738,7 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { commit }
context 'ignores command with no argument' do
it_behaves_like 'empty command' do
it_behaves_like 'failed command' do
let(:content) { '/tag' }
end
end
@ -1797,7 +1803,7 @@ RSpec.describe QuickActions::InterpretService do
context 'if issuable is not an Issue' do
let(:issuable) { merge_request }
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply create_merge_request command.'
end
context "when logged user cannot create_merge_requests in the project" do
@ -1807,14 +1813,14 @@ RSpec.describe QuickActions::InterpretService do
project.add_developer(developer)
end
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply create_merge_request command.'
end
context 'when logged user cannot push code to the project' do
let(:project) { create(:project, :private) }
let(:service) { described_class.new(project, create(:user)) }
it_behaves_like 'empty command'
it_behaves_like 'failed command', 'Could not apply create_merge_request command.'
end
it 'populates create_merge_request with branch_name and issue iid' do
@ -1953,7 +1959,7 @@ RSpec.describe QuickActions::InterpretService do
context 'invite_email command' do
let_it_be(:issuable) { issue }
it_behaves_like 'empty command', "No email participants were added. Either none were provided, or they already exist." do
it_behaves_like 'failed command', "No email participants were added. Either none were provided, or they already exist." do
let(:content) { '/invite_email' }
end
@ -1964,7 +1970,7 @@ RSpec.describe QuickActions::InterpretService do
issuable.issue_email_participants.create!(email: "a@gitlab.com")
end
it_behaves_like 'empty command', "No email participants were added. Either none were provided, or they already exist."
it_behaves_like 'failed command', "No email participants were added. Either none were provided, or they already exist."
end
context 'with new email participants' do

View File

@ -82,8 +82,8 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable|
let!(:email_raw) { update_commands_only }
context 'and current user cannot update noteable' do
it 'raises a CommandsOnlyNoteError' do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
it 'does not raise an error' do
expect { receiver.execute }.not_to raise_error
end
end
@ -92,15 +92,11 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable|
project.add_developer(user)
end
it 'does not raise an error', unless: forwardable do
it 'does not raise an error' do
expect { receiver.execute }.to change { noteable.resource_state_events.count }.by(1)
expect(noteable.reload).to be_closed
end
it 'raises an InvalidNoteError', if: forwardable do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
end
end
end
end

View File

@ -24,3 +24,13 @@ RSpec.shared_examples 'page has active sub tab' do |title|
.to have_content(title)
end
end
RSpec.shared_examples 'sidebar includes snowplow attributes' do |track_action, track_label, track_property|
specify do
allow(view).to receive(:tracking_enabled?).and_return(true)
render
expect(rendered).to have_css(".nav-sidebar[data-track-action=\"#{track_action}\"][data-track-label=\"#{track_label}\"][data-track-property=\"#{track_property}\"]")
end
end

View File

@ -10,4 +10,5 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
it_behaves_like 'has nav sidebar'
it_behaves_like 'sidebar includes snowplow attributes', 'render', 'groups_side_navigation', 'groups_side_navigation'
end

View File

@ -10,4 +10,5 @@ RSpec.describe 'layouts/nav/sidebar/_profile' do
end
it_behaves_like 'has nav sidebar'
it_behaves_like 'sidebar includes snowplow attributes', 'render', 'user_side_navigation', 'user_side_navigation'
end

View File

@ -346,4 +346,6 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
end
end
it_behaves_like 'sidebar includes snowplow attributes', 'render', 'projects_side_navigation', 'projects_side_navigation'
end