Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1838e24407
commit
09ff71d425
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
query getDagVisData($projectPath: ID!, $iid: ID!) {
|
||||
project(fullPath: $projectPath) {
|
||||
pipeline(iid: $iid) {
|
||||
id
|
||||
stages {
|
||||
nodes {
|
||||
name
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow users to enable force push to protected branches
|
||||
merge_request: 57053
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix N+1 for searching notes (comments) scope
|
||||
merge_request: 57460
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change the way deprecation information is presented in GraphQL documentation
|
||||
merge_request: 56864
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve payload format of DORA metrics API
|
||||
merge_request: 57314
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Sort code quality degradations in MR Widget comparison reports
|
||||
merge_request: 57258
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Optimize group level Maven package finder query
|
||||
merge_request: 57692
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix tooltip position in mini pipeline chart
|
||||
merge_request: 57425
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Give better feedback when quick actions have no effect
|
||||
merge_request: 57570
|
||||
author: Hilco van der Wilk
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Revert Ignore default_enabled value in Feature.enabled?
|
||||
merge_request: 57707
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
\
|
||||
|
|
|
|||
|
|
@ -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=)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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') }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue