Commit 96b41391 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 61130ebf 8eb4b377
......@@ -250,13 +250,19 @@ update-static-analysis-cache:
static-analysis:
extends:
- .static-analysis-base
- .rails:rules:code-backstage-qa
- .static-analysis:rules:ee-and-foss
stage: test
parallel: 4
script:
- run_timed_command "retry yarn install --frozen-lockfile"
- scripts/static-analysis
static-analysis as-if-foss:
extends:
- static-analysis
- .static-analysis:rules:as-if-foss
- .as-if-foss
rspec migration pg11:
extends:
- .rspec-base-pg11
......
......@@ -931,6 +931,25 @@
- <<: *if-merge-request
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
#########################
# Static analysis rules #
#########################
.static-analysis:rules:ee-and-foss:
rules:
- changes: *code-backstage-qa-patterns
.static-analysis:rules:as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-as-if-foss
changes: *code-backstage-qa-patterns
- <<: *if-security-merge-request
changes: *code-backstage-qa-patterns
- <<: *if-merge-request
changes: *ci-patterns
#######################
# Vendored gems rules #
#######################
......
import * as Sentry from '@sentry/browser';
import { updateListQueries } from 'ee_else_ce/boards/constants';
import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
import {
BoardType,
ListType,
......@@ -12,7 +8,11 @@ import {
titleQueries,
subscriptionQueries,
SupportedFilters,
} from '~/boards/constants';
updateListQueries,
} from 'ee_else_ce/boards/constants';
import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { convertObjectPropsToCamelCase, urlParamsToObject } from '~/lib/utils/common_utils';
......
......@@ -7,6 +7,11 @@ module Mutations
include FindsProject
graphql_name 'ConfigureSast'
description <<~DESC
Configure SAST for a project by enabling SAST in a new or modified
`.gitlab-ci.yml` file in a new branch. The new branch and a URL to
create a Merge Request are a part of the response.
DESC
argument :project_path, GraphQL::ID_TYPE,
required: true,
......@@ -16,12 +21,12 @@ module Mutations
required: true,
description: 'SAST CI configuration for the project.'
field :status, GraphQL::STRING_TYPE, null: false,
description: 'Status of creating the commit for the supplied SAST CI configuration.'
field :success_path, GraphQL::STRING_TYPE, null: true,
description: 'Redirect path to use when the response is successful.'
field :branch, GraphQL::STRING_TYPE, null: true,
description: 'Branch that has the new/modified `.gitlab-ci.yml` file.'
authorize :push_code
def resolve(project_path:, configuration:)
......@@ -35,9 +40,9 @@ module Mutations
def prepare_response(result)
{
status: result[:status],
success_path: result[:success_path],
errors: Array(result[:errors])
branch: result.payload[:branch],
success_path: result.payload[:success_path],
errors: result.errors
}
end
end
......
# frozen_string_literal: true
module Mutations
module Security
module CiConfiguration
class ConfigureSecretDetection < BaseMutation
include FindsProject
graphql_name 'ConfigureSecretDetection'
description <<~DESC
Configure Secret Detection for a project by enabling Secret Detection
in a new or modified `.gitlab-ci.yml` file in a new branch. The new
branch and a URL to create a Merge Request are a part of the
response.
DESC
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'Full path of the project.'
field :success_path, GraphQL::STRING_TYPE, null: true,
description: 'Redirect path to use when the response is successful.'
field :branch, GraphQL::STRING_TYPE, null: true,
description: 'Branch that has the new/modified `.gitlab-ci.yml` file.'
authorize :push_code
def resolve(project_path:)
project = authorized_find!(project_path)
result = ::Security::CiConfiguration::SecretDetectionCreateService.new(project, current_user).execute
prepare_response(result)
end
private
def prepare_response(result)
{
branch: result.payload[:branch],
success_path: result.payload[:success_path],
errors: result.errors
}
end
end
end
end
end
......@@ -16,6 +16,7 @@ module Types
mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken
mount_mutation Mutations::AlertManagement::HttpIntegration::Destroy
mount_mutation Mutations::Security::CiConfiguration::ConfigureSast
mount_mutation Mutations::Security::CiConfiguration::ConfigureSecretDetection
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
......
......@@ -72,11 +72,7 @@ module Timebox
groups = groups.compact if groups.is_a? Array
groups = [] if groups.nil?
if Feature.enabled?(:optimized_timebox_queries, default_enabled: true)
from_union([where(project_id: projects), where(group_id: groups)], remove_duplicates: false)
else
where(project_id: projects).or(where(group_id: groups))
end
from_union([where(project_id: projects), where(group_id: groups)], remove_duplicates: false)
end
# A timebox is within the timeframe (start_date, end_date) if it overlaps
......
......@@ -1677,6 +1677,12 @@ class User < ApplicationRecord
def invalidate_issue_cache_counts
Rails.cache.delete(['users', id, 'assigned_open_issues_count'])
if Feature.enabled?(:assigned_open_issues_cache, default_enabled: :yaml)
run_after_commit do
Users::UpdateOpenIssueCountWorker.perform_async(self.id)
end
end
end
def invalidate_merge_request_cache_counts
......
# frozen_string_literal: true
module Security
module CiConfiguration
class BaseCreateService
attr_reader :branch_name, :current_user, :project
def initialize(project, current_user)
@project = project
@current_user = current_user
@branch_name = project.repository.next_branch(next_branch)
end
def execute
project.repository.add_branch(current_user, branch_name, project.default_branch)
attributes_for_commit = attributes
result = ::Files::MultiService.new(project, current_user, attributes_for_commit).execute
return ServiceResponse.error(message: result[:message]) unless result[:status] == :success
track_event(attributes_for_commit)
ServiceResponse.success(payload: { branch: branch_name, success_path: successful_change_path })
rescue Gitlab::Git::PreReceiveError => e
ServiceResponse.error(message: e.message)
rescue StandardError
project.repository.rm_branch(current_user, branch_name) if project.repository.branch_exists?(branch_name)
raise
end
private
def attributes
{
commit_message: message,
branch_name: branch_name,
start_branch: branch_name,
actions: [action]
}
end
def existing_gitlab_ci_content
@gitlab_ci_yml ||= project.repository.gitlab_ci_yml_for(project.repository.root_ref_sha)
YAML.safe_load(@gitlab_ci_yml) if @gitlab_ci_yml
end
def successful_change_path
merge_request_params = { source_branch: branch_name, description: description }
Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
end
def track_event(attributes_for_commit)
action = attributes_for_commit[:actions].first
Gitlab::Tracking.event(
self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
)
end
end
end
end
......@@ -2,64 +2,30 @@
module Security
module CiConfiguration
class SastCreateService < ::BaseService
class SastCreateService < ::Security::CiConfiguration::BaseCreateService
attr_reader :params
def initialize(project, current_user, params)
@project = project
@current_user = current_user
super(project, current_user)
@params = params
@branch_name = @project.repository.next_branch('set-sast-config')
end
def execute
attributes_for_commit = attributes
result = ::Files::MultiService.new(@project, @current_user, attributes_for_commit).execute
if result[:status] == :success
result[:success_path] = successful_change_path
track_event(attributes_for_commit)
else
result[:errors] = result[:message]
end
result
rescue Gitlab::Git::PreReceiveError => e
{ status: :error, errors: e.message }
end
private
def attributes
actions = Security::CiConfiguration::SastBuildActions.new(@project.auto_devops_enabled?, @params, existing_gitlab_ci_content).generate
@project.repository.add_branch(@current_user, @branch_name, @project.default_branch)
message = _('Set .gitlab-ci.yml to enable or configure SAST')
{
commit_message: message,
branch_name: @branch_name,
start_branch: @branch_name,
actions: actions
}
def action
Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_gitlab_ci_content).generate
end
def existing_gitlab_ci_content
gitlab_ci_yml = @project.repository.gitlab_ci_yml_for(@project.repository.root_ref_sha)
YAML.safe_load(gitlab_ci_yml) if gitlab_ci_yml
def next_branch
'set-sast-config'
end
def successful_change_path
description = _('Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
merge_request_params = { source_branch: @branch_name, description: description }
Gitlab::Routing.url_helpers.project_new_merge_request_url(@project, merge_request: merge_request_params)
def message
_('Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist')
end
def track_event(attributes_for_commit)
action = attributes_for_commit[:actions].first
Gitlab::Tracking.event(
self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
)
def description
_('Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
end
end
end
......
......@@ -74,7 +74,7 @@ module Security
def sast_excluded_analyzers
strong_memoize(:sast_excluded_analyzers) do
all_analyzers = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS.split(', ') rescue []
all_analyzers = Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS.split(', ') rescue []
enabled_analyzers = sast_default_analyzers.split(',').map(&:strip) rescue []
excluded_analyzers = gitlab_ci_yml_attributes["SAST_EXCLUDED_ANALYZERS"] || sast_template_attributes["SAST_EXCLUDED_ANALYZERS"]
......
# frozen_string_literal: true
module Security
module CiConfiguration
class SecretDetectionCreateService < ::Security::CiConfiguration::BaseCreateService
private
def action
Security::CiConfiguration::SecretDetectionBuildAction.new(project.auto_devops_enabled?, existing_gitlab_ci_content).generate
end
def next_branch
'set-secret-detection-config'
end
def message
_('Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist')
end
def description
_('Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings.')
end
end
end
end
# frozen_string_literal: true
module Users
# Service class for calculating and caching the number of assigned open issues for a user.
class UpdateAssignedOpenIssueCountService
attr_accessor :target_user
def initialize(target_user:)
@target_user = target_user
raise ArgumentError.new("Please provide a target user") unless target_user.is_a?(User)
end
def execute
value = calculate_count
Rails.cache.write(cache_key, value, expires_in: User::COUNT_CACHE_VALIDITY_PERIOD)
ServiceResponse.success(payload: { count: value })
rescue StandardError => e
ServiceResponse.error(message: e.message)
end
private
def cache_key
['users', target_user.id, 'assigned_open_issues_count']
end
def calculate_count
IssuesFinder.new(target_user, assignee_id: target_user.id, state: 'opened', non_archived: true).execute.count
end
end
end
......@@ -2440,6 +2440,14 @@
:weight: 1
:idempotent:
:tags: []
- :name: users_update_open_issue_count
:feature_category: :users
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: web_hook
:feature_category: :integrations
:has_external_dependencies: true
......
# frozen_string_literal: true
module Users
class UpdateOpenIssueCountWorker
include ApplicationWorker
feature_category :users
idempotent!
def perform(target_user_ids)
target_user_ids = Array.wrap(target_user_ids)
raise ArgumentError.new('No target user ID provided') if target_user_ids.empty?
target_users = User.id_in(target_user_ids)
raise ArgumentError.new('No valid target user ID provided') if target_users.empty?
target_users.each do |user|
Users::UpdateAssignedOpenIssueCountService.new(target_user: user).execute
end
rescue StandardError => exception
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(exception)
end
end
end
---
title: Add ConfigureSecretDetection graphql mutation
merge_request: 58230
author:
type: added
---
title: Update 5 min app metric defintions
merge_request: 60364
author:
type: other
---
title: Update instance_auto_devops_enabled metric team metadata
merge_request: 60374
author:
type: other
---
title: Update AWS deploy templates metrics
merge_request: 60377
author:
type: other
---
title: Recalculate assigned open issues count after cache invalidation
merge_request: 59961
author:
type: performance
---
title: Remove optimized_timebox_queries feature flag
merge_request: 60326
author:
type: other
---
name: optimized_timebox_queries
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32953
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34043
milestone: '13.1'
name: assigned_open_issues_cache
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59961
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/325470
group: group::product planning
type: development
group: group::project management
default_enabled: true
default_enabled: false
......@@ -6,4 +6,3 @@ filenames:
- ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
- app/assets/javascripts/repository/queries/blob_info.query.graphql
- ee/app/assets/javascripts/security_configuration/graphql/configure_dependency_scanning.mutation.graphql
- ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_monthly
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
description: Number of projects using 5 min production app CI template in last 7 days.
product_section: seg
product_stage: deploy
product_group: "group::5-min-app"
product_category: five_minute_production_app
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_aws_cf_deploy_ec2_monthly
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
description: "Count of projects using `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template in last 28 days."
product_section: ops
product_stage: release
product_group: "group::release"
product_category: continuous_delivery
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_aws_deploy_ecs_monthly
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
description: "Count of projects using `AWS/Deploy-ECS.gitlab-ci.yml` template in last 28 days."
product_section: ops
product_stage: release
product_group: "group::release"
product_category: continuous_delivery
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_weekly
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
description: Number of projects using 5 min production app CI template in last 7 days.
product_section: seg
product_stage: deploy
product_group: "group::5-min-app"
product_category: five_minute_production_app
value_type: number
status: data_available
time_frame: 7d
data_source: redis_hll
distribution:
- ce
- ee
tier: []
skip_validation: true
tier:
- free
- premium
- ultimate
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_aws_cf_deploy_ec2_weekly
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
description: "Count of projects using `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template in last 7 days."
product_section: ops
product_stage: release
product_group: "group::release"
product_category: continuous_delivery
value_type: number
status: data_available
time_frame: 7d
data_source: redis_hll
distribution:
- ce
- ee
tier: []
skip_validation: true
tier:
- free
- premium
- ultimate
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_aws_deploy_ecs_weekly
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
description: "Count of projects using `AWS/Deploy-ECS.gitlab-ci.yml` template in last 7 days."
product_section: ops
product_stage: release
product_group: "group::release"
product_category: continuous_delivery
value_type: number
status: data_available
time_frame: 7d
data_source: redis_hll
distribution:
- ce
- ee
tier: []
skip_validation: true
tier:
- free
- premium
- ultimate
......@@ -18,3 +18,4 @@ tier:
- free
- premium
- ultimate
value_json_schema: 'config/metrics/objects_schemas/projects_with_enabled_alert_integrations_histogram.json'
{
"type": "object",
"description": "Histogram (buckets 1 to 100) of projects with at least 1 enabled integration"
"propertyNames": {
"pattern": "^[1-9][0-9]?0?$"
}
}
......@@ -67,6 +67,9 @@
},
"skip_validation": {
"type": "boolean"
},
"value_json_schema": {
"type": "string"
}
}
}
---
key_path: instance_auto_devops_enabled
description: Whether auto DevOps is enabled
product_section: growth
product_stage: growth
product_group: group::product intelligence
product_category: collection
product_section: ops
product_stage: configure
product_group: group::configure
product_category: auto_devops
value_type: boolean
status: data_available
time_frame: none
data_source:
data_source: database
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate
......@@ -18,4 +18,4 @@ tier:
- free
- premium
- ultimate
object_json_schema: 'config/metrics/objects_schemas/topology_schema.json'
value_json_schema: 'config/metrics/objects_schemas/topology_schema.json'
......@@ -398,6 +398,8 @@
- 1
- - upload_checksum
- 1
- - users_update_open_issue_count
- 1
- - vulnerabilities_statistics_adjustment
- 1
- - vulnerability_exports_export
......
......@@ -39,7 +39,7 @@ at least 1 secondary in [hot standby](https://www.postgresql.org/docs/11/hot-sta
Load balancing also requires that the configured hosts **always** point to the
primary, even after a database failover. Furthermore, the additional hosts to
balance load among must **always** point to secondary databases. This means that
you should put a load balance in front of every database, and have GitLab connect
you should put a load balancer in front of every database, and have GitLab connect
to those load balancers.
For example, say you have a primary (`db1.gitlab.com`) and two secondaries,
......
......@@ -851,6 +851,10 @@ Input type: `CommitCreateInput`
### `Mutation.configureSast`
Configure SAST for a project by enabling SAST in a new or modified
`.gitlab-ci.yml` file in a new branch. The new branch and a URL to
create a Merge Request are a part of the response.
Input type: `ConfigureSastInput`
#### Arguments
......@@ -865,11 +869,36 @@ Input type: `ConfigureSastInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationconfiguresastbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. |
| <a id="mutationconfiguresastclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationconfiguresasterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationconfiguresaststatus"></a>`status` | [`String!`](#string) | Status of creating the commit for the supplied SAST CI configuration. |
| <a id="mutationconfiguresastsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. |
### `Mutation.configureSecretDetection`
Configure Secret Detection for a project by enabling Secret Detection
in a new or modified `.gitlab-ci.yml` file in a new branch. The new
branch and a URL to create a Merge Request are a part of the
response.
Input type: `ConfigureSecretDetectionInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationconfiguresecretdetectionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationconfiguresecretdetectionprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationconfiguresecretdetectionbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. |
| <a id="mutationconfiguresecretdetectionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationconfiguresecretdetectionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationconfiguresecretdetectionsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. |
### `Mutation.createAlertIssue`
Input type: `CreateAlertIssueInput`
......
---
stage: Growth
group: Product Intelligence
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
<!---
This documentation is auto generated by a script.
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
--->
<!-- vale gitlab.Spelling = NO -->
# Event Dictionary
This file is autogenerated, please do not edit it directly.
To generate these files from the GitLab repository, run:
```shell
bundle exec rake gitlab:snowplow:generate_event_dictionary
```
The Event Dictionary is based on the following event definition YAML files:
- [`config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/config/events)
- [`ee/config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/ee/config/events)
## Event definitions
### `epics promote`
| category | action | label | property | value |
|---|---|---|---|---|
| `epics` | `promote` | `` | `The string "issue_id"` | `ID of the issue` |
Issue promoted to epic
YAML definition: `/ee/config/events/epics_promote.yml`
Owner: `group::product planning`
Tiers: `premium`, `ultimate`
......@@ -6602,11 +6602,11 @@ Whether auto DevOps is enabled
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/settings/20210204124856_instance_auto_devops_enabled.yml)
Group: `group::product intelligence`
Group: `group::configure`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `ldap_enabled`
......@@ -7666,27 +7666,27 @@ Tiers:
### `redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_monthly`
Missing description
Number of projects using 5 min production app CI template in last 7 days.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184517_p_ci_templates_5_min_production_app_monthly.yml)
Group: ``
Group: `group::5-min-app`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_weekly`
Missing description
Number of projects using 5 min production app CI template in last 7 days.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184515_p_ci_templates_5_min_production_app_weekly.yml)
Group: ``
Group: `group::5-min-app`
Status: `data_available`
Tiers:
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ci_templates.p_ci_templates_auto_devops_build_monthly`
......@@ -7786,51 +7786,51 @@ Tiers:
### `redis_hll_counters.ci_templates.p_ci_templates_aws_cf_deploy_ec2_monthly`
Missing description
Count of projects using `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template in last 28 days.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184526_p_ci_templates_aws_cf_deploy_ec2_monthly.yml)
Group: ``
Group: `group::release`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ci_templates.p_ci_templates_aws_cf_deploy_ec2_weekly`
Missing description
Count of projects using `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template in last 7 days.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184524_p_ci_templates_aws_cf_deploy_ec2_weekly.yml)
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184524_p_ci_templates_aws_cf_deploy_ec2_weekly.yml)
Group: ``
Group: `group::release`
Status: `data_available`
Tiers:
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ci_templates.p_ci_templates_aws_deploy_ecs_monthly`
Missing description
Count of projects using `AWS/Deploy-ECS.gitlab-ci.yml` template in last 28 days.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184530_p_ci_templates_aws_deploy_ecs_monthly.yml)
Group: ``
Group: `group::release`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ci_templates.p_ci_templates_aws_deploy_ecs_weekly`
Missing description
Count of projects using `AWS/Deploy-ECS.gitlab-ci.yml` template in last 7 days.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184528_p_ci_templates_aws_deploy_ecs_weekly.yml)
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184528_p_ci_templates_aws_deploy_ecs_weekly.yml)
Group: ``
Group: `group::release`
Status: `data_available`
Tiers:
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.ci_templates.p_ci_templates_implicit_auto_devops_build_monthly`
......
......@@ -68,6 +68,19 @@ export const updateListQueries = {
},
};
// re-export some FOSS constants so that lint does not yell
// https://gitlab.com/gitlab-org/gitlab/-/issues/329164
export {
BoardType,
ListType,
inactiveId,
flashAnimationDuration,
ISSUABLE,
titleQueries,
subscriptionQueries,
SupportedFilters,
} from '~/boards/constants';
export default {
updateListQueries,
DRAGGABLE_TAG,
......
......@@ -39,7 +39,9 @@ export default {
const { data } = await this.$apollo.mutate({
mutation: this.featureSettings.mutation,
variables: {
fullPath: this.projectPath,
input: {
projectPath: this.projectPath,
},
},
});
const { errors, successPath } = data[this.featureSettings.type];
......
mutation configureSecretDetection($fullPath: ID!) {
configureSecretDetection(fullPath: $fullPath) {
mutation configureSecretDetection($input: ConfigureSecretDetectionInput!) {
configureSecretDetection(input: $input) {
successPath
errors
}
......
......@@ -19,6 +19,7 @@ export const initSecurityConfiguration = (el) => {
dependencyScanningHelpPath,
toggleAutofixSettingEndpoint,
gitlabCiHistoryPath,
projectPath,
} = el.dataset;
return new Vue({
......@@ -26,6 +27,9 @@ export const initSecurityConfiguration = (el) => {
components: {
SecurityConfigurationApp,
},
provide: {
projectPath,
},
render(createElement) {
return createElement(SecurityConfigurationApp, {
props: {
......
......@@ -8,7 +8,6 @@ module SubscriptionsHelper
setup_for_company: (current_user.setup_for_company == true).to_s,
full_name: current_user.name,
available_plans: subscription_available_plans.to_json,
ci_minutes_plans: ci_minutes_plans.to_json,
plan_id: params[:plan_id],
namespace_id: params[:namespace_id],
new_user: new_user?.to_s,
......@@ -44,26 +43,6 @@ module SubscriptionsHelper
plans_data.reject { |plan_data| plan_data[:deprecated] }
end
def ci_minutes_plans
return if ::Feature.disabled?(:new_route_ci_minutes_purchase, default_enabled: :yaml)
strong_memoize(:ci_minutes_plans) do
fields = %w[
name
code
active
free
price_per_month
price_per_year
features
about_page_href
hide_deprecated_card
]
Gitlab::SubscriptionPortal::Client.plan_data("CI_1000_MINUTES_PLAN", fields)[:plans]
end
end
def present_groups(groups)
groups.map do |namespace|
{
......
......@@ -26,8 +26,6 @@ class License < ApplicationRecord
group_activity_analytics
group_bulk_edit
group_webhooks
instance_level_devops_adoption
group_level_devops_adoption
issuable_default_templates
issue_weights
iterations
......@@ -156,9 +154,11 @@ class License < ApplicationRecord
evaluate_group_level_compliance_pipeline
group_ci_cd_analytics
group_level_compliance_dashboard
group_level_devops_adoption
incident_management
inline_codequality
insights
instance_level_devops_adoption
issuable_health_status
jira_vulnerabilities_integration
jira_issue_association_enforcement
......
......@@ -5,6 +5,7 @@
= render_ce 'projects/security/configuration/show'
- else
#js-security-configuration{ data: { **@configuration.to_html_data_attribute,
project_path: @project.full_path,
auto_fix_help_path: '/',
toggle_autofix_setting_endpoint: 'configuration/auto_fix',
container_scanning_help_path: help_page_path('user/application_security/container_scanning/index'),
......
---
title: Fix tier for instance and group DevOps Adoption
merge_request: 60032
author:
type: changed
......@@ -57,7 +57,7 @@ module Gitlab
}
GQL
response = execute_graphql_query({ query: query })[:data]
response = execute_graphql_query({ query: query }).dig(:data)
if response['errors'].blank?
eligible = response.dig('data', 'subscription', 'eoaStarterBronzeEligible')
......@@ -75,31 +75,6 @@ module Gitlab
end
end
def plan_data(plan_tags, fields)
query = <<~GQL
query($tags: [PlanTag!]) {
plans(planTags: $tags) {
deprecated
#{(fields - ['deprecated']).map { |field| "#{field}: #{field.to_s.camelize(:lower)}" }.join(" ")}
}
}
GQL
response = execute_graphql_query({ query: query, variables: { tags: plan_tags } })[:data]
if response['errors'].present?
track_error(query, response)
return error
end
{
success: true,
plans: response.dig('data', 'plans')
.reject { |plan| plan['deprecated'] }
}
end
def subscription_last_term(namespace_id)
return error('Must provide a namespace ID') unless namespace_id
......
......@@ -21,12 +21,28 @@ RSpec.describe 'Group navbar' do
insert_package_nav(_('Kubernetes'))
end
context 'when devops adoption analytics is available' do
before do
stub_licensed_features(group_level_devops_adoption: true)
insert_after_sub_nav_item(
_('Contribution'),
within: _('Analytics'),
new_sub_nav_item_name: _('DevOps Adoption')
)
visit group_path(group)
end
it_behaves_like 'verified navigation bar'
end
context 'when productivity analytics is available' do
before do
stub_licensed_features(productivity_analytics: true)
insert_after_sub_nav_item(
_('DevOps Adoption'),
_('Contribution'),
within: _('Analytics'),
new_sub_nav_item_name: _('Productivity')
)
......@@ -42,7 +58,7 @@ RSpec.describe 'Group navbar' do
stub_licensed_features(cycle_analytics_for_groups: true)
insert_after_sub_nav_item(
_('DevOps Adoption'),
_('Contribution'),
within: _('Analytics'),
new_sub_nav_item_name: _('Value Stream')
)
......
......@@ -3,17 +3,13 @@
require 'spec_helper'
RSpec.describe 'Subscriptions Content Security Policy' do
include SubscriptionPortalHelpers
subject { response_headers['Content-Security-Policy'] }
let_it_be(:default_csp_values) { "'self' https://some-cdn.test" }
let_it_be(:zuora_url) { 'https://*.zuora.com' }
let(:plan_tags) { 'CI_1000_MINUTES_PLAN' }
before do
stub_request(:get, /.*gitlab_plans.*/).to_return(status: 200, body: "{}")
stub_plan_data_request(plan_tags)
expect_next_instance_of(SubscriptionsController) do |controller|
expect(controller).to receive(:current_content_security_policy).and_return(csp)
......
......@@ -11,6 +11,10 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
resolve(described_class, args: args, ctx: context)
end
before do
stub_licensed_features(instance_level_devops_adoption: true, group_level_devops_adoption: true)
end
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:root_group_1) { create(:group, name: 'bbb') }
......@@ -28,11 +32,6 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
create(:devops_adoption_segment, namespace: indirect_subgroup)
end
before do
stub_licensed_features(instance_level_devops_adoption: true)
stub_licensed_features(group_level_devops_adoption: true)
end
subject(:resolved_segments) { resolve_segments(params, { current_user: current_user }) }
let(:params) { {} }
......
......@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe SubscriptionsHelper do
include SubscriptionPortalHelpers
using RSpec::Parameterized::TableSyntax
let_it_be(:free_plan) do
......@@ -25,15 +23,12 @@ RSpec.describe SubscriptionsHelper do
[free_plan, bronze_plan]
end
let(:plan_tags) { 'CI_1000_MINUTES_PLAN' }
before do
stub_feature_flags(hide_deprecated_billing_plans: false)
allow(helper).to receive(:params).and_return(plan_id: 'bronze_id', namespace_id: nil)
allow_next_instance_of(GitlabSubscriptions::FetchSubscriptionPlansService) do |instance|
allow(instance).to receive(:execute).and_return(raw_plan_data)
end
stub_plan_data_request(plan_tags)
end
describe '#subscription_data' do
......@@ -53,7 +48,6 @@ RSpec.describe SubscriptionsHelper do
it { is_expected.to include(setup_for_company: 'false') }
it { is_expected.to include(full_name: 'First Last') }
it { is_expected.to include(available_plans: '[{"id":"bronze_id","code":"bronze","price_per_year":48.0,"name":"Bronze Plan"}]') }
it { is_expected.to include(ci_minutes_plans: '[{"name":"1000 CI minutes pack","code":"ci_minutes","active":true,"deprecated":false,"free":null,"price_per_month":0.8333333333333334,"price_per_year":10,"features":null,"about_page_href":null,"hide_deprecated_card":false}]') }
it { is_expected.to include(plan_id: 'bronze_id') }
it { is_expected.to include(namespace_id: group.id.to_s) }
it { is_expected.to include(group_data: %Q{[{"id":#{group.id},"name":"My Namespace","users":2,"guests":1}]}) }
......@@ -111,14 +105,6 @@ RSpec.describe SubscriptionsHelper do
it { is_expected.to include(available_plans: '[]') }
end
end
context 'when ff new_route_ci_minutes_purchase is disabled' do
before do
stub_feature_flags(new_route_ci_minutes_purchase: false)
end
it { is_expected.not_to include(ci_minutes_plans: '[{"name":"1000 CI minutes pack","code":"ci_minutes","active":true,"deprecated":false,"free":null,"price_per_month":0.8333333333333334,"price_per_year":10,"features":null,"about_page_href":null,"hide_deprecated_card":false}]') }
end
end
describe '#plan_title' do
......
......@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Gitlab::SubscriptionPortal::Clients::Graphql do
include SubscriptionPortalHelpers
let(:client) { Gitlab::SubscriptionPortal::Client }
describe '#activate' do
......@@ -134,63 +132,6 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::Graphql do
end
end
describe '#plan_data' do
let(:plan_tags) { 'CI_1000_MINUTES_PLAN' }
subject(:plan_data) { client.plan_data(plan_tags, stubbed_plan_data_query_fields) }
context 'when the response contains errors' do
before do
expect(client).to receive(:execute_graphql_query).and_return(response)
end
let(:response) do
{
success: true,
data: {
'errors' => [{ 'message' => 'this will be ignored' }]
}
}
end
it 'logs an error and returns a failure' do
expect(Gitlab::ErrorTracking)
.to receive(:track_and_raise_for_dev_exception)
.with(
a_kind_of(Gitlab::SubscriptionPortal::Client::ResponseError),
query: include(*stubbed_plan_data_query_fields_camelized), response: response[:data])
expect(plan_data).to eq({ success: false })
end
end
context 'when the response does not contain errors' do
before do
allow(client).to receive(:execute_graphql_query).and_return({ data: Gitlab::Json.parse(stubbed_plan_data_response_body) })
end
it 'filters out the deprecated plans' do
expect(plan_data).to match({
success: true,
plans: contain_exactly(include('deprecated' => false))
})
end
context 'when plans is an empty array' do
before do
allow(client).to receive(:execute_graphql_query).and_return({
success: true,
data: { "data" => { "plans" => [] } }
})
end
it 'returns the correct response' do
expect(plan_data).to eq({ success: true, plans: [] })
end
end
end
end
describe '#subscription_last_term' do
let(:query) do
<<~GQL
......
......@@ -323,6 +323,10 @@ RSpec.describe GlobalPolicy do
end
context 'when feature is enabled and license include the feature' do
before do
stub_licensed_features(instance_level_devops_adoption: true)
end
it { is_expected.to be_allowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
context 'for non-admins' do
......
......@@ -1682,6 +1682,7 @@ RSpec.describe GroupPolicy do
end
before do
stub_licensed_features(group_level_devops_adoption: true)
allow(group).to receive(:feature_available?).with(:group_level_devops_adoption).and_return(false)
enable_admin_mode!(current_user) if current_user.admin?
end
......
......@@ -30,6 +30,8 @@ RSpec.describe 'DevopsAdoptionSegments' do
end
before do
stub_licensed_features(instance_level_devops_adoption: true, group_level_devops_adoption: true)
post_graphql(query, current_user: current_user)
end
......
......@@ -16,6 +16,7 @@ RSpec.describe Analytics::DevopsAdoption::Segments::BulkDeleteService do
before do
enable_admin_mode!(admin)
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'deletes the segments' do
......
......@@ -19,6 +19,10 @@ RSpec.describe Analytics::DevopsAdoption::Segments::BulkFindOrCreateService do
subject(:response) { described_class.new(params: params, current_user: current_user).execute }
before do
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'authorizes for manage_devops_adoption' do
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group)
......
......@@ -13,6 +13,10 @@ RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
subject(:response) { described_class.new(params: params, current_user: current_user).execute }
before do
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'persists the segment' do
expect(response).to be_success
expect(segment.namespace).to eq(group)
......
......@@ -11,6 +11,10 @@ RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do
subject(:response) { described_class.new(segment: segment, current_user: current_user).execute }
before do
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
it 'deletes the segment' do
expect(response).to be_success
expect(segment).not_to be_persisted
......
......@@ -13,7 +13,7 @@ RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
subject(:response) { described_class.new(params: params, current_user: current_user).execute }
before do
stub_licensed_features(group_level_devops_adoption: true)
stub_licensed_features(group_level_devops_adoption: true, instance_level_devops_adoption: true)
end
context 'when segment for given namespace already exists' do
......
......@@ -21,24 +21,6 @@ module SubscriptionPortalHelpers
)
end
def stub_plan_data_request(plan_tags)
stub_full_request("#{EE::SUBSCRIPTIONS_URL}/graphql", method: :post)
.with(
body: include(*plan_tags, *stubbed_plan_data_query_fields_camelized),
headers: {
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Admin-Email' => EE::SUBSCRIPTION_PORTAL_ADMIN_EMAIL,
'X-Admin-Token' => EE::SUBSCRIPTION_PORTAL_ADMIN_TOKEN
}
)
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: stubbed_plan_data_response_body
)
end
private
def stubbed_eoa_eligibility_response_body(eligible, free_upgrade_plan_id, assisted_upgrade_plan_id)
......@@ -52,55 +34,4 @@ module SubscriptionPortalHelpers
}
}.to_json
end
def stubbed_plan_data_query_fields
%w[
name
code
active
free
price_per_month
price_per_year
features
about_page_href
hide_deprecated_card
]
end
def stubbed_plan_data_query_fields_camelized
stubbed_plan_data_query_fields.map { |field| field.to_s.camelize(:lower) }
end
def stubbed_plan_data_response_body
{
"data": {
"plans": [
{
"name": "1000 CI minutes pack",
"code": "ci_minutes",
"active": true,
"deprecated": false,
"free": nil,
"price_per_month": 0.8333333333333334,
"price_per_year": 10,
"features": nil,
"about_page_href": nil,
"hide_deprecated_card": false
},
{
"name": "Deprecated 1000 CI minutes pack",
"code": "deprecated_ci_minutes",
"active": true,
"deprecated": true,
"free": nil,
"price_per_month": 1,
"price_per_year": 12,
"features": nil,
"about_page_href": nil,
"hide_deprecated_card": false
}
]
}
}.to_json
end
end
# frozen_string_literal: true
module Gitlab
module Tracking
module Docs
# Helper with functions to be used by HAML templates
module Helper
def auto_generated_comment
<<-MARKDOWN.strip_heredoc
---
stage: Growth
group: Product Intelligence
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
<!---
This documentation is auto generated by a script.
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
--->
<!-- vale gitlab.Spelling = NO -->
MARKDOWN
end
def render_description(object)
return 'Missing description' unless object.description.present?
object.description
end
def render_event_taxonomy(object)
headers = %w[category action label property value]
values = %i[category action label property_description value_description]
values = values.map { |key| backtick(object.attributes[key]) }
values = values.join(" | ")
[
"| #{headers.join(" | ")} |",
"#{'|---' * headers.size}|",
"| #{values} |"
].join("\n")
end
def md_link_to(anchor_text, url)
"[#{anchor_text}](#{url})"
end
def render_owner(object)
"Owner: #{backtick(object.product_group)}"
end
def render_tiers(object)
"Tiers: #{object.tiers.map(&method(:backtick)).join(', ')}"
end
def render_yaml_definition_path(object)
"YAML definition: #{backtick(object.yaml_path)}"
end
def backtick(string)
"`#{string}`"
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Tracking
module Docs
class Renderer
include Gitlab::Tracking::Docs::Helper
DICTIONARY_PATH = Rails.root.join('doc', 'development', 'snowplow')
TEMPLATE_PATH = Rails.root.join('lib', 'gitlab', 'tracking', 'docs', 'templates', 'default.md.haml')
def initialize(event_definitions)
@layout = Haml::Engine.new(File.read(TEMPLATE_PATH))
@event_definitions = event_definitions.sort
end
def contents
# Render and remove an extra trailing new line
@contents ||= @layout.render(self, event_definitions: @event_definitions).sub!(/\n(?=\Z)/, '')
end
def write
filename = DICTIONARY_PATH.join('dictionary.md').to_s
FileUtils.mkdir_p(DICTIONARY_PATH)
File.write(filename, contents)
filename
end
end
end
end
end
= auto_generated_comment
:plain
# Event Dictionary
This file is autogenerated, please do not edit it directly.
To generate these files from the GitLab repository, run:
```shell
bundle exec rake gitlab:snowplow:generate_event_dictionary
```
The Event Dictionary is based on the following event definition YAML files:
- [`config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/config/events)
- [`ee/config/events`](https://gitlab.com/gitlab-org/gitlab/-/tree/f9a404301ca22d038e7b9a9eb08d9c1bbd6c4d84/ee/config/events)
## Event definitions
\
- event_definitions.each do |_path, object|
= "### `#{object.category} #{object.action}`"
\
= render_event_taxonomy(object)
\
= render_description(object)
\
= render_yaml_definition_path(object)
\
= render_owner(object)
\
= render_tiers(object)
\
# frozen_string_literal: true
module Gitlab
module Tracking
InvalidEventError = Class.new(RuntimeError)
class EventDefinition
EVENT_SCHEMA_PATH = Rails.root.join('config', 'events', 'schema.json')
BASE_REPO_PATH = 'https://gitlab.com/gitlab-org/gitlab/-/blob/master'
SCHEMA = ::JSONSchemer.schema(Pathname.new(EVENT_SCHEMA_PATH))
attr_reader :path
attr_reader :attributes
class << self
def paths
@paths ||= [Rails.root.join('config', 'events', '*.yml'), Rails.root.join('ee', 'config', 'events', '*.yml')]
end
def definitions
paths.each_with_object({}) do |glob_path, definitions|
load_all_from_path!(definitions, glob_path)
end
end
private
def load_from_file(path)
definition = File.read(path)
definition = YAML.safe_load(definition)
definition.deep_symbolize_keys!
self.new(path, definition).tap(&:validate!)
rescue StandardError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Tracking::InvalidEventError.new(e.message))
end
def load_all_from_path!(definitions, glob_path)
Dir.glob(glob_path).each do |path|
definition = load_from_file(path)
definitions[definition.path] = definition
end
end
end
def initialize(path, opts = {})
@path = path
@attributes = opts
end
def to_h
attributes
end
alias_method :to_dictionary, :to_h
def yaml_path
path.delete_prefix(Rails.root.to_s)
end
def validate!
SCHEMA.validate(attributes.stringify_keys).each do |error|
error_message = <<~ERROR_MSG
Error type: #{error['type']}
Data: #{error['data']}
Path: #{error['data_pointer']}
Details: #{error['details']}
Definition file: #{path}
ERROR_MSG
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Tracking::InvalidEventError.new(error_message))
end
end
private
def method_missing(method, *args)
attributes[method] || super
end
end
end
end
......@@ -26,11 +26,11 @@ module Gitlab
def json_schema_path
return '' unless has_json_schema?
"#{BASE_REPO_PATH}/#{attributes[:object_json_schema]}"
"#{BASE_REPO_PATH}/#{attributes[:value_json_schema]}"
end
def has_json_schema?
attributes[:value_type] == 'object' && attributes[:object_json_schema].present?
attributes[:value_type] == 'object' && attributes[:value_json_schema].present?
end
def yaml_path
......
# frozen_string_literal: true
module Security
module CiConfiguration
class BaseBuildAction
def initialize(auto_devops_enabled, existing_gitlab_ci_content)
@auto_devops_enabled = auto_devops_enabled
@existing_gitlab_ci_content = existing_gitlab_ci_content || {}
end
def generate
action = @existing_gitlab_ci_content.present? ? 'update' : 'create'
update_existing_content!
{ action: action, file_path: '.gitlab-ci.yml', content: prepare_existing_content, default_values_overwritten: @default_values_overwritten }
end
private
def generate_includes
includes = @existing_gitlab_ci_content['include'] || []
includes = Array.wrap(includes)
includes << { 'template' => template }
includes.uniq
end
def prepare_existing_content
content = @existing_gitlab_ci_content.to_yaml
content = remove_document_delimiter(content)
content.prepend(comment)
end
def remove_document_delimiter(content)
content.gsub(/^---\n/, '')
end
def comment
<<~YAML
# You can override the included template(s) by including variable overrides
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
YAML
end
end
end
end
......@@ -2,25 +2,16 @@
module Security
module CiConfiguration
class SastBuildActions
class SastBuildAction < BaseBuildAction
SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, sobelow, spotbugs'
def initialize(auto_devops_enabled, params, existing_gitlab_ci_content)
@auto_devops_enabled = auto_devops_enabled
super(auto_devops_enabled, existing_gitlab_ci_content)
@variables = variables(params)
@existing_gitlab_ci_content = existing_gitlab_ci_content || {}
@default_sast_values = default_sast_values(params)
@default_values_overwritten = false
end
def generate
action = @existing_gitlab_ci_content.present? ? 'update' : 'create'
update_existing_content!
[{ action: action, file_path: '.gitlab-ci.yml', content: prepare_existing_content, default_values_overwritten: @default_values_overwritten }]
end
private
def variables(params)
......@@ -71,19 +62,12 @@ module Security
@existing_gitlab_ci_content['stages'] = set_stages
@existing_gitlab_ci_content['variables'] = set_variables(global_variables, @existing_gitlab_ci_content)
@existing_gitlab_ci_content['sast'] = set_sast_block
@existing_gitlab_ci_content['include'] = set_includes
@existing_gitlab_ci_content['include'] = generate_includes
@existing_gitlab_ci_content.select! { |k, v| v.present? }
@existing_gitlab_ci_content['sast'].select! { |k, v| v.present? }
end
def set_includes
includes = @existing_gitlab_ci_content['include'] || []
includes = includes.is_a?(Array) ? includes : [includes]
includes << { 'template' => template }
includes.uniq
end
def set_stages
existing_stages = @existing_gitlab_ci_content['stages'] || []
base_stages = @auto_devops_enabled ? auto_devops_stages : ['test']
......@@ -121,26 +105,6 @@ module Security
sast_content.select { |k, v| v.present? }
end
def prepare_existing_content
content = @existing_gitlab_ci_content.to_yaml
content = remove_document_delimeter(content)
content.prepend(sast_comment)
end
def remove_document_delimeter(content)
content.gsub(/^---\n/, '')
end
def sast_comment
<<~YAML
# You can override the included template(s) by including variable overrides
# See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
YAML
end
def template
return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled
......
# frozen_string_literal: true
module Security
module CiConfiguration
class SecretDetectionBuildAction < BaseBuildAction
private
def update_existing_content!
@existing_gitlab_ci_content['include'] = generate_includes
end
def template
return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled
'Security/Secret-Detection.gitlab-ci.yml'
end
end
end
end
# frozen_string_literal: true
namespace :gitlab do
namespace :snowplow do
desc 'GitLab | Snowplow | Generate event dictionary'
task generate_event_dictionary: :environment do
items = Gitlab::Tracking::EventDefinition.definitions
Gitlab::Tracking::Docs::Renderer.new(items).write
end
end
end
......@@ -8308,6 +8308,18 @@ msgstr ""
msgid "Configure Prometheus"
msgstr ""
msgid "Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings."
msgstr ""
msgid "Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist"
msgstr ""
msgid "Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings."
msgstr ""
msgid "Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist"
msgstr ""
msgid "Configure Tracing"
msgstr ""
......@@ -29095,12 +29107,6 @@ msgstr ""
msgid "Set %{epic_ref} as the parent epic."
msgstr ""
msgid "Set .gitlab-ci.yml to enable or configure SAST"
msgstr ""
msgid "Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings."
msgstr ""
msgid "Set a default description template to be used for new issues. %{link_start}What are description templates?%{link_end}"
msgstr ""
......
......@@ -3,118 +3,11 @@
require 'spec_helper'
RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
include GraphqlHelpers
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
let(:service) { ::Security::CiConfiguration::SastCreateService }
let_it_be(:service_result_json) do
{
status: "success",
success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?",
errors: nil
}
end
subject { resolve(described_class, args: { project_path: project.full_path, configuration: {} }, ctx: { current_user: user }) }
let_it_be(:service_error_result_json) do
{
status: "error",
success_path: nil,
errors: %w(error1 error2)
}
end
let(:context) do
GraphQL::Query::Context.new(
query: OpenStruct.new(schema: nil),
values: { current_user: user },
object: nil
)
end
specify { expect(described_class).to require_graphql_authorizations(:push_code) }
describe '#resolve' do
subject { mutation.resolve(project_path: project.full_path, configuration: {}) }
let(:result) { subject }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when user does not have enough permissions' do
before do
project.add_guest(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user is a maintainer of a different project' do
before do
create(:project_empty_repo).add_maintainer(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user does not have permission to create a new branch' do
before_all do
project.add_developer(user)
end
let(:error_message) { 'You are not allowed to create protected branches on this project.' }
it 'returns an array of errors' do
allow_next_instance_of(::Files::MultiService) do |multi_service|
allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}"))
end
expect(result).to match(
status: :error,
success_path: nil,
errors: match_array([error_message])
)
end
end
context 'when the user can create a merge request' do
before_all do
project.add_developer(user)
end
context 'when service successfully generates a path to create a new merge request' do
it 'returns a success path' do
allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
allow(service).to receive(:execute).and_return(service_result_json)
end
expect(result).to match(
status: 'success',
success_path: service_result_json[:success_path],
errors: []
)
end
end
context 'when service can not generate any path to create a new merge request' do
it 'returns an array of errors' do
allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
allow(service).to receive(:execute).and_return(service_error_result_json)
end
expect(result).to match(
status: 'error',
success_path: be_nil,
errors: match_array(service_error_result_json[:errors])
)
end
end
end
end
include_examples 'graphql mutations security ci configuration'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Security::CiConfiguration::ConfigureSecretDetection do
include GraphqlHelpers
let(:service) { ::Security::CiConfiguration::SecretDetectionCreateService }
subject { resolve(described_class, args: { project_path: project.full_path }, ctx: { current_user: user }) }
include_examples 'graphql mutations security ci configuration'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::Docs::Helper do
let_it_be(:klass) do
Class.new do
include Gitlab::Tracking::Docs::Helper
end
end
describe '#auto_generated_comment' do
it 'renders information about missing description' do
expect(klass.new.auto_generated_comment).to match /This documentation is auto generated by a script/
end
end
describe '#render_description' do
context 'description is empty' do
it 'renders information about missing description' do
object = double(description: '')
expect(klass.new.render_description(object)).to eq('Missing description')
end
end
context 'description is present' do
it 'render description' do
object = double(description: 'some description')
expect(klass.new.render_description(object)).to eq('some description')
end
end
end
describe '#render_event_taxonomy' do
it 'render table with event taxonomy' do
attributes = {
category: 'epics',
action: 'promote',
label: nil,
property_description: 'String with issue id',
value_description: 'Integer issue id'
}
object = double(attributes: attributes)
event_taxonomy = <<~MD.chomp
| category | action | label | property | value |
|---|---|---|---|---|
| `epics` | `promote` | `` | `String with issue id` | `Integer issue id` |
MD
expect(klass.new.render_event_taxonomy(object)).to eq(event_taxonomy)
end
end
describe '#md_link_to' do
it 'render link in md format' do
expect(klass.new.md_link_to('zelda', 'link')).to eq('[zelda](link)')
end
end
describe '#render_owner' do
it 'render information about group owning event' do
object = double(product_group: "group::product intelligence")
expect(klass.new.render_owner(object)).to eq("Owner: `group::product intelligence`")
end
end
describe '#render_tiers' do
it 'render information about tiers' do
object = double(tiers: %w[bronze silver gold])
expect(klass.new.render_tiers(object)).to eq("Tiers: `bronze`, `silver`, `gold`")
end
end
describe '#render_yaml_definition_path' do
it 'render relative location of yaml definition' do
object = double(yaml_path: 'config/events/button_click.yaml')
expect(klass.new.render_yaml_definition_path(object)).to eq("YAML definition: `config/events/button_click.yaml`")
end
end
describe '#backtick' do
it 'wraps string in backticks chars' do
expect(klass.new.backtick('test')).to eql("`test`")
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::Docs::Renderer do
describe 'contents' do
let(:dictionary_path) { described_class::DICTIONARY_PATH }
let(:items) { Gitlab::Tracking::EventDefinition.definitions.first(10).to_h }
it 'generates dictionary for given items' do
generated_dictionary = described_class.new(items).contents
table_of_contents_items = items.values.map { |item| "#{item.category} #{item.action}"}
generated_dictionary_keys = RDoc::Markdown
.parse(generated_dictionary)
.table_of_contents
.select { |metric_doc| metric_doc.level == 3 }
.map { |item| item.text.match(%r{<code>(.*)</code>})&.captures&.first }
expect(generated_dictionary_keys).to match_array(table_of_contents_items)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::EventDefinition do
let(:attributes) do
{
description: 'Created issues',
category: 'issues',
action: 'create',
label_description: 'API',
property_description: 'The string "issue_id"',
value_description: 'ID of the issue',
extra_properties: { confidential: false },
product_category: 'collection',
product_stage: 'growth',
product_section: 'dev',
product_group: 'group::product analytics',
distribution: %w(ee ce),
tier: %w(free premium ultimate)
}
end
let(:path) { File.join('events', 'issues_create.yml') }
let(:definition) { described_class.new(path, attributes) }
let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
def write_metric(metric, path, content)
path = File.join(metric, path)
dir = File.dirname(path)
FileUtils.mkdir_p(dir)
File.write(path, content)
end
it 'has all definitions valid' do
expect { described_class.definitions }.not_to raise_error(Gitlab::Tracking::InvalidEventError)
end
describe '#validate' do
using RSpec::Parameterized::TableSyntax
where(:attribute, :value) do
:description | 1
:category | nil
:action | nil
:label_description | 1
:property_description | 1
:value_description | 1
:extra_properties | 'smth'
:product_category | 1
:product_stage | 1
:product_section | nil
:product_group | nil
:distributions | %[be eb]
:tiers | %[pro]
end
with_them do
before do
attributes[attribute] = value
end
it 'raise exception' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Tracking::InvalidEventError))
described_class.new(path, attributes).validate!
end
end
end
describe '.definitions' do
let(:metric1) { Dir.mktmpdir('metric1') }
let(:metric2) { Dir.mktmpdir('metric2') }
before do
allow(described_class).to receive(:paths).and_return(
[
File.join(metric1, '**', '*.yml'),
File.join(metric2, '**', '*.yml')
]
)
end
subject { described_class.definitions }
it 'has empty list when there are no definition files' do
is_expected.to be_empty
end
it 'has one metric when there is one file' do
write_metric(metric1, path, yaml_content)
is_expected.to be_one
end
after do
FileUtils.rm_rf(metric1)
FileUtils.rm_rf(metric2)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do
subject(:result) { described_class.new(auto_devops_enabled, gitlab_ci_content).generate }
let(:params) { {} }
context 'with existing .gitlab-ci.yml' do
let(:auto_devops_enabled) { false }
context 'secret_detection has not been included' do
let(:expected_yml) do
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
- security
variables:
RANDOM: make sure this persists
include:
- template: existing.yml
- template: Security/Secret-Detection.gitlab-ci.yml
CI_YML
end
context 'template includes are an array' do
let(:gitlab_ci_content) do
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists" },
"include" => [{ "template" => "existing.yml" }] }
end
it 'generates the correct YML' do
expect(result[:action]).to eq('update')
expect(result[:content]).to eq(expected_yml)
end
end
context 'template include is not an array' do
let(:gitlab_ci_content) do
{ "stages" => %w(test security),
"variables" => { "RANDOM" => "make sure this persists" },
"include" => { "template" => "existing.yml" } }
end
it 'generates the correct YML' do
expect(result[:action]).to eq('update')
expect(result[:content]).to eq(expected_yml)
end
end
end
context 'secret_detection has been included' do
let(:expected_yml) do
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
stages:
- test
variables:
RANDOM: make sure this persists
include:
- template: Security/Secret-Detection.gitlab-ci.yml
CI_YML
end
context 'secret_detection template include are an array' do
let(:gitlab_ci_content) do
{ "stages" => %w(test),
"variables" => { "RANDOM" => "make sure this persists" },
"include" => [{ "template" => "Security/Secret-Detection.gitlab-ci.yml" }] }
end
it 'generates the correct YML' do
expect(result[:action]).to eq('update')
expect(result[:content]).to eq(expected_yml)
end
end
context 'secret_detection template include is not an array' do
let(:gitlab_ci_content) do
{ "stages" => %w(test),
"variables" => { "RANDOM" => "make sure this persists" },
"include" => { "template" => "Security/Secret-Detection.gitlab-ci.yml" } }
end
it 'generates the correct YML' do
expect(result[:action]).to eq('update')
expect(result[:content]).to eq(expected_yml)
end
end
end
end
context 'with no .gitlab-ci.yml' do
let(:gitlab_ci_content) { nil }
context 'autodevops disabled' do
let(:auto_devops_enabled) { false }
let(:expected_yml) do
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
include:
- template: Security/Secret-Detection.gitlab-ci.yml
CI_YML
end
it 'generates the correct YML' do
expect(result[:action]).to eq('create')
expect(result[:content]).to eq(expected_yml)
end
end
context 'with autodevops enabled' do
let(:auto_devops_enabled) { true }
let(:expected_yml) do
<<-CI_YML.strip_heredoc
# You can override the included template(s) by including variable overrides
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
include:
- template: Auto-DevOps.gitlab-ci.yml
CI_YML
end
before do
allow_next_instance_of(described_class) do |secret_detection_build_actions|
allow(secret_detection_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages)
end
end
it 'generates the correct YML' do
expect(result[:action]).to eq('create')
expect(result[:content]).to eq(expected_yml)
end
end
end
# stubbing this method allows this spec file to use fast_spec_helper
def fast_auto_devops_stages
auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') )
auto_devops_template['stages']
end
end
......@@ -293,21 +293,7 @@ RSpec.describe Milestone do
end
end
context 'when `optimized_timebox_queries` feature flag is enabled' do
before do
stub_feature_flags(optimized_timebox_queries: true)
end
it_behaves_like '#for_projects_and_groups'
end
context 'when `optimized_timebox_queries` feature flag is disabled' do
before do
stub_feature_flags(optimized_timebox_queries: false)
end
it_behaves_like '#for_projects_and_groups'
end
it_behaves_like '#for_projects_and_groups'
describe '.upcoming_ids' do
let(:group_1) { create(:group) }
......
......@@ -4220,16 +4220,45 @@ RSpec.describe User do
end
describe '#invalidate_issue_cache_counts' do
let(:user) { build_stubbed(:user) }
let_it_be(:user) { create(:user) }
it 'invalidates cache for issue counter' do
cache_mock = double
subject do
user.invalidate_issue_cache_counts
user.save!
end
expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_issues_count'])
shared_examples 'invalidates the cached value' do
it 'invalidates cache for issue counter' do
expect(Rails.cache).to receive(:delete).with(['users', user.id, 'assigned_open_issues_count'])
allow(Rails).to receive(:cache).and_return(cache_mock)
subject
end
end
user.invalidate_issue_cache_counts
it_behaves_like 'invalidates the cached value'
context 'if feature flag assigned_open_issues_cache is enabled' do
it 'calls the recalculate worker' do
expect(Users::UpdateOpenIssueCountWorker).to receive(:perform_async).with(user.id)
subject
end
it_behaves_like 'invalidates the cached value'
end
context 'if feature flag assigned_open_issues_cache is disabled' do
before do
stub_feature_flags(assigned_open_issues_cache: false)
end
it 'does not call the recalculate worker' do
expect(Users::UpdateOpenIssueCountWorker).not_to receive(:perform_async).with(user.id)
subject
end
it_behaves_like 'invalidates the cached value'
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'ConfigureSecretDetection' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :test_repo) }
let(:variables) { { project_path: project.full_path } }
let(:mutation) { graphql_mutation(:configure_secret_detection, variables) }
let(:mutation_response) { graphql_mutation_response(:configureSecretDetection) }
context 'when authorized' do
let_it_be(:user) { project.owner }
it 'creates a branch with secret detection configured' do
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(mutation_response['branch']).not_to be_empty
expect(mutation_response['successPath']).not_to be_empty
end
end
end
......@@ -3,67 +3,24 @@
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SastCreateService, :snowplow do
describe '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:params) { {} }
subject(:result) { described_class.new(project, user, params).execute }
subject(:result) { described_class.new(project, user, params).execute }
let(:branch_name) { 'set-sast-config-1' }
context 'user does not belong to project' do
it 'returns an error status' do
expect(result[:status]).to eq(:error)
expect(result[:success_path]).to be_nil
end
it 'does not track a snowplow event' do
subject
expect_no_snowplow_event
end
end
context 'user belongs to project' do
before do
project.add_developer(user)
end
it 'does track the snowplow event' do
subject
expect_snowplow_event(
category: 'Security::CiConfiguration::SastCreateService',
action: 'create',
label: 'false'
)
end
it 'raises exception if the user does not have permission to create a new branch' do
allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
end
context 'with no parameters' do
it 'returns the path to create a new merge request' do
expect(result[:status]).to eq(:success)
expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
end
end
context 'with parameters' do
let(:params) do
{ 'stage' => 'security',
'SEARCH_MAX_DEPTH' => 1,
'SECURE_ANALYZERS_PREFIX' => 'new_registry',
'SAST_EXCLUDED_PATHS' => 'spec,docs' }
end
let(:non_empty_params) do
{ 'stage' => 'security',
'SEARCH_MAX_DEPTH' => 1,
'SECURE_ANALYZERS_PREFIX' => 'new_registry',
'SAST_EXCLUDED_PATHS' => 'spec,docs' }
end
it 'returns the path to create a new merge request' do
expect(result[:status]).to eq(:success)
expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
end
end
end
let(:snowplow_event) do
{
category: 'Security::CiConfiguration::SastCreateService',
action: 'create',
label: 'false'
}
end
include_examples 'services security ci configuration create service'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SecretDetectionCreateService, :snowplow do
subject(:result) { described_class.new(project, user).execute }
let(:branch_name) { 'set-secret-detection-config-1' }
let(:snowplow_event) do
{
category: 'Security::CiConfiguration::SecretDetectionCreateService',
action: 'create',
label: ''
}
end
include_examples 'services security ci configuration create service', true
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::UpdateAssignedOpenIssueCountService do
let_it_be(:user) { create(:user) }
describe '#initialize' do
context 'incorrect arguments provided' do
it 'raises an error if there are no target user' do
expect { described_class.new(target_user: nil) }.to raise_error(ArgumentError, /Please provide a target user/)
expect { described_class.new(target_user: "nonsense") }.to raise_error(ArgumentError, /Please provide a target user/)
end
end
context 'when correct arguments provided' do
it 'is successful' do
expect { described_class.new(target_user: user) }.not_to raise_error
end
end
end
describe "#execute", :clean_gitlab_redis_cache do
let(:fake_update_service) { double }
let(:fake_issue_count_service) { double }
let(:provided_value) { nil }
subject { described_class.new(target_user: user).execute }
context 'successful' do
it 'returns a success response' do
expect(subject).to be_success
end
it 'writes the cache with the new value' do
expect(Rails.cache).to receive(:write).with(['users', user.id, 'assigned_open_issues_count'], 0, expires_in: User::COUNT_CACHE_VALIDITY_PERIOD)
subject
end
it 'calls the issues finder to get the latest value' do
expect(IssuesFinder).to receive(:new).with(user, assignee_id: user.id, state: 'opened', non_archived: true).and_return(fake_issue_count_service)
expect(fake_issue_count_service).to receive(:execute)
subject
end
end
end
end
......@@ -124,8 +124,7 @@ RSpec.shared_context 'group navbar structure' do
{
nav_item: _('Analytics'),
nav_sub_items: [
_('Contribution'),
_('DevOps Adoption')
_('Contribution')
]
}
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples_for 'graphql mutations security ci configuration' do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
let(:branch) do
"set-secret-config"
end
let(:success_path) do
"http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?"
end
let(:service_response) do
ServiceResponse.success(payload: { branch: branch, success_path: success_path })
end
let(:error) { "An error occured!" }
let(:service_error_response) do
ServiceResponse.error(message: error)
end
specify { expect(described_class).to require_graphql_authorizations(:push_code) }
describe '#resolve' do
let(:result) { subject }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when user does not have enough permissions' do
before do
project.add_guest(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user is a maintainer of a different project' do
before do
create(:project_empty_repo).add_maintainer(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user does not have permission to create a new branch' do
let(:error_message) { 'You are not allowed to create protected branches on this project.' }
before do
project.add_developer(user)
allow_next_instance_of(::Files::MultiService) do |multi_service|
allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}"))
end
end
it 'returns an array of errors' do
expect(result).to match(
branch: be_nil,
success_path: be_nil,
errors: match_array([error_message])
)
end
end
context 'when the user can create a merge request' do
before do
project.add_developer(user)
end
context 'when service successfully generates a path to create a new merge request' do
before do
allow_next_instance_of(service) do |service|
allow(service).to receive(:execute).and_return(service_response)
end
end
it 'returns a success path' do
expect(result).to match(
branch: branch,
success_path: success_path,
errors: []
)
end
end
context 'when service can not generate any path to create a new merge request' do
before do
allow_next_instance_of(service) do |service|
allow(service).to receive(:execute).and_return(service_error_response)
end
end
it 'returns an array of errors' do
expect(result).to match(
branch: be_nil,
success_path: be_nil,
errors: match_array([error])
)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples_for 'services security ci configuration create service' do |skip_w_params|
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
describe '#execute' do
let(:params) { {} }
context 'user does not belong to project' do
it 'returns an error status' do
expect(result.status).to eq(:error)
expect(result.payload[:success_path]).to be_nil
end
it 'does not track a snowplow event' do
subject
expect_no_snowplow_event
end
end
context 'user belongs to project' do
before do
project.add_developer(user)
end
it 'does track the snowplow event' do
subject
expect_snowplow_event(**snowplow_event)
end
it 'raises exception if the user does not have permission to create a new branch' do
allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
end
context 'when exception is raised' do
let_it_be(:project) { create(:project, :repository) }
before do
allow(project.repository).to receive(:add_branch).and_raise(StandardError, "The unexpected happened!")
end
context 'when branch was created' do
before do
allow(project.repository).to receive(:branch_exists?).and_return(true)
end
it 'tries to rm branch' do
expect(project.repository).to receive(:rm_branch).with(user, branch_name)
expect { subject }.to raise_error(StandardError)
end
end
context 'when branch was not created' do
before do
allow(project.repository).to receive(:branch_exists?).and_return(false)
end
it 'does not try to rm branch' do
expect(project.repository).not_to receive(:rm_branch)
expect { subject }.to raise_error(StandardError)
end
end
end
context 'with no parameters' do
it 'returns the path to create a new merge request' do
expect(result.status).to eq(:success)
expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
end
end
unless skip_w_params
context 'with parameters' do
let(:params) { non_empty_params }
it 'returns the path to create a new merge request' do
expect(result.status).to eq(:success)
expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::UpdateOpenIssueCountWorker do
let_it_be(:first_user) { create(:user) }
let_it_be(:second_user) { create(:user) }
describe '#perform' do
let(:target_user_ids) { [first_user.id, second_user.id] }
subject { described_class.new.perform(target_user_ids) }
context 'when arguments are missing' do
context 'when target_user_ids are missing' do
context 'when nil' do
let(:target_user_ids) { nil }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, /No target user ID provided/)
end
end
context 'when empty array' do
let(:target_user_ids) { [] }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, /No target user ID provided/)
end
end
context 'when not an ID' do
let(:target_user_ids) { "nonsense" }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, /No valid target user ID provided/)
end
end
end
end
context 'when successful' do
let(:job_args) { [target_user_ids] }
let(:fake_service1) { double }
let(:fake_service2) { double }
it 'calls the user update service' do
expect(Users::UpdateAssignedOpenIssueCountService).to receive(:new).with(target_user: first_user).and_return(fake_service1)
expect(Users::UpdateAssignedOpenIssueCountService).to receive(:new).with(target_user: second_user).and_return(fake_service2)
expect(fake_service1).to receive(:execute)
expect(fake_service2).to receive(:execute)
subject
end
it_behaves_like 'an idempotent worker' do
it 'recalculates' do
subject
expect(first_user.assigned_open_issues_count).to eq(0)
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment