Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue16
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js4
-rw-r--r--app/assets/javascripts/security_configuration/graphql/current_license.query.graphql6
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss3
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss3
-rw-r--r--app/controllers/projects/service_ping_controller.rb1
-rw-r--r--app/models/concerns/integrations/loggable.rb30
-rw-r--r--app/models/concerns/project_services_loggable.rb28
-rw-r--r--app/models/integration.rb2
-rw-r--r--app/policies/issue_policy.rb11
-rw-r--r--app/serializers/issue_sidebar_basic_entity.rb2
-rw-r--r--app/services/concerns/group_linkable.rb38
-rw-r--r--app/services/git/base_hooks_service.rb2
-rw-r--r--app/services/groups/group_links/create_service.rb51
-rw-r--r--app/services/jira/requests/base.rb2
-rw-r--r--app/services/jira_connect/sync_service.rb2
-rw-r--r--app/services/projects/group_links/create_service.rb35
-rw-r--r--config/feature_flags/development/ci_expand_environment_name_and_url.yml2
-rw-r--r--config/metrics/counts_28d/20220428154012_live_preview.yml26
-rw-r--r--data/removals/15_0/15-0-removal-testcoveragesetting.yml14
-rw-r--r--db/migrate/20220420173247_add_group_inheritance_type_to_pe_authorizable.rb14
-rw-r--r--db/schema_migrations/202204201732471
-rw-r--r--db/structure.sql4
-rw-r--r--doc/update/removals.md15
-rw-r--r--doc/user/infrastructure/iac/terraform_state.md4
-rw-r--r--lib/api/groups.rb7
-rw-r--r--lib/api/projects.rb6
-rw-r--r--lib/bulk_imports/projects/stage.rb8
-rw-r--r--lib/gitlab/integrations_logger.rb (renamed from lib/gitlab/project_service_logger.rb)2
-rw-r--r--lib/gitlab/usage_data_counters/editor_unique_counter.rb5
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml5
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/controllers/groups/shared_projects_controller_spec.rb3
-rw-r--r--spec/controllers/projects/service_ping_controller_spec.rb12
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js47
-rw-r--r--spec/frontend/security_configuration/mock_data.js9
-rw-r--r--spec/lib/bulk_imports/projects/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb3
-rw-r--r--spec/models/integration_spec.rb10
-rw-r--r--spec/policies/issue_policy_spec.rb27
-rw-r--r--spec/serializers/issue_sidebar_basic_entity_spec.rb32
-rw-r--r--spec/services/groups/group_links/create_service_spec.rb210
-rw-r--r--spec/services/issues/set_crm_contacts_service_spec.rb16
-rw-r--r--spec/services/jira_connect/sync_service_spec.rb2
-rw-r--r--spec/services/projects/group_links/create_service_spec.rb121
-rw-r--r--spec/support/shared_examples/services/jira/requests/base_shared_examples.rb4
47 files changed, 610 insertions, 259 deletions
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index ff0e0ed0ab2..866148596e3 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -4,9 +4,10 @@ import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
+import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
-import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
+import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, LICENSE_ULTIMATE } from './constants';
import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
import UpgradeBanner from './upgrade_banner.vue';
@@ -50,6 +51,14 @@ export default {
TrainingProviderList,
},
inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
+ apollo: {
+ currentLicensePlan: {
+ query: currentLicenseQuery,
+ update({ currentLicense }) {
+ return currentLicense?.plan;
+ },
+ },
+ },
props: {
augmentedSecurityFeatures: {
type: Array,
@@ -89,6 +98,7 @@ export default {
return {
autoDevopsEnabledAlertDismissedProjects: [],
errorMessage: '',
+ currentLicensePlan: '',
};
},
computed: {
@@ -109,6 +119,9 @@ export default {
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectFullPath)
);
},
+ shouldShowVulnerabilityManagementTab() {
+ return this.currentLicensePlan === LICENSE_ULTIMATE;
+ },
},
methods: {
dismissAutoDevopsEnabledAlert() {
@@ -250,6 +263,7 @@ export default {
</section-layout>
</gl-tab>
<gl-tab
+ v-if="shouldShowVulnerabilityManagementTab"
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
query-param-value="vulnerability-management"
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index 6db28ef0fad..2fa14d4fc53 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -310,3 +310,7 @@ export const TEMP_PROVIDER_URLS = {
Kontra: 'https://application.security/',
[__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
};
+
+export const LICENSE_ULTIMATE = 'ultimate';
+export const LICENSE_FREE = 'free';
+export const LICENSE_PREMIUM = 'premium';
diff --git a/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql b/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql
new file mode 100644
index 00000000000..9ab4f4d4347
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql
@@ -0,0 +1,6 @@
+query getCurrentLicensePlan {
+ currentLicense {
+ id
+ plan
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f8ac3adebb5..a960f90ca44 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -64,6 +64,10 @@
border-radius: $border-radius-default;
}
+ .canary-badge {
+ margin-left: -8px;
+ }
+
.project-item-select {
right: auto;
left: 0;
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index d5364e018a0..9f93f80aa61 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -807,6 +807,9 @@ input {
margin: 4px 2px 4px -12px;
border-radius: 4px;
}
+.navbar-gitlab .header-content .title .canary-badge {
+ margin-left: -8px;
+}
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
margin: 0 2px;
}
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 989e63d32c4..999f1772118 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -792,6 +792,9 @@ input {
margin: 4px 2px 4px -12px;
border-radius: 4px;
}
+.navbar-gitlab .header-content .title .canary-badge {
+ margin-left: -8px;
+}
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
margin: 0 2px;
}
diff --git a/app/controllers/projects/service_ping_controller.rb b/app/controllers/projects/service_ping_controller.rb
index 368da8d1ef2..d8e3990a244 100644
--- a/app/controllers/projects/service_ping_controller.rb
+++ b/app/controllers/projects/service_ping_controller.rb
@@ -17,6 +17,7 @@ class Projects::ServicePingController < Projects::ApplicationController
return render_404 unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
Gitlab::UsageDataCounters::WebIdeCounter.increment_previews_success_count
+ Gitlab::UsageDataCounters::EditorUniqueCounter.track_live_preview_edit_action(author: current_user)
head(200)
end
diff --git a/app/models/concerns/integrations/loggable.rb b/app/models/concerns/integrations/loggable.rb
new file mode 100644
index 00000000000..6d9c04b49cd
--- /dev/null
+++ b/app/models/concerns/integrations/loggable.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Integrations
+ module Loggable
+ def log_info(message, params = {})
+ message = build_message(message, params)
+
+ logger.info(message)
+ end
+
+ def log_error(message, params = {})
+ message = build_message(message, params)
+
+ logger.error(message)
+ end
+
+ def build_message(message, params = {})
+ {
+ integration_class: self.class.name,
+ project_id: project&.id,
+ project_path: project&.full_path,
+ message: message
+ }.merge(params)
+ end
+
+ def logger
+ Gitlab::IntegrationsLogger
+ end
+ end
+end
diff --git a/app/models/concerns/project_services_loggable.rb b/app/models/concerns/project_services_loggable.rb
deleted file mode 100644
index e5385435138..00000000000
--- a/app/models/concerns/project_services_loggable.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module ProjectServicesLoggable
- def log_info(message, params = {})
- message = build_message(message, params)
-
- logger.info(message)
- end
-
- def log_error(message, params = {})
- message = build_message(message, params)
-
- logger.error(message)
- end
-
- def build_message(message, params = {})
- {
- service_class: self.class.name,
- project_id: project&.id,
- project_path: project&.full_path,
- message: message
- }.merge(params)
- end
-
- def logger
- Gitlab::ProjectServiceLogger
- end
-end
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 37447890c27..13ef37e0157 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -5,7 +5,7 @@
class Integration < ApplicationRecord
include Sortable
include Importable
- include ProjectServicesLoggable
+ include Integrations::Loggable
include Integrations::HasDataFields
include Integrations::ResetSecretFields
include FromUnion
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index a667c843bc6..a341d1ef661 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -12,8 +12,11 @@ class IssuePolicy < IssuablePolicy
@user && IssueCollection.new([@subject]).visible_to(@user).any?
end
- desc "User can read contacts belonging to the issue group"
- condition(:can_read_crm_contacts, scope: :subject) { @user.can?(:read_crm_contact, @subject.project.root_ancestor) }
+ desc "Project belongs to a group, crm is enabled and user can read contacts in the root group"
+ condition(:can_read_crm_contacts, scope: :subject) do
+ subject.project.group&.crm_enabled? &&
+ @user.can?(:read_crm_contact, @subject.project.root_ancestor)
+ end
desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? }
@@ -81,6 +84,10 @@ class IssuePolicy < IssuablePolicy
enable :set_confidentiality
end
+ rule { can_read_crm_contacts }.policy do
+ enable :read_crm_contacts
+ end
+
rule { can?(:set_issue_metadata) & can_read_crm_contacts }.policy do
enable :set_issue_crm_contacts
end
diff --git a/app/serializers/issue_sidebar_basic_entity.rb b/app/serializers/issue_sidebar_basic_entity.rb
index 7222b5df425..2450c6a4d85 100644
--- a/app/serializers/issue_sidebar_basic_entity.rb
+++ b/app/serializers/issue_sidebar_basic_entity.rb
@@ -12,7 +12,7 @@ class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
end
expose :show_crm_contacts do |issuable|
- current_user&.can?(:read_crm_contact, issuable.project.root_ancestor) &&
+ current_user&.can?(:read_crm_contacts, issuable) &&
CustomerRelations::Contact.exists_for_group?(issuable.project.root_ancestor)
end
end
diff --git a/app/services/concerns/group_linkable.rb b/app/services/concerns/group_linkable.rb
new file mode 100644
index 00000000000..3e2e9cfd5eb
--- /dev/null
+++ b/app/services/concerns/group_linkable.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module GroupLinkable
+ extend ActiveSupport::Concern
+
+ def execute
+ return error('Not Found', 404) unless valid_to_create?
+
+ build_link
+
+ if link.save
+ after_successful_save
+ success(link: link)
+ else
+ error(link.errors.full_messages.to_sentence, 409)
+ end
+ end
+
+ private
+
+ attr_reader :shared_with_group, :link
+
+ def sharing_allowed?
+ sharing_outside_hierarchy_allowed? || within_hierarchy?
+ end
+
+ def sharing_outside_hierarchy_allowed?
+ !root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
+ end
+
+ def within_hierarchy?
+ root_ancestor.self_and_descendants_ids.include?(shared_with_group.id)
+ end
+
+ def after_successful_save
+ setup_authorizations
+ end
+end
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index 63f3f73905a..269637805ad 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -172,7 +172,7 @@ module Git
else
# This service runs in Sidekiq, so this shouldn't ever be
# called, but this is included just in case.
- Gitlab::ProjectServiceLogger
+ Gitlab::IntegrationsLogger
end
end
end
diff --git a/app/services/groups/group_links/create_service.rb b/app/services/groups/group_links/create_service.rb
index 8c3ba0a63f2..56ddf3ec0b4 100644
--- a/app/services/groups/group_links/create_service.rb
+++ b/app/services/groups/group_links/create_service.rb
@@ -3,50 +3,35 @@
module Groups
module GroupLinks
class CreateService < Groups::BaseService
- def initialize(shared_group, shared_with_group, user, params)
- @shared_group = shared_group
- super(shared_with_group, user, params)
- end
-
- def execute
- unless shared_with_group && shared_group &&
- can?(current_user, :admin_group_member, shared_group) &&
- can?(current_user, :read_group, shared_with_group) &&
- sharing_allowed?
- return error('Not Found', 404)
- end
+ include GroupLinkable
- link = GroupGroupLink.new(
- shared_group: shared_group,
- shared_with_group: shared_with_group,
- group_access: params[:shared_group_access],
- expires_at: params[:expires_at]
- )
+ def initialize(group, shared_with_group, user, params)
+ @shared_with_group = shared_with_group
- if link.save
- shared_with_group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
- success(link: link)
- else
- error(link.errors.full_messages.to_sentence, 409)
- end
+ super(group, user, params)
end
private
- attr_reader :shared_group
+ delegate :root_ancestor, to: :group
- alias_method :shared_with_group, :group
-
- def sharing_allowed?
- sharing_outside_hierarchy_allowed? || within_hierarchy?
+ def valid_to_create?
+ can?(current_user, :admin_group_member, group) &&
+ can?(current_user, :read_group, shared_with_group) &&
+ sharing_allowed?
end
- def sharing_outside_hierarchy_allowed?
- !shared_group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
+ def build_link
+ @link = GroupGroupLink.new(
+ shared_group: group,
+ shared_with_group: shared_with_group,
+ group_access: params[:shared_group_access],
+ expires_at: params[:expires_at]
+ )
end
- def within_hierarchy?
- shared_group.root_ancestor.self_and_descendants_ids.include?(shared_with_group.id)
+ def setup_authorizations
+ shared_with_group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
end
end
end
diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb
index 3e15d47e8af..f73bba55bb9 100644
--- a/app/services/jira/requests/base.rb
+++ b/app/services/jira/requests/base.rb
@@ -3,7 +3,7 @@
module Jira
module Requests
class Base
- include ProjectServicesLoggable
+ include Integrations::Loggable
JIRA_API_VERSION = 2
# Limit the size of the JSON error message we will attempt to parse, as the JSON is external input.
diff --git a/app/services/jira_connect/sync_service.rb b/app/services/jira_connect/sync_service.rb
index bddc7cbe5a0..92255711399 100644
--- a/app/services/jira_connect/sync_service.rb
+++ b/app/services/jira_connect/sync_service.rb
@@ -39,7 +39,7 @@ module JiraConnect
end
def logger
- Gitlab::ProjectServiceLogger
+ Gitlab::IntegrationsLogger
end
end
end
diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb
index a0232779c97..72036aaff35 100644
--- a/app/services/projects/group_links/create_service.rb
+++ b/app/services/projects/group_links/create_service.rb
@@ -3,26 +3,31 @@
module Projects
module GroupLinks
class CreateService < BaseService
- def execute(group)
- return error('Not Found', 404) unless group && can?(current_user, :read_namespace, group)
+ include GroupLinkable
- link = project.project_group_links.new(
- group: group,
- group_access: params[:link_group_access],
- expires_at: params[:expires_at]
- )
+ def initialize(project, shared_with_group, user, params)
+ @shared_with_group = shared_with_group
- if link.save
- setup_authorizations(group)
- success(link: link)
- else
- error(link.errors.full_messages.to_sentence, 409)
- end
+ super(project, user, params)
end
private
- def setup_authorizations(group)
+ delegate :root_ancestor, to: :project
+
+ def valid_to_create?
+ can?(current_user, :read_namespace, shared_with_group) && sharing_allowed?
+ end
+
+ def build_link
+ @link = project.project_group_links.new(
+ group: shared_with_group,
+ group_access: params[:link_group_access],
+ expires_at: params[:expires_at]
+ )
+ end
+
+ def setup_authorizations
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id)
# AuthorizedProjectsWorker uses an exclusive lease per user but
@@ -30,7 +35,7 @@ module Projects
# compare the inconsistency rates of both approaches, we still run
# AuthorizedProjectsWorker but with some delay and lower urgency as a
# safety net.
- group.refresh_members_authorized_projects(
+ shared_with_group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
diff --git a/config/feature_flags/development/ci_expand_environment_name_and_url.yml b/config/feature_flags/development/ci_expand_environment_name_and_url.yml
index 58626cdfc46..beb7065cf53 100644
--- a/config/feature_flags/development/ci_expand_environment_name_and_url.yml
+++ b/config/feature_flags/development/ci_expand_environment_name_and_url.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358831
milestone: '14.10'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/metrics/counts_28d/20220428154012_live_preview.yml b/config/metrics/counts_28d/20220428154012_live_preview.yml
new file mode 100644
index 00000000000..8d2954cb825
--- /dev/null
+++ b/config/metrics/counts_28d/20220428154012_live_preview.yml
@@ -0,0 +1,26 @@
+---
+data_category: optional
+key_path: usage_activity_by_stage_monthly.create.action_monthly_active_users_live_preview_edit
+description: Count of monthly unique users that successfully connect to Live Preview
+product_section: dev
+product_stage: create
+product_group: group::editor
+product_category: web_ide
+value_type: number
+status: active
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85420
+time_frame: 28d
+data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - g_edit_by_live_preview
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+performance_indicator_type: []
+milestone: "15.0"
diff --git a/data/removals/15_0/15-0-removal-testcoveragesetting.yml b/data/removals/15_0/15-0-removal-testcoveragesetting.yml
new file mode 100644
index 00000000000..b929eaa96dd
--- /dev/null
+++ b/data/removals/15_0/15-0-removal-testcoveragesetting.yml
@@ -0,0 +1,14 @@
+- name: "Test coverage project CI/CD setting" # The headline announcing the removal. i.e. "`CI_PROJECT_CONFIG_PATH` removed in Gitlab 14.0"
+ announcement_milestone: "14.8" # The milestone when this feature was deprecated.
+ announcement_date: "2022-03-22" # The date of the milestone release when this feature was deprecated. This should almost always be the 22nd of a month (YYYY-MM-DD), unless you did an out of band blog post.
+ removal_milestone: "15.0" # The milestone when this feature is being removed.
+ removal_date: "2022-05-22" # This should almost always be the 22nd of a month (YYYY-MM-DD), the date of the milestone release when this feature will be removed.
+ breaking_change: true # Change to true if this removal is a breaking change.
+ reporter: exampleuser # GitLab username of the person reporting the removal
+ body: | # Do not modify this line, instead modify the lines below.
+ To specify a test coverage pattern, beginning in GitLab 15.0 the
+ [project setting for test coverage parsing](https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-to-a-merge-request-deprecated)
+ has been removed.
+
+ To set test coverage parsing, use the project’s `.gitlab-ci.yml` file by providing a regular expression with the
+ [`coverage` keyword](https://docs.gitlab.com/ee/ci/yaml/index.html#coverage).
diff --git a/db/migrate/20220420173247_add_group_inheritance_type_to_pe_authorizable.rb b/db/migrate/20220420173247_add_group_inheritance_type_to_pe_authorizable.rb
new file mode 100644
index 00000000000..f1ddf48304e
--- /dev/null
+++ b/db/migrate/20220420173247_add_group_inheritance_type_to_pe_authorizable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddGroupInheritanceTypeToPeAuthorizable < Gitlab::Database::Migration[2.0]
+ def change
+ add_column :protected_environment_deploy_access_levels,
+ :group_inheritance_type,
+ :smallint,
+ default: 0, limit: 2, null: false
+ add_column :protected_environment_approval_rules,
+ :group_inheritance_type,
+ :smallint,
+ default: 0, limit: 2, null: false
+ end
+end
diff --git a/db/schema_migrations/20220420173247 b/db/schema_migrations/20220420173247
new file mode 100644
index 00000000000..9ab6dac867e
--- /dev/null
+++ b/db/schema_migrations/20220420173247
@@ -0,0 +1 @@
+a4113363674f268a3beaef22e29b2aba4e5ba7566bc47dc5676ddc8f8733d331 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index ea6c398df46..a84db424277 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19786,6 +19786,7 @@ CREATE TABLE protected_environment_approval_rules (
updated_at timestamp with time zone NOT NULL,
access_level smallint,
required_approvals smallint NOT NULL,
+ group_inheritance_type smallint DEFAULT 0 NOT NULL,
CONSTRAINT chk_rails_bed75249bc CHECK ((((access_level IS NOT NULL) AND (group_id IS NULL) AND (user_id IS NULL)) OR ((user_id IS NOT NULL) AND (access_level IS NULL) AND (group_id IS NULL)) OR ((group_id IS NOT NULL) AND (user_id IS NULL) AND (access_level IS NULL)))),
CONSTRAINT chk_rails_cfa90ae3b5 CHECK ((required_approvals > 0))
);
@@ -19806,7 +19807,8 @@ CREATE TABLE protected_environment_deploy_access_levels (
access_level integer DEFAULT 40,
protected_environment_id integer NOT NULL,
user_id integer,
- group_id integer
+ group_id integer,
+ group_inheritance_type smallint DEFAULT 0 NOT NULL
);
CREATE SEQUENCE protected_environment_deploy_access_levels_id_seq
diff --git a/doc/update/removals.md b/doc/update/removals.md
index 98d4718049f..a536d938eda 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -104,6 +104,21 @@ A feature flag was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11
In GitLab 15.0, we will remove the feature flag, and you must always authenticate when you use the Dependency Proxy.
+### Test coverage project CI/CD setting
+
+WARNING:
+This feature was changed or removed in 15.0
+as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
+Before updating GitLab, review the details carefully to determine if you need to make any
+changes to your code, settings, or workflow.
+
+To specify a test coverage pattern, beginning in GitLab 15.0 the
+[project setting for test coverage parsing](https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-to-a-merge-request-deprecated)
+has been removed.
+
+To set test coverage parsing, use the project’s `.gitlab-ci.yml` file by providing a regular expression with the
+[`coverage` keyword](https://docs.gitlab.com/ee/ci/yaml/index.html#coverage).
+
### Update to the Container Registry group-level API
WARNING:
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index 60f97f522cf..7277a9c714a 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -199,8 +199,8 @@ and the CI YAML file:
dependencies:
- plan
when: manual
- only:
- - master
+ rules:
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
```
1. Push your project to GitLab, which triggers a CI job pipeline. This pipeline
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index d6f679d0255..60bb51bf48f 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -418,7 +418,6 @@ module API
optional :expires_at, type: Date, desc: 'Share expiration date'
end
post ":id/share", feature_category: :subgroups do
- shared_group = find_group!(params[:id])
shared_with_group = find_group!(params[:group_id])
group_link_create_params = {
@@ -426,11 +425,11 @@ module API
expires_at: params[:expires_at]
}
- result = ::Groups::GroupLinks::CreateService.new(shared_group, shared_with_group, current_user, group_link_create_params).execute
- shared_group.preload_shared_group_links
+ result = ::Groups::GroupLinks::CreateService.new(user_group, shared_with_group, current_user, group_link_create_params).execute
+ user_group.preload_shared_group_links
if result[:status] == :success
- present shared_group, with: Entities::GroupDetail, current_user: current_user
+ present user_group, with: Entities::GroupDetail, current_user: current_user
else
render_api_error!(result[:message], result[:http_status])
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 0bb062a6f66..28d6bfb1858 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -575,14 +575,14 @@ module API
end
post ":id/share", feature_category: :authentication_and_authorization do
authorize! :admin_project, user_project
- group = Group.find_by_id(params[:group_id])
+ shared_with_group = Group.find_by_id(params[:group_id])
unless user_project.allowed_to_share_with_group?
break render_api_error!("The project sharing with group is disabled", 400)
end
- result = ::Projects::GroupLinks::CreateService.new(user_project, current_user, declared_params(include_missing: false))
- .execute(group)
+ result = ::Projects::GroupLinks::CreateService
+ .new(user_project, shared_with_group, current_user, declared_params(include_missing: false)).execute
if result[:status] == :success
present result[:link], with: Entities::ProjectGroupLink
diff --git a/lib/bulk_imports/projects/stage.rb b/lib/bulk_imports/projects/stage.rb
index 289db225c5d..229df9c410d 100644
--- a/lib/bulk_imports/projects/stage.rb
+++ b/lib/bulk_imports/projects/stage.rb
@@ -63,10 +63,6 @@ module BulkImports
pipeline: BulkImports::Projects::Pipelines::ProtectedBranchesPipeline,
stage: 4
},
- ci_pipelines: {
- pipeline: BulkImports::Projects::Pipelines::CiPipelinesPipeline,
- stage: 4
- },
project_feature: {
pipeline: BulkImports::Projects::Pipelines::ProjectFeaturePipeline,
stage: 4
@@ -83,6 +79,10 @@ module BulkImports
pipeline: BulkImports::Projects::Pipelines::ReleasesPipeline,
stage: 4
},
+ ci_pipelines: {
+ pipeline: BulkImports::Projects::Pipelines::CiPipelinesPipeline,
+ stage: 5
+ },
wiki: {
pipeline: BulkImports::Common::Pipelines::WikiPipeline,
stage: 5
diff --git a/lib/gitlab/project_service_logger.rb b/lib/gitlab/integrations_logger.rb
index 9b0357d3161..c62a5f6d321 100644
--- a/lib/gitlab/project_service_logger.rb
+++ b/lib/gitlab/integrations_logger.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Gitlab
- class ProjectServiceLogger < Gitlab::JsonLogger
+ class IntegrationsLogger < Gitlab::JsonLogger
def self.file_name_noext
'integrations_json'
end
diff --git a/lib/gitlab/usage_data_counters/editor_unique_counter.rb b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
index bc0126cd893..f97ebdccecf 100644
--- a/lib/gitlab/usage_data_counters/editor_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
@@ -8,6 +8,7 @@ module Gitlab
EDIT_BY_WEB_IDE = 'g_edit_by_web_ide'
EDIT_BY_SSE = 'g_edit_by_sse'
EDIT_CATEGORY = 'ide_edit'
+ EDIT_BY_LIVE_PREVIEW = 'g_edit_by_live_preview'
class << self
def track_web_ide_edit_action(author:, time: Time.zone.now)
@@ -47,6 +48,10 @@ module Gitlab
count_unique(EDIT_BY_SSE, date_from, date_to)
end
+ def track_live_preview_edit_action(author:, time: Time.zone.now)
+ track_unique_action(EDIT_BY_LIVE_PREVIEW, author, time)
+ end
+
private
def track_unique_action(action, author, time)
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
index 0d89a5181ec..448ed4c66e1 100644
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ b/lib/gitlab/usage_data_counters/known_events/common.yml
@@ -40,6 +40,11 @@
redis_slot: edit
expiry: 29
aggregation: daily
+- name: g_edit_by_live_preview
+ category: ide_edit
+ redis_slot: edit
+ expiry: 29
+ aggregation: daily
- name: i_search_total
category: search
redis_slot: search
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2260741f390..9532437d5a1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11725,25 +11725,25 @@ msgstr ""
msgid "DastSiteValidation|Revoke validation"
msgstr ""
-msgid "DastSiteValidation|Step 1 - Choose site validation method"
+msgid "DastSiteValidation|Step 1 - Choose site validation method."
msgstr ""
-msgid "DastSiteValidation|Step 2 - Add following HTTP header to your site"
+msgid "DastSiteValidation|Step 2 - Add the following HTTP header to your site."
msgstr ""
-msgid "DastSiteValidation|Step 2 - Add following meta tag to your site"
+msgid "DastSiteValidation|Step 2 - Add the following meta tag to your site."
msgstr ""
-msgid "DastSiteValidation|Step 2 - Add following text to the target site"
+msgid "DastSiteValidation|Step 2 - Download the following text file, then upload it to the target site."
msgstr ""
-msgid "DastSiteValidation|Step 3 - Confirm header location and validate"
+msgid "DastSiteValidation|Step 3 - Confirm header location."
msgstr ""
-msgid "DastSiteValidation|Step 3 - Confirm meta tag location and validate"
+msgid "DastSiteValidation|Step 3 - Confirm meta tag location."
msgstr ""
-msgid "DastSiteValidation|Step 3 - Confirm text file location and validate"
+msgid "DastSiteValidation|Step 3 - Confirm text file location."
msgstr ""
msgid "DastSiteValidation|Text file validation"
@@ -11760,13 +11760,13 @@ msgid_plural "DastSiteValidation|This will affect %d other profiles targeting th
msgstr[0] ""
msgstr[1] ""
-msgid "DastSiteValidation|To run an active scan, validate your target site. All site profiles that share the same base URL share the same validation status."
+msgid "DastSiteValidation|To run an active scan, validate your site. Site profile validation reduces the risk of running an active scan against the wrong website. All site profiles that share the same base URL share the same validation status."
msgstr ""
msgid "DastSiteValidation|Validate"
msgstr ""
-msgid "DastSiteValidation|Validate target site"
+msgid "DastSiteValidation|Validate site"
msgstr ""
msgid "DastSiteValidation|Validated"
diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb
index 528d5c073b7..0c5a3b9df08 100644
--- a/spec/controllers/groups/shared_projects_controller_spec.rb
+++ b/spec/controllers/groups/shared_projects_controller_spec.rb
@@ -12,9 +12,10 @@ RSpec.describe Groups::SharedProjectsController do
Projects::GroupLinks::CreateService.new(
project,
+ group,
user,
link_group_access: Gitlab::Access::DEVELOPER
- ).execute(group)
+ ).execute
end
let!(:group) { create(:group) }
diff --git a/spec/controllers/projects/service_ping_controller_spec.rb b/spec/controllers/projects/service_ping_controller_spec.rb
index 13b34290962..fa92efee079 100644
--- a/spec/controllers/projects/service_ping_controller_spec.rb
+++ b/spec/controllers/projects/service_ping_controller_spec.rb
@@ -79,6 +79,18 @@ RSpec.describe Projects::ServicePingController do
it_behaves_like 'counter is not increased'
it_behaves_like 'counter is increased', 'WEB_IDE_PREVIEWS_SUCCESS_COUNT'
+
+ context 'when the user has access to the project' do
+ let(:user) { project.owner }
+
+ it 'increases the live preview view counter' do
+ expect(Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_live_preview_edit_action).with(author: user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
context 'when web ide clientside preview is not enabled' do
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index f18225f1246..a983ec43557 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -1,6 +1,8 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import { GlTab, GlTabs, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
@@ -18,15 +20,22 @@ import {
LICENSE_COMPLIANCE_DESCRIPTION,
LICENSE_COMPLIANCE_HELP_PATH,
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
+ LICENSE_ULTIMATE,
+ LICENSE_PREMIUM,
+ LICENSE_FREE,
} from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
+import waitForPromises from 'helpers/wait_for_promises';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
import {
REPORT_TYPE_LICENSE_COMPLIANCE,
REPORT_TYPE_SAST,
} from '~/vue_shared/security_reports/constants';
+import { getCurrentLicensePlanResponse } from '../mock_data';
const upgradePath = '/upgrade';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
@@ -36,14 +45,24 @@ const projectFullPath = 'namespace/project';
const vulnerabilityTrainingDocsPath = 'user/application_security/vulnerabilities/index';
useLocalStorageSpy();
+Vue.use(VueApollo);
describe('App component', () => {
let wrapper;
let userCalloutDismissSpy;
+ let mockApollo;
- const createComponent = ({ shouldShowCallout = true, ...propsData }) => {
+ const createComponent = ({
+ shouldShowCallout = true,
+ license = LICENSE_ULTIMATE,
+ ...propsData
+ }) => {
userCalloutDismissSpy = jest.fn();
+ mockApollo = createMockApollo([
+ [currentLicenseQuery, jest.fn().mockResolvedValue(getCurrentLicensePlanResponse(license))],
+ ]);
+
wrapper = extendedWrapper(
mount(SecurityConfigurationApp, {
propsData,
@@ -54,6 +73,7 @@ describe('App component', () => {
projectFullPath,
vulnerabilityTrainingDocsPath,
},
+ apolloProvider: mockApollo,
stubs: {
...stubChildren(SecurityConfigurationApp),
GlLink: false,
@@ -128,14 +148,16 @@ describe('App component', () => {
afterEach(() => {
wrapper.destroy();
+ mockApollo = null;
});
describe('basic structure', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
+ await waitForPromises();
});
it('renders main-heading with correct text', () => {
@@ -438,11 +460,12 @@ describe('App component', () => {
});
describe('Vulnerability management', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
+ await waitForPromises();
});
it('renders TrainingProviderList component', () => {
@@ -459,5 +482,21 @@ describe('App component', () => {
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
});
+
+ it.each`
+ license | display
+ ${LICENSE_ULTIMATE} | ${true}
+ ${LICENSE_PREMIUM} | ${false}
+ ${LICENSE_FREE} | ${false}
+ ${null} | ${false}
+ `('displays $display for license $license', async ({ license, display }) => {
+ createComponent({
+ license,
+ augmentedSecurityFeatures: securityFeaturesMock,
+ augmentedComplianceFeatures: complianceFeaturesMock,
+ });
+ await waitForPromises();
+ expect(findVulnerabilityManagementTab().exists()).toBe(display);
+ });
});
});
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 18a480bf082..94a36472a1d 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -111,3 +111,12 @@ export const tempProviderLogos = {
svg: `<svg>${[testProviderName[1]]}</svg>`,
},
};
+
+export const getCurrentLicensePlanResponse = (plan) => ({
+ data: {
+ currentLicense: {
+ id: 'gid://gitlab/License/1',
+ plan,
+ },
+ },
+});
diff --git a/spec/lib/bulk_imports/projects/stage_spec.rb b/spec/lib/bulk_imports/projects/stage_spec.rb
index 3cc9877df7d..e81d9cc5fb4 100644
--- a/spec/lib/bulk_imports/projects/stage_spec.rb
+++ b/spec/lib/bulk_imports/projects/stage_spec.rb
@@ -20,11 +20,11 @@ RSpec.describe BulkImports::Projects::Stage do
[4, BulkImports::Projects::Pipelines::MergeRequestsPipeline],
[4, BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline],
[4, BulkImports::Projects::Pipelines::ProtectedBranchesPipeline],
- [4, BulkImports::Projects::Pipelines::CiPipelinesPipeline],
[4, BulkImports::Projects::Pipelines::ProjectFeaturePipeline],
[4, BulkImports::Projects::Pipelines::ContainerExpirationPolicyPipeline],
[4, BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline],
[4, BulkImports::Projects::Pipelines::ReleasesPipeline],
+ [5, BulkImports::Projects::Pipelines::CiPipelinesPipeline],
[5, BulkImports::Common::Pipelines::WikiPipeline],
[5, BulkImports::Common::Pipelines::UploadsPipeline],
[5, BulkImports::Common::Pipelines::LfsObjectsPipeline],
diff --git a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
index 5f66387c82b..9aecb8f8b25 100644
--- a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
@@ -80,10 +80,13 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
it 'can return the count of actions per user deduplicated' do
described_class.track_web_ide_edit_action(author: user1)
+ described_class.track_live_preview_edit_action(author: user1)
described_class.track_snippet_editor_edit_action(author: user1)
described_class.track_sfe_edit_action(author: user1)
described_class.track_web_ide_edit_action(author: user2, time: time - 2.days)
described_class.track_web_ide_edit_action(author: user3, time: time - 3.days)
+ described_class.track_live_preview_edit_action(author: user2, time: time - 2.days)
+ described_class.track_live_preview_edit_action(author: user3, time: time - 3.days)
described_class.track_snippet_editor_edit_action(author: user3, time: time - 3.days)
described_class.track_sfe_edit_action(author: user3, time: time - 3.days)
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index 0f596d3908d..a2b914c42f2 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -848,7 +848,7 @@ RSpec.describe Integration do
let(:test_message) { "test message" }
let(:arguments) do
{
- service_class: integration.class.name,
+ integration_class: integration.class.name,
project_path: project.full_path,
project_id: project.id,
message: test_message,
@@ -857,13 +857,13 @@ RSpec.describe Integration do
end
it 'logs info messages using json logger' do
- expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
+ expect(Gitlab::IntegrationsLogger).to receive(:info).with(arguments)
integration.log_info(test_message, additional_argument: 'some argument')
end
it 'logs error messages using json logger' do
- expect(Gitlab::JsonLogger).to receive(:error).with(arguments)
+ expect(Gitlab::IntegrationsLogger).to receive(:error).with(arguments)
integration.log_error(test_message, additional_argument: 'some argument')
end
@@ -872,7 +872,7 @@ RSpec.describe Integration do
let(:project) { nil }
let(:arguments) do
{
- service_class: integration.class.name,
+ integration_class: integration.class.name,
project_path: nil,
project_id: nil,
message: test_message,
@@ -881,7 +881,7 @@ RSpec.describe Integration do
end
it 'logs info messages using json logger' do
- expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
+ expect(Gitlab::IntegrationsLogger).to receive(:info).with(arguments)
integration.log_info(test_message, additional_argument: 'some argument')
end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 1fe9e430011..557bda985af 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -397,7 +397,7 @@ RSpec.describe IssuePolicy do
end
end
- describe 'set_issue_crm_contacts' do
+ describe 'crm permissions' do
let(:user) { create(:user) }
let(:subgroup) { create(:group, :crm_enabled, parent: create(:group, :crm_enabled)) }
let(:project) { create(:project, group: subgroup) }
@@ -408,6 +408,7 @@ RSpec.describe IssuePolicy do
it 'is disallowed' do
project.add_reporter(user)
+ expect(policies).to be_disallowed(:read_crm_contacts)
expect(policies).to be_disallowed(:set_issue_crm_contacts)
end
end
@@ -416,6 +417,7 @@ RSpec.describe IssuePolicy do
it 'is allowed' do
subgroup.add_reporter(user)
+ expect(policies).to be_disallowed(:read_crm_contacts)
expect(policies).to be_disallowed(:set_issue_crm_contacts)
end
end
@@ -424,8 +426,31 @@ RSpec.describe IssuePolicy do
it 'is allowed' do
subgroup.parent.add_reporter(user)
+ expect(policies).to be_allowed(:read_crm_contacts)
expect(policies).to be_allowed(:set_issue_crm_contacts)
end
end
+
+ context 'when crm disabled on subgroup' do
+ let(:subgroup) { create(:group, parent: create(:group, :crm_enabled)) }
+
+ it 'is disallowed' do
+ subgroup.parent.add_reporter(user)
+
+ expect(policies).to be_disallowed(:read_crm_contacts)
+ expect(policies).to be_disallowed(:set_issue_crm_contacts)
+ end
+ end
+
+ context 'when peronsal namespace' do
+ let(:project) { create(:project) }
+
+ it 'is disallowed' do
+ project.add_reporter(user)
+
+ expect(policies).to be_disallowed(:read_crm_contacts)
+ expect(policies).to be_disallowed(:set_issue_crm_contacts)
+ end
+ end
end
end
diff --git a/spec/serializers/issue_sidebar_basic_entity_spec.rb b/spec/serializers/issue_sidebar_basic_entity_spec.rb
index 716c97f72af..564ffb1aea9 100644
--- a/spec/serializers/issue_sidebar_basic_entity_spec.rb
+++ b/spec/serializers/issue_sidebar_basic_entity_spec.rb
@@ -94,5 +94,37 @@ RSpec.describe IssueSidebarBasicEntity do
expect(entity[:show_crm_contacts]).to be(expected)
end
end
+
+ context 'in subgroup' do
+ let(:subgroup_project) { create(:project, :repository, group: subgroup) }
+ let(:subgroup_issue) { create(:issue, project: subgroup_project) }
+ let(:serializer) { IssueSerializer.new(current_user: user, project: subgroup_project) }
+
+ subject(:entity) { serializer.represent(subgroup_issue, serializer: 'sidebar') }
+
+ before do
+ subgroup_project.root_ancestor.add_reporter(user)
+ end
+
+ context 'with crm enabled' do
+ let(:subgroup) { create(:group, :crm_enabled, parent: group) }
+
+ it 'is true' do
+ allow(CustomerRelations::Contact).to receive(:exists_for_group?).with(group).and_return(true)
+
+ expect(entity[:show_crm_contacts]).to be_truthy
+ end
+ end
+
+ context 'with crm disabled' do
+ let(:subgroup) { create(:group, parent: group) }
+
+ it 'is false' do
+ allow(CustomerRelations::Contact).to receive(:exists_for_group?).with(group).and_return(true)
+
+ expect(entity[:show_crm_contacts]).to be_falsy
+ end
+ end
+ end
end
end
diff --git a/spec/services/groups/group_links/create_service_spec.rb b/spec/services/groups/group_links/create_service_spec.rb
index 03dac14be54..bfbaedbd06f 100644
--- a/spec/services/groups/group_links/create_service_spec.rb
+++ b/spec/services/groups/group_links/create_service_spec.rb
@@ -3,23 +3,13 @@
require 'spec_helper'
RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
- let(:parent_group_user) { create(:user) }
- let(:group_user) { create(:user) }
- let(:child_group_user) { create(:user) }
- let(:prevent_sharing) { false }
+ let_it_be(:shared_with_group_parent) { create(:group, :private) }
+ let_it_be(:shared_with_group) { create(:group, :private, parent: shared_with_group_parent) }
+ let_it_be(:shared_with_group_child) { create(:group, :private, parent: shared_with_group) }
let_it_be(:group_parent) { create(:group, :private) }
- let_it_be(:group) { create(:group, :private, parent: group_parent) }
- let_it_be(:group_child) { create(:group, :private, parent: group) }
- let(:ns_for_parent) { create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: prevent_sharing) }
- let(:shared_group_parent) { create(:group, :private, namespace_settings: ns_for_parent) }
- let(:shared_group) { create(:group, :private, parent: shared_group_parent) }
- let(:shared_group_child) { create(:group, :private, parent: shared_group) }
-
- let(:project_parent) { create(:project, group: shared_group_parent) }
- let(:project) { create(:project, group: shared_group) }
- let(:project_child) { create(:project, group: shared_group_child) }
+ let(:group) { create(:group, :private, parent: group_parent) }
let(:opts) do
{
@@ -28,127 +18,161 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
}
end
- let(:user) { group_user }
+ subject { described_class.new(group, shared_with_group, user, opts) }
- subject { described_class.new(shared_group, group, user, opts) }
+ shared_examples_for 'not shareable' do
+ it 'does not share and returns an error' do
+ expect do
+ result = subject.execute
- before do
- group.add_guest(group_user)
- shared_group.add_owner(group_user)
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(404)
+ end.not_to change { group.shared_with_group_links.count }
+ end
end
- it 'adds group to another group' do
- expect { subject.execute }.to change { group.shared_group_links.count }.from(0).to(1)
- end
+ shared_examples_for 'shareable' do
+ it 'adds group to another group' do
+ expect do
+ result = subject.execute
- it 'returns false if shared group is blank' do
- expect { described_class.new(nil, group, user, opts) }.not_to change { group.shared_group_links.count }
+ expect(result[:status]).to eq(:success)
+ end.to change { group.shared_with_group_links.count }.from(0).to(1)
+ end
end
- context 'user does not have access to group' do
- let(:user) { create(:user) }
-
- before do
- shared_group.add_owner(user)
- end
+ context 'when user has proper membership to share a group' do
+ let_it_be(:group_user) { create(:user) }
- it 'returns error' do
- result = subject.execute
+ let(:user) { group_user }
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(404)
+ before do
+ shared_with_group.add_guest(group_user)
+ group.add_owner(group_user)
end
- end
- context 'user does not have admin access to shared group' do
- let(:user) { create(:user) }
+ it_behaves_like 'shareable'
- before do
- group.add_guest(user)
- shared_group.add_developer(user)
- end
+ context 'when sharing outside the hierarchy is disabled' do
+ let_it_be(:group_parent) do
+ create(:group,
+ namespace_settings: create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true))
+ end
- it 'returns error' do
- result = subject.execute
+ it_behaves_like 'not shareable'
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(404)
- end
- end
+ context 'when group is inside hierarchy' do
+ let(:shared_with_group) { create(:group, :private, parent: group_parent) }
- context 'project authorizations based on group hierarchies' do
- before do
- group_parent.add_owner(parent_group_user)
- group.add_owner(group_user)
- group_child.add_owner(child_group_user)
+ it_behaves_like 'shareable'
+ end
end
- context 'project authorizations refresh' do
- it 'is executed only for the direct members of the group' do
- expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id)).and_call_original
+ context 'project authorizations based on group hierarchies' do
+ let_it_be(:child_group_user) { create(:user) }
+ let_it_be(:parent_group_user) { create(:user) }
- subject.execute
+ before do
+ shared_with_group_parent.add_owner(parent_group_user)
+ shared_with_group.add_owner(group_user)
+ shared_with_group_child.add_owner(child_group_user)
end
- end
- context 'project authorizations' do
- context 'group user' do
- let(:user) { group_user }
+ context 'project authorizations refresh' do
+ it 'is executed only for the direct members of the group' do
+ expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id))
+ .and_call_original
- it 'create proper authorizations' do
subject.execute
-
- expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
- expect(Ability.allowed?(user, :read_project, project)).to be_truthy
- expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
end
end
- context 'parent group user' do
- let(:user) { parent_group_user }
+ context 'project authorizations' do
+ let(:group_child) { create(:group, :private, parent: group) }
+ let(:project_parent) { create(:project, group: group_parent) }
+ let(:project) { create(:project, group: group) }
+ let(:project_child) { create(:project, group: group_child) }
- it 'create proper authorizations' do
- subject.execute
+ context 'group user' do
+ let(:user) { group_user }
+
+ it 'create proper authorizations' do
+ subject.execute
- expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
- expect(Ability.allowed?(user, :read_project, project)).to be_falsey
- expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
+ expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
+ expect(Ability.allowed?(user, :read_project, project)).to be_truthy
+ expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
+ end
end
- end
- context 'child group user' do
- let(:user) { child_group_user }
+ context 'parent group user' do
+ let(:user) { parent_group_user }
- it 'create proper authorizations' do
- subject.execute
+ it 'create proper authorizations' do
+ subject.execute
+
+ expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
+ expect(Ability.allowed?(user, :read_project, project)).to be_falsey
+ expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
+ end
+ end
- expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
- expect(Ability.allowed?(user, :read_project, project)).to be_falsey
- expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
+ context 'child group user' do
+ let(:user) { child_group_user }
+
+ it 'create proper authorizations' do
+ subject.execute
+
+ expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
+ expect(Ability.allowed?(user, :read_project, project)).to be_falsey
+ expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
+ end
end
end
end
end
- context 'sharing outside the hierarchy is disabled' do
- let(:prevent_sharing) { true }
+ context 'user does not have access to group' do
+ let(:user) { create(:user) }
- it 'prevents sharing with a group outside the hierarchy' do
- result = subject.execute
+ before do
+ group.add_owner(user)
+ end
- expect(group.reload.shared_group_links.count).to eq(0)
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(404)
+ it_behaves_like 'not shareable'
+ end
+
+ context 'user does not have admin access to shared group' do
+ let(:user) { create(:user) }
+
+ before do
+ shared_with_group.add_guest(user)
+ group.add_developer(user)
end
- it 'allows sharing with a group within the hierarchy' do
- sibling_group = create(:group, :private, parent: shared_group_parent)
- sibling_group.add_guest(group_user)
+ it_behaves_like 'not shareable'
+ end
+
+ context 'when group is blank' do
+ let(:group_user) { create(:user) }
+ let(:user) { group_user }
+ let(:group) { nil }
- result = described_class.new(shared_group, sibling_group, user, opts).execute
+ it 'does not share and returns an error' do
+ expect do
+ result = subject.execute
- expect(sibling_group.reload.shared_group_links.count).to eq(1)
- expect(result[:status]).to eq(:success)
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(404)
+ end.not_to change { shared_with_group.shared_group_links.count }
end
end
+
+ context 'when shared_with_group is blank' do
+ let(:group_user) { create(:user) }
+ let(:user) { group_user }
+ let(:shared_with_group) { nil }
+
+ it_behaves_like 'not shareable'
+ end
end
diff --git a/spec/services/issues/set_crm_contacts_service_spec.rb b/spec/services/issues/set_crm_contacts_service_spec.rb
index b0befb9f77c..5613cc49cc5 100644
--- a/spec/services/issues/set_crm_contacts_service_spec.rb
+++ b/spec/services/issues/set_crm_contacts_service_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Issues::SetCrmContactsService do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :crm_enabled) }
- let_it_be(:project) { create(:project, group: create(:group, parent: group)) }
+ let_it_be(:project) { create(:project, group: create(:group, :crm_enabled, parent: group)) }
let_it_be(:contacts) { create_list(:contact, 4, group: group) }
let_it_be(:issue, reload: true) { create(:issue, project: project) }
let_it_be(:issue_contact_1) do
@@ -58,6 +58,20 @@ RSpec.describe Issues::SetCrmContactsService do
group.add_reporter(user)
end
+ context 'but the crm setting is disabled' do
+ let(:params) { { replace_ids: [contacts[1].id, contacts[2].id] } }
+ let(:subgroup_with_crm_disabled) { create(:group, parent: group) }
+ let(:project_with_crm_disabled) { create(:project, group: subgroup_with_crm_disabled) }
+ let(:issue_with_crm_disabled) { create(:issue, project: project_with_crm_disabled) }
+
+ it 'returns expected error response' do
+ response = described_class.new(project: project_with_crm_disabled, current_user: user, params: params).execute(issue_with_crm_disabled)
+
+ expect(response).to be_error
+ expect(response.message).to eq('You have insufficient permissions to set customer relations contacts for this issue')
+ end
+ end
+
context 'when the contact does not exist' do
let(:params) { { replace_ids: [non_existing_record_id] } }
diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb
index c20aecaaef0..75d93a678f1 100644
--- a/spec/services/jira_connect/sync_service_spec.rb
+++ b/spec/services/jira_connect/sync_service_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe JiraConnect::SyncService do
end
def expect_log(type, message)
- expect(Gitlab::ProjectServiceLogger)
+ expect(Gitlab::IntegrationsLogger)
.to receive(type).with(
message: 'response from jira dev_info api',
integration: 'JiraConnect',
diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb
index 4ea5f2b3a53..65d3085a850 100644
--- a/spec/services/projects/group_links/create_service_spec.rb
+++ b/spec/services/projects/group_links/create_service_spec.rb
@@ -5,65 +5,104 @@ require 'spec_helper'
RSpec.describe Projects::GroupLinks::CreateService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:group) { create :group }
- let_it_be(:project) { create :project }
+ let_it_be(:project) { create(:project, namespace: create(:namespace, :with_namespace_settings)) }
- let(:group_access) { Gitlab::Access::DEVELOPER }
let(:opts) do
{
- link_group_access: group_access,
+ link_group_access: Gitlab::Access::DEVELOPER,
expires_at: nil
}
end
- subject { described_class.new(project, user, opts) }
+ subject { described_class.new(project, group, user, opts) }
- before do
- group.add_developer(user)
- end
+ shared_examples_for 'not shareable' do
+ it 'does not share and returns an error' do
+ expect do
+ result = subject.execute
- it 'adds group to project' do
- expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(404)
+ end.not_to change { project.project_group_links.count }
+ end
end
- it 'updates authorization', :sidekiq_inline do
- expect { subject.execute(group) }.to(
- change { Ability.allowed?(user, :read_project, project) }
- .from(false).to(true))
- end
+ shared_examples_for 'shareable' do
+ it 'adds group to project' do
+ expect do
+ result = subject.execute
- it 'returns false if group is blank' do
- expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
+ expect(result[:status]).to eq(:success)
+ end.to change { project.project_group_links.count }.from(0).to(1)
+ end
end
- it 'returns error if user is not allowed to share with a group' do
- expect { subject.execute(create(:group)) }.not_to change { project.project_group_links.count }
- end
+ context 'when user has proper membership to share a group' do
+ before do
+ group.add_guest(user)
+ end
- context 'with specialized project_authorization workers' do
- let_it_be(:other_user) { create(:user) }
+ it_behaves_like 'shareable'
- before do
- group.add_developer(other_user)
+ it 'updates authorization', :sidekiq_inline do
+ expect { subject.execute }.to(
+ change { Ability.allowed?(user, :read_project, project) }
+ .from(false).to(true))
+ end
+
+ context 'with specialized project_authorization workers' do
+ let_it_be(:other_user) { create(:user) }
+
+ before do
+ group.add_developer(other_user)
+ end
+
+ it 'schedules authorization update for users with access to group' do
+ expect(AuthorizedProjectsWorker).not_to(
+ receive(:bulk_perform_async)
+ )
+ expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to(
+ receive(:perform_async)
+ .with(project.id)
+ .and_call_original
+ )
+ expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
+ receive(:bulk_perform_in)
+ .with(1.hour,
+ array_including([user.id], [other_user.id]),
+ batch_delay: 30.seconds, batch_size: 100)
+ .and_call_original
+ )
+
+ subject.execute
+ end
end
- it 'schedules authorization update for users with access to group' do
- expect(AuthorizedProjectsWorker).not_to(
- receive(:bulk_perform_async)
- )
- expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to(
- receive(:perform_async)
- .with(project.id)
- .and_call_original
- )
- expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
- receive(:bulk_perform_in)
- .with(1.hour,
- array_including([user.id], [other_user.id]),
- batch_delay: 30.seconds, batch_size: 100)
- .and_call_original
- )
-
- subject.execute(group)
+ context 'when sharing outside the hierarchy is disabled' do
+ let_it_be(:shared_group_parent) do
+ create(:group,
+ namespace_settings: create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true))
+ end
+
+ let_it_be(:project, reload: true) { create(:project, group: shared_group_parent) }
+
+ it_behaves_like 'not shareable'
+
+ context 'when group is inside hierarchy' do
+ let(:group) { create(:group, :private, parent: shared_group_parent) }
+
+ it_behaves_like 'shareable'
+ end
end
end
+
+ context 'when user does not have permissions for the group' do
+ it_behaves_like 'not shareable'
+ end
+
+ context 'when group is blank' do
+ let(:group) { nil }
+
+ it_behaves_like 'not shareable'
+ end
end
diff --git a/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
index c4f6273b46c..ce611601f19 100644
--- a/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
@@ -66,11 +66,11 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
it 'logs the error' do
stub_client_and_raise(Timeout::Error, 'foo')
- expect(Gitlab::ProjectServiceLogger).to receive(:error).with(
+ expect(Gitlab::IntegrationsLogger).to receive(:error).with(
hash_including(
client_url: be_present,
message: 'Error sending message',
- service_class: described_class.name,
+ integration_class: described_class.name,
error: hash_including(
exception_class: Timeout::Error.name,
exception_message: 'foo',