Commit 30a500b0 authored by Toon Claes's avatar Toon Claes

Merge branch 'mw/rename-external-approval-rules-to-status-checks' into 'master'

Refactor ExternalApprovalRules to ExternalStatusChecks

See merge request gitlab-org/gitlab!62186
parents b5665e3a cc0f65c0
......@@ -375,7 +375,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- ee/spec/requests/api/commits_spec.rb
- ee/spec/requests/api/dora/metrics_spec.rb
- ee/spec/requests/api/epics_spec.rb
- ee/spec/requests/api/external_approval_rules_spec.rb
- ee/spec/requests/api/geo_spec.rb
- ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb
- ee/spec/requests/api/graphql/current_user/todos_query_spec.rb
......@@ -439,9 +438,9 @@ RSpec/EmptyLineAfterFinalLetItBe:
- ee/spec/services/dast_scanner_profiles/destroy_service_spec.rb
- ee/spec/services/dast_scanner_profiles/update_service_spec.rb
- ee/spec/services/dast_site_profiles/destroy_service_spec.rb
- ee/spec/services/external_approval_rules/create_service_spec.rb
- ee/spec/services/external_approval_rules/destroy_service_spec.rb
- ee/spec/services/external_approval_rules/update_service_spec.rb
- ee/spec/services/external_status_checks/create_service_spec.rb
- ee/spec/services/external_status_checks/destroy_service_spec.rb
- ee/spec/services/external_status_checks/update_service_spec.rb
- ee/spec/services/gitlab_subscriptions/activate_service_spec.rb
- ee/spec/services/gitlab_subscriptions/apply_trial_service_spec.rb
- ee/spec/services/incident_management/incidents/upload_metric_service_spec.rb
......@@ -2829,7 +2828,7 @@ Gitlab/FeatureAvailableUsage:
- 'ee/app/views/shared/promotions/_promote_repository_features.html.haml'
- 'ee/app/workers/analytics/code_review_metrics_worker.rb'
- 'ee/app/workers/group_saml_group_sync_worker.rb'
- 'ee/lib/api/external_approval_rules.rb'
- 'ee/lib/api/external_status_checks.rb'
- 'ee/lib/ee/api/entities/approval_state.rb'
- 'ee/lib/ee/api/entities/board.rb'
- 'ee/lib/ee/api/entities/issue.rb'
......
......@@ -24,9 +24,15 @@ module MergeRequests
merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
merge_request.project.execute_services(merge_data, :merge_request_hooks)
execute_external_hooks(merge_request, merge_data)
enqueue_jira_connect_messages_for(merge_request)
end
def execute_external_hooks(merge_request, merge_data)
# Implemented in EE
end
def handle_changes(merge_request, options)
old_associations = options.fetch(:old_associations, {})
old_assignees = old_associations.fetch(:assignees, [])
......
# frozen_string_literal: true
class CreateExternalStatusChecksTable < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
create_table_with_constraints :external_status_checks, if_not_exists: true do |t|
t.references :project, foreign_key: { on_delete: :cascade }, null: false, index: false
t.timestamps_with_timezone
t.text :external_url, null: false
t.text_limit :external_url, 255
t.text :name, null: false
t.text_limit :name, 255
t.index([:project_id, :name],
unique: true,
name: 'idx_on_external_status_checks_project_id_name')
t.index([:project_id, :external_url],
unique: true,
name: 'idx_on_external_status_checks_project_id_external_url')
end
create_table :external_status_checks_protected_branches do |t|
t.bigint :external_status_check_id, null: false
t.bigint :protected_branch_id, null: false
t.index :external_status_check_id, name: 'index_esc_protected_branches_on_external_status_check_id'
t.index :protected_branch_id, name: 'index_esc_protected_branches_on_protected_branch_id'
end
end
def down
with_lock_retries do
drop_table :external_status_checks_protected_branches, force: :cascade, if_exists: true
end
with_lock_retries do
drop_table :external_status_checks, force: :cascade, if_exists: true
end
end
end
# frozen_string_literal: true
class RenameStatusCheckResponsesApprovalRule < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
execute('DELETE FROM status_check_responses')
unless column_exists?(:status_check_responses, :external_status_check_id)
add_column :status_check_responses, :external_status_check_id, :bigint, null: false # rubocop:disable Rails/NotNullColumn
end
add_concurrent_foreign_key :status_check_responses, :external_status_checks, column: :external_status_check_id, on_delete: :cascade
add_concurrent_foreign_key :status_check_responses, :merge_requests, column: :merge_request_id, on_delete: :cascade
add_concurrent_index :status_check_responses, :external_status_check_id
# Setting this to true so that we can remove the column in a future release once the column has been removed. It has been ignored in 14.0
change_column_null :status_check_responses, :external_approval_rule_id, true
with_lock_retries do
remove_foreign_key :status_check_responses, :external_approval_rules
end
end
def down
change_column_null :status_check_responses, :external_approval_rule_id, false
with_lock_retries do
add_foreign_key :status_check_responses, :external_approval_rules
end
remove_column :status_check_responses, :external_status_check_id
end
end
# frozen_string_literal: true
class AddStatusCheckForeignKeyToExternalStatusCheckId < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_concurrent_foreign_key :external_status_checks_protected_branches, :external_status_checks, column: :external_status_check_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :external_status_checks_protected_branches, column: :external_status_check_id
end
end
end
# frozen_string_literal: true
class AddStatusCheckForeignKeyToProtectedBranchId < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_concurrent_foreign_key :external_status_checks_protected_branches, :protected_branches, column: :protected_branch_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :external_status_checks_protected_branches, column: :protected_branch_id
end
end
end
f4191b4b8ae7c282c0012f533a01ebe341d62cb0418e39ad543d06ed2dac63a4
\ No newline at end of file
8b6e1c7bacf2cbc05ba94e3fea2ab20e30b78ccaa6833949c11f89d1bdec8110
\ No newline at end of file
b6c503eddc1c5e36957b59efc8fc5dd75da18104499667c3fcc435fcbd739af3
\ No newline at end of file
09771c6f56e54a4d3dc0caab4891cbaf2a1d5685ccb1161d141ce38e44d6cfdb
\ No newline at end of file
......@@ -12893,6 +12893,41 @@ CREATE SEQUENCE external_pull_requests_id_seq
ALTER SEQUENCE external_pull_requests_id_seq OWNED BY external_pull_requests.id;
CREATE TABLE external_status_checks (
id bigint NOT NULL,
project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
external_url text NOT NULL,
name text NOT NULL,
CONSTRAINT check_7e3b9eb41a CHECK ((char_length(name) <= 255)),
CONSTRAINT check_ae0dec3f61 CHECK ((char_length(external_url) <= 255))
);
CREATE SEQUENCE external_status_checks_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE external_status_checks_id_seq OWNED BY external_status_checks.id;
CREATE TABLE external_status_checks_protected_branches (
id bigint NOT NULL,
external_status_check_id bigint NOT NULL,
protected_branch_id bigint NOT NULL
);
CREATE SEQUENCE external_status_checks_protected_branches_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE external_status_checks_protected_branches_id_seq OWNED BY external_status_checks_protected_branches.id;
CREATE TABLE feature_gates (
id integer NOT NULL,
feature_key character varying NOT NULL,
......@@ -18127,8 +18162,9 @@ ALTER SEQUENCE sprints_id_seq OWNED BY sprints.id;
CREATE TABLE status_check_responses (
id bigint NOT NULL,
merge_request_id bigint NOT NULL,
external_approval_rule_id bigint NOT NULL,
sha bytea NOT NULL
external_approval_rule_id bigint,
sha bytea NOT NULL,
external_status_check_id bigint NOT NULL
);
CREATE SEQUENCE status_check_responses_id_seq
......@@ -19863,6 +19899,10 @@ ALTER TABLE ONLY external_approval_rules_protected_branches ALTER COLUMN id SET
ALTER TABLE ONLY external_pull_requests ALTER COLUMN id SET DEFAULT nextval('external_pull_requests_id_seq'::regclass);
ALTER TABLE ONLY external_status_checks ALTER COLUMN id SET DEFAULT nextval('external_status_checks_id_seq'::regclass);
ALTER TABLE ONLY external_status_checks_protected_branches ALTER COLUMN id SET DEFAULT nextval('external_status_checks_protected_branches_id_seq'::regclass);
ALTER TABLE ONLY feature_gates ALTER COLUMN id SET DEFAULT nextval('feature_gates_id_seq'::regclass);
ALTER TABLE ONLY features ALTER COLUMN id SET DEFAULT nextval('features_id_seq'::regclass);
......@@ -21174,6 +21214,12 @@ ALTER TABLE ONLY external_approval_rules_protected_branches
ALTER TABLE ONLY external_pull_requests
ADD CONSTRAINT external_pull_requests_pkey PRIMARY KEY (id);
ALTER TABLE ONLY external_status_checks
ADD CONSTRAINT external_status_checks_pkey PRIMARY KEY (id);
ALTER TABLE ONLY external_status_checks_protected_branches
ADD CONSTRAINT external_status_checks_protected_branches_pkey PRIMARY KEY (id);
ALTER TABLE ONLY feature_gates
ADD CONSTRAINT feature_gates_pkey PRIMARY KEY (id);
......@@ -22388,6 +22434,10 @@ CREATE UNIQUE INDEX idx_on_external_approval_rules_project_id_external_url ON ex
CREATE UNIQUE INDEX idx_on_external_approval_rules_project_id_name ON external_approval_rules USING btree (project_id, name);
CREATE UNIQUE INDEX idx_on_external_status_checks_project_id_external_url ON external_status_checks USING btree (project_id, external_url);
CREATE UNIQUE INDEX idx_on_external_status_checks_project_id_name ON external_status_checks USING btree (project_id, name);
CREATE INDEX idx_packages_build_infos_on_package_id ON packages_build_infos USING btree (package_id);
CREATE INDEX idx_packages_debian_group_component_files_on_architecture_id ON packages_debian_group_component_files USING btree (architecture_id);
......@@ -23278,6 +23328,10 @@ CREATE INDEX index_epics_on_start_date_sourcing_epic_id ON epics USING btree (st
CREATE INDEX index_epics_on_start_date_sourcing_milestone_id ON epics USING btree (start_date_sourcing_milestone_id);
CREATE INDEX index_esc_protected_branches_on_external_status_check_id ON external_status_checks_protected_branches USING btree (external_status_check_id);
CREATE INDEX index_esc_protected_branches_on_protected_branch_id ON external_status_checks_protected_branches USING btree (protected_branch_id);
CREATE INDEX index_events_on_action ON events USING btree (action);
CREATE INDEX index_events_on_author_id_and_created_at ON events USING btree (author_id, created_at);
......@@ -24668,6 +24722,8 @@ CREATE INDEX index_sprints_on_title_trigram ON sprints USING gin (title gin_trgm
CREATE INDEX index_status_check_responses_on_external_approval_rule_id ON status_check_responses USING btree (external_approval_rule_id);
CREATE INDEX index_status_check_responses_on_external_status_check_id ON status_check_responses USING btree (external_status_check_id);
CREATE INDEX index_status_check_responses_on_merge_request_id ON status_check_responses USING btree (merge_request_id);
CREATE UNIQUE INDEX index_status_page_published_incidents_on_issue_id ON status_page_published_incidents USING btree (issue_id);
......@@ -25449,9 +25505,6 @@ ALTER TABLE ONLY ci_unit_test_failures
ALTER TABLE ONLY project_pages_metadata
ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL;
ALTER TABLE ONLY status_check_responses
ADD CONSTRAINT fk_116e7e7369 FOREIGN KEY (external_approval_rule_id) REFERENCES external_approval_rules(id) ON DELETE CASCADE;
ALTER TABLE ONLY group_deletion_schedules
ADD CONSTRAINT fk_11e3ebfcdd FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
......@@ -25623,6 +25676,9 @@ ALTER TABLE ONLY clusters_applications_prometheus
ALTER TABLE ONLY terraform_states
ADD CONSTRAINT fk_558901b030 FOREIGN KEY (locked_by_user_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY status_check_responses
ADD CONSTRAINT fk_55bd2abc83 FOREIGN KEY (external_status_check_id) REFERENCES external_status_checks(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_request_metrics
ADD CONSTRAINT fk_56067dcb44 FOREIGN KEY (target_project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -25911,6 +25967,9 @@ ALTER TABLE ONLY bulk_import_entities
ALTER TABLE ONLY compliance_management_frameworks
ADD CONSTRAINT fk_b74c45b71f FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY external_status_checks_protected_branches
ADD CONSTRAINT fk_b7d788e813 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
ALTER TABLE ONLY issue_assignees
ADD CONSTRAINT fk_b7d881734a FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
......@@ -25971,6 +26030,9 @@ ALTER TABLE ONLY external_approval_rules_protected_branches
ALTER TABLE ONLY external_approval_rules_protected_branches
ADD CONSTRAINT fk_ca2ffb55e6 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
ALTER TABLE ONLY external_status_checks_protected_branches
ADD CONSTRAINT fk_cc0dcc36d1 FOREIGN KEY (external_status_check_id) REFERENCES external_status_checks(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_profiles_pipelines
ADD CONSTRAINT fk_cc206a8c13 FOREIGN KEY (dast_profile_id) REFERENCES dast_profiles(id) ON DELETE CASCADE;
......@@ -26358,6 +26420,9 @@ ALTER TABLE ONLY boards_epic_board_positions
ALTER TABLE ONLY geo_repository_created_events
ADD CONSTRAINT fk_rails_1f49e46a61 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY external_status_checks
ADD CONSTRAINT fk_rails_1f5a8aa809 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY dora_daily_metrics
ADD CONSTRAINT fk_rails_1fd07aff6f FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE;
......@@ -503,120 +503,6 @@ DELETE /projects/:id/approval_rules/:approval_rule_id
| `id` | integer | yes | The ID of a project |
| `approval_rule_id` | integer | yes | The ID of a approval rule
## External Project-level MR approvals **(ULTIMATE)**
Configuration for approvals on a specific Merge Request which makes a call to an external HTTP resource.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 13.10.
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-external-project-level-mr-approvals). **(ULTIMATE SELF)**
### Get project external approval rules **(ULTIMATE)**
You can request information about a project's external approval rules using the following endpoint:
```plaintext
GET /projects/:id/external_approval_rules
```
**Parameters:**
| Attribute | Type | Required | Description |
|---------------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```json
[
{
"id": 1,
"name": "Compliance Check",
"project_id": 6,
"external_url": "https://gitlab.com/example/test.json",
"protected_branches": [
{
"id": 14,
"project_id": 6,
"name": "master",
"created_at": "2020-10-12T14:04:50.787Z",
"updated_at": "2020-10-12T14:04:50.787Z",
"code_owner_approval_required": false
}
]
}
]
```
### Create external approval rule **(ULTIMATE)**
You can create a new external approval rule for a project using the following endpoint:
```plaintext
POST /projects/:id/external_approval_rules
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `name` | string | yes | Display name of approval rule |
| `external_url` | string | yes | URL of external approval resource |
| `protected_branch_ids` | `array<Integer>` | no | The ids of protected branches to scope the rule by |
### Delete external approval rule **(ULTIMATE)**
You can delete an external approval rule for a project using the following endpoint:
```plaintext
DELETE /projects/:id/external_approval_rules/:rule_id
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `rule_id` | integer | yes | The ID of an approval rule |
| `id` | integer | yes | The ID of a project |
### Update external approval rule **(ULTIMATE)**
You can update an existing external approval rule for a project using the following endpoint:
```plaintext
PUT /projects/:id/external_approval_rules/:rule_id
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `rule_id` | integer | yes | The ID of an external approval rule |
| `name` | string | no | Display name of approval rule |
| `external_url` | string | no | URL of external approval resource |
| `protected_branch_ids` | `array<Integer>` | no | The ids of protected branches to scope the rule by |
### Enable or disable External Project-level MR approvals **(ULTIMATE SELF)**
Enable or disable External Project-level MR approvals is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../user/feature_flags.md)
can enable it.
To enable it:
```ruby
# For the instance
Feature.enable(:ff_compliance_approval_gates)
# For a single project
Feature.enable(:ff_compliance_approval_gates, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:ff_compliance_approval_gates)
# For a single project
Feature.disable(:ff_compliance_approval_gates, Project.find(<project id>))
```
## Merge Request-level MR approvals
Configuration for approvals on a specific Merge Request. Must be authenticated for all endpoints.
......
......@@ -74,6 +74,110 @@ deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
can enable it.
## Get project external status checks **(ULTIMATE)**
You can request information about a project's external status checks using the following endpoint:
```plaintext
GET /projects/:id/external_status_checks
```
**Parameters:**
| Attribute | Type | Required | Description |
|---------------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```json
[
{
"id": 1,
"name": "Compliance Check",
"project_id": 6,
"external_url": "https://gitlab.com/example/test.json",
"protected_branches": [
{
"id": 14,
"project_id": 6,
"name": "master",
"created_at": "2020-10-12T14:04:50.787Z",
"updated_at": "2020-10-12T14:04:50.787Z",
"code_owner_approval_required": false
}
]
}
]
```
### Create external status check **(ULTIMATE)**
You can create a new external status check for a project using the following endpoint:
```plaintext
POST /projects/:id/external_status_checks
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `name` | string | yes | Display name of status check |
| `external_url` | string | yes | URL of status check resource |
| `protected_branch_ids` | `array<Integer>` | no | The ids of protected branches to scope the rule by |
### Delete external status check **(ULTIMATE)**
You can delete an external status check for a project using the following endpoint:
```plaintext
DELETE /projects/:id/external_status_checks/:check_id
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `rule_id` | integer | yes | The ID of an status check |
| `id` | integer | yes | The ID of a project |
### Update external status check **(ULTIMATE)**
You can update an existing external status check for a project using the following endpoint:
```plaintext
PUT /projects/:id/external_status_checks/:check_id
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `rule_id` | integer | yes | The ID of an external status check |
| `name` | string | no | Display name of status check |
| `external_url` | string | no | URL of external status check resource |
| `protected_branch_ids` | `array<Integer>` | no | The ids of protected branches to scope the rule by |
### Enable or disable External Project-level MR status checks **(ULTIMATE SELF)**
Enable or disable External Project-level MR status checks is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../user/feature_flags.md)
can enable it.
To enable it:
```ruby
# For the instance
Feature.enable(:ff_compliance_approval_gates)
# For a single project
Feature.enable(:ff_compliance_approval_gates, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:ff_compliance_approval_gates)
# For a single project
Feature.disable(:ff_compliance_approval_gates, Project.find(<project id>))
```
To enable it:
```ruby
......
......@@ -97,36 +97,6 @@ Without the approvals, the work cannot merge. Required approvals enable multiple
- [Require approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests)
before merging code that could introduce a vulnerability. **(ULTIMATE)**
## Notify external services **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab Ultimate 13.10.
> - [Deployed behind a feature flag](../../../feature_flags.md), disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](../../../../api/merge_request_approvals.md#enable-or-disable-external-project-level-mr-approvals). **(ULTIMATE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
You can create an external approval rule to integrate approvals with third-party tools.
When users create, change, or close merge requests, GitLab sends a notification.
The users of the third-party tools can then approve merge requests from outside of GitLab.
With this integration, you can integrate with third-party workflow tools, like
[ServiceNow](https://www.servicenow.co.uk/), or the custom tool of your choice.
You can modify your external approval rules
[by using the REST API](../../../../api/merge_request_approvals.md#external-project-level-mr-approvals).
The lack of an external approval doesn't block the merging of a merge request.
When [approval rule overrides](settings.md#prevent-overrides-of-default-approvals) are allowed,
changes to default approval rules will **not** be applied to existing
merge requests, except for changes to the [target branch](rules.md#approvals-for-protected-branches)
of the rule.
To learn more about use cases, feature discovery, and development timelines,
see the [External API approval rules epic](https://gitlab.com/groups/gitlab-org/-/epics/3869).
## Related links
- [Merge request approvals API](../../../../api/merge_request_approvals.md)
......
......@@ -81,7 +81,7 @@ module EE
'license_check_help_page_path': help_page_path('user/application_security/index', anchor: 'enabling-license-approvals-within-a-project') }
if ::Feature.enabled?(:ff_compliance_approval_gates, project, default_enabled: :yaml)
data[:external_approval_rules_path] = expose_path(api_v4_projects_external_approval_rules_path(id: project.id))
data[:external_approval_rules_path] = expose_path(api_v4_projects_external_status_checks_path(id: project.id))
end
{ data: data }
......@@ -91,7 +91,7 @@ module EE
{
data: {
project_id: project.id,
status_checks_path: expose_path(api_v4_projects_external_approval_rules_path(id: project.id))
status_checks_path: expose_path(api_v4_projects_external_status_checks_path(id: project.id))
}
}
end
......
......@@ -62,7 +62,7 @@ module EE
includes(:protected_branches).reject { |rule| rule.applies_to_branch?(branch) }
end
end
has_many :external_approval_rules, class_name: 'ApprovalRules::ExternalApprovalRule'
has_many :external_status_checks, class_name: 'MergeRequests::ExternalStatusCheck'
has_many :approval_merge_request_rules, through: :merge_requests, source: :approval_rules
has_many :audit_events, as: :entity
has_many :path_locks
......@@ -440,7 +440,7 @@ module EE
end
def execute_external_compliance_hooks(data)
external_approval_rules.each do |approval_rule|
external_status_checks.each do |approval_rule|
approval_rule.async_execute(data)
end
end
......
# frozen_string_literal: true
module ApprovalRules
class ExternalApprovalRule < ApplicationRecord
self.table_name = 'external_approval_rules'
module MergeRequests
class ExternalStatusCheck < ApplicationRecord
self.table_name = 'external_status_checks'
include IgnorableColumns
ignore_column :external_approval_rule_id, remove_with: '14.3', remove_after: '2021-09-22'
scope :with_api_entity_associations, -> { preload(:protected_branches) }
belongs_to :project
......@@ -16,7 +20,7 @@ module ApprovalRules
end
def approved?(merge_request, sha)
merge_request.status_check_responses.where(external_approval_rule: self, sha: sha).exists?
merge_request.status_check_responses.where(external_status_check: self, sha: sha).exists?
end
def to_h
......
......@@ -9,10 +9,10 @@ module MergeRequests
sha_attribute :sha
belongs_to :merge_request
belongs_to :external_approval_rule, class_name: 'ApprovalRules::ExternalApprovalRule'
belongs_to :external_status_check, class_name: 'MergeRequests::ExternalStatusCheck'
validates :merge_request, presence: true
validates :external_approval_rule, presence: true
validates :external_status_check, presence: true
validates :sha, presence: true
end
end
......@@ -9,12 +9,10 @@ module EE
attr_accessor :blocking_merge_requests_params
override :execute_hooks
def execute_hooks(merge_request, action = 'open', old_rev: nil, old_associations: {})
super do
override :execute_external_hooks
def execute_external_hooks(merge_request, merge_data)
merge_request.project.execute_external_compliance_hooks(merge_data)
end
end
override :filter_params
def filter_params(merge_request)
......
# frozen_string_literal: true
module ExternalApprovalRules
module ExternalStatusChecks
class CreateService < BaseContainerService
def execute
return ServiceResponse.error(message: 'Failed to create rule', payload: { errors: ['Not allowed'] }, http_status: :unauthorized) unless current_user.can?(:admin_project, container)
rule = container.external_approval_rules.new(name: params[:name],
rule = container.external_status_checks.new(name: params[:name],
project: container,
external_url: params[:external_url],
protected_branch_ids: params[:protected_branch_ids])
......
# frozen_string_literal: true
module ExternalApprovalRules
module ExternalStatusChecks
class DestroyService < BaseContainerService
def execute(rule)
return unauthorized_error_response unless current_user.can?(:admin_project, container)
......
# frozen_string_literal: true
module ExternalApprovalRules
module ExternalStatusChecks
class DispatchService
REQUEST_BODY_SIZE_LIMIT = 25.megabytes
......
# frozen_string_literal: true
module ExternalApprovalRules
module ExternalStatusChecks
class UpdateService < BaseContainerService
def execute
return unauthorized_error_response unless current_user.can?(:admin_project, container)
......@@ -21,7 +21,7 @@ module ExternalApprovalRules
end
def rule
container.external_approval_rules.find(params[:rule_id])
container.external_status_checks.find(params[:check_id])
end
def unauthorized_error_response
......
......@@ -11,9 +11,9 @@ module ApprovalRules
tags :exclude_from_kubernetes
def perform(rule_id, data)
rule = ApprovalRules::ExternalApprovalRule.find(rule_id)
rule = MergeRequests::ExternalStatusCheck.find(rule_id)
ExternalApprovalRules::DispatchService.new(rule, data).execute
ExternalStatusChecks::DispatchService.new(rule, data).execute
end
end
end
......@@ -2,7 +2,7 @@
module API
module Entities
class ExternalApprovalRule < Grape::Entity
class ExternalStatusCheck < Grape::Entity
expose :id
expose :name
expose :project_id
......
......@@ -6,7 +6,7 @@ module API
class StatusCheckResponse < Grape::Entity
expose :id
expose :merge_request, using: Entities::MergeRequest
expose :external_approval_rule, using: Entities::ExternalApprovalRule
expose :external_status_check, using: Entities::ExternalStatusCheck
end
end
end
......
# frozen_string_literal: true
module API
class ExternalApprovalRules < ::API::Base
include PaginationParams
feature_category :source_code_management
before do
authenticate!
check_feature_enabled!
end
helpers do
def check_feature_enabled!
unauthorized! unless user_project.feature_available?(:compliance_approval_gates) &&
Feature.enabled?(:ff_compliance_approval_gates, user_project)
end
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
segment ':id/external_approval_rules' do
desc 'Create a new external approval rule' do
success ::API::Entities::ExternalApprovalRule
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :name, type: String, desc: 'The name of the external approval rule'
requires :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids,
type: Array[Integer],
coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce,
desc: 'The protected branch ids for this rule'
end
post do
service = ::ExternalApprovalRules::CreateService.new(
container: user_project,
current_user: current_user,
params: declared_params(include_missing: false)
).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalApprovalRule
else
render_api_error!(service.payload[:errors], service.http_status)
end
end
desc 'List project\'s external approval rules' do
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
use :pagination
end
get do
unauthorized! unless current_user.can?(:admin_project, user_project)
present paginate(user_project.external_approval_rules), with: ::API::Entities::ExternalApprovalRule
end
segment ':rule_id' do
desc 'Delete an external approval rule' do
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :rule_id, type: Integer, desc: 'The ID of the external approval rule'
end
delete do
external_approval_rule = user_project.external_approval_rules.find(params[:rule_id])
destroy_conditionally!(external_approval_rule) do |external_approval_rule|
::ExternalApprovalRules::DestroyService.new(
container: user_project,
current_user: current_user
).execute(external_approval_rule)
end
end
desc 'Update an external approval rule' do
success ::API::Entities::ExternalApprovalRule
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :rule_id, type: Integer, desc: 'The ID of the external approval rule'
optional :name, type: String, desc: 'The name of the approval rule'
optional :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids,
type: Array[Integer],
coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce,
desc: 'The protected branch ids for this rule'
end
put do
service = ::ExternalApprovalRules::UpdateService.new(
container: user_project,
current_user: current_user,
params: declared_params(include_missing: false)
).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalApprovalRule
else
render_api_error!(service.payload[:errors], service.http_status)
end
end
end
end
end
end
end
......@@ -6,9 +6,104 @@ module API
feature_category :compliance_management
before { authenticate! }
before do
authenticate!
check_feature_enabled!
end
helpers do
def check_feature_enabled!
unauthorized! unless user_project.licensed_feature_available?(:compliance_approval_gates) &&
Feature.enabled?(:ff_compliance_approval_gates, user_project)
end
end
resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
segment ':id/external_status_checks' do
desc 'Create a new external status check' do
success ::API::Entities::ExternalStatusCheck
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :name, type: String, desc: 'The name of the external status check'
requires :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids,
type: Array[Integer],
coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce,
desc: 'The protected branch ids for this check'
end
post do
service = ::ExternalStatusChecks::CreateService.new(
container: user_project,
current_user: current_user,
params: declared_params(include_missing: false)
).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalStatusCheck
else
render_api_error!(service.payload[:errors], service.http_status)
end
end
desc 'List project\'s external approval rules' do
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
use :pagination
end
get do
unauthorized! unless current_user.can?(:admin_project, user_project)
present paginate(user_project.external_status_checks), with: ::API::Entities::ExternalStatusCheck
end
segment ':check_id' do
desc 'Update an external approval rule' do
success ::API::Entities::ExternalStatusCheck
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :check_id, type: Integer, desc: 'The ID of the external status check'
optional :name, type: String, desc: 'The name of the status check'
optional :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids,
type: Array[Integer],
coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce,
desc: 'The protected branch ids for this check'
end
put do
service = ::ExternalStatusChecks::UpdateService.new(
container: user_project,
current_user: current_user,
params: declared_params(include_missing: false)
).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalStatusCheck
else
render_api_error!(service.payload[:errors], service.http_status)
end
end
desc 'Delete an external status check' do
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :check_id, type: Integer, desc: 'The ID of the status check'
end
delete do
external_status_check = user_project.external_status_checks.find(params[:check_id])
destroy_conditionally!(external_status_check) do |external_status_check|
::ExternalStatusChecks::DestroyService.new(
container: user_project,
current_user: current_user
).execute(external_status_check)
end
end
end
end
segment ':id/merge_requests/:merge_request_iid' do
desc 'Externally approve a merge request' do
detail 'This feature was introduced in 13.12 and is gated behind the :ff_compliance_approval_gates feature flag.'
......@@ -17,7 +112,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
requires :external_approval_rule_id, type: Integer, desc: 'The ID of a merge request rule'
requires :external_status_check_id, type: Integer, desc: 'The ID of a external status check'
requires :sha, type: String, desc: 'The current SHA at HEAD of the merge request.'
end
post 'status_check_responses' do
......@@ -27,7 +122,7 @@ module API
check_sha_param!(params, merge_request)
approval = merge_request.status_check_responses.create!(external_approval_rule_id: params[:external_approval_rule_id], sha: params[:sha])
approval = merge_request.status_check_responses.create!(external_status_check_id: params[:external_status_check_id], sha: params[:sha])
present(approval, with: Entities::MergeRequests::StatusCheckResponse)
end
......@@ -40,7 +135,7 @@ module API
merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
present(paginate(merge_request.project.external_approval_rules.all), with: Entities::MergeRequests::StatusCheck, merge_request: merge_request, sha: merge_request.source_branch_sha)
present(paginate(merge_request.project.external_status_checks.all), with: Entities::MergeRequests::StatusCheck, merge_request: merge_request, sha: merge_request.source_branch_sha)
end
end
end
......
......@@ -12,7 +12,6 @@ module EE
mount ::API::AuditEvents
mount ::API::ProjectApprovalRules
mount ::API::ExternalApprovalRules
mount ::API::StatusChecks
mount ::API::ProjectApprovalSettings
mount ::API::Dora::Metrics
......
# frozen_string_literal: true
FactoryBot.define do
factory :external_approval_rule, class: 'ApprovalRules::ExternalApprovalRule' do
factory :external_status_check, class: 'MergeRequests::ExternalStatusCheck' do
project
external_url { FFaker::Internet.http_url }
......
......@@ -3,6 +3,6 @@
FactoryBot.define do
factory :status_check_response, class: 'MergeRequests::StatusCheckResponse' do
merge_request
external_approval_rule
external_status_check
end
end
......@@ -112,7 +112,7 @@ RSpec.describe 'Project settings > [EE] Merge Requests', :js do
end
context 'with a status check' do
let_it_be(:rule) { create(:external_approval_rule, project: project) }
let_it_be(:rule) { create(:external_status_check, project: project) }
it 'updates the status check' do
visit edit_project_path(project)
......
......@@ -37,7 +37,7 @@ const TEST_RULE_RESPONSE = {
};
const TEST_SETTINGS_PATH = 'projects/9/approval_settings';
const TEST_RULES_PATH = 'projects/9/approval_settings/rules';
const TEST_EXTERNAL_RULES_PATH = 'projects/9/external_approval_rules';
const TEST_EXTERNAL_RULES_PATH = 'projects/9/external_status_checks';
describe('EE approvals project settings module actions', () => {
let state;
......
......@@ -9,7 +9,7 @@ jest.mock('~/flash');
describe('mountStatusChecks', () => {
const projectId = '12345';
const statusChecksPath = '/api/v4/projects/1/external_approval_rules';
const statusChecksPath = '/api/v4/projects/1/external_status_checks';
const dispatch = jest.fn();
let el;
......
......@@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
const statusChecksPath = '/api/v4/projects/1/external_approval_rules';
const statusChecksPath = '/api/v4/projects/1/external_status_checks';
const rootState = { settings: { statusChecksPath } };
const commit = jest.fn();
const dispatch = jest.fn();
......
......@@ -374,7 +374,7 @@ RSpec.describe ProjectsHelper do
stub_feature_flags(ff_compliance_approval_gates: feature_flag_enabled)
end
it 'includes external_approval_rules_path only when enabled' do
it 'includes external_status_checks_path only when enabled' do
expect(subject[:data].key?(:external_approval_rules_path)).to eq(feature_flag_enabled)
end
end
......@@ -387,7 +387,7 @@ RSpec.describe ProjectsHelper do
it 'returns the correct data' do
expect(subject[:data]).to eq({
project_id: project.id,
status_checks_path: expose_path(api_v4_projects_external_approval_rules_path(id: project.id))
status_checks_path: expose_path(api_v4_projects_external_status_checks_path(id: project.id))
})
end
end
......
......@@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe ApprovalRules::ExternalApprovalRule, type: :model do
subject { build(:external_approval_rule) }
RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do
subject { build(:external_status_check) }
describe 'Associations' do
it { is_expected.to belong_to(:project) }
......@@ -23,7 +23,7 @@ RSpec.describe ApprovalRules::ExternalApprovalRule, type: :model do
end
describe 'approved?' do
let_it_be(:rule) { create(:external_approval_rule) }
let_it_be(:rule) { create(:external_status_check) }
let_it_be(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
......@@ -31,13 +31,13 @@ RSpec.describe ApprovalRules::ExternalApprovalRule, type: :model do
subject { rule.approved?(merge_request, merge_request.source_branch_sha) }
context 'when a rule has a positive status check response' do
let_it_be(:status_check_response) { create(:status_check_response, merge_request: merge_request, external_approval_rule: rule, sha: merge_request.source_branch_sha) }
let_it_be(:status_check_response) { create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: merge_request.source_branch_sha) }
it { is_expected.to be true }
context 'when a rule also has a positive check response from an old sha' do
before do
create(:status_check_response, merge_request: merge_request, external_approval_rule: rule, sha: 'abc1234')
create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: 'abc1234')
end
it { is_expected.to be true }
......@@ -50,7 +50,7 @@ RSpec.describe ApprovalRules::ExternalApprovalRule, type: :model do
context 'when a rule has a positive status check response from an old sha' do
before do
create(:status_check_response, merge_request: merge_request, external_approval_rule: rule, sha: 'abc123')
create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: 'abc123')
end
it { is_expected.to be false }
......
......@@ -6,9 +6,9 @@ RSpec.describe MergeRequests::StatusCheckResponse, type: :model do
subject { build(:status_check_response) }
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:external_approval_rule).class_name('ApprovalRules::ExternalApprovalRule') }
it { is_expected.to belong_to(:external_status_check).class_name('MergeRequests::ExternalStatusCheck') }
it { is_expected.to validate_presence_of(:merge_request) }
it { is_expected.to validate_presence_of(:external_approval_rule) }
it { is_expected.to validate_presence_of(:external_status_check) }
it { is_expected.to validate_presence_of(:sha) }
end
......@@ -878,7 +878,7 @@ RSpec.describe Project do
end
describe '#execute_external_compliance_hooks' do
let_it_be(:rule) { create(:external_approval_rule) }
let_it_be(:rule) { create(:external_status_check) }
it 'enqueues the correct number of workers' do
allow(rule).to receive(:async_execute).once
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::API::ExternalApprovalRules do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) }
let_it_be(:rule) { create(:external_approval_rule, project: project, name: 'Rule 2', external_url: 'https://rule2.example') }
let(:collection_url) { "/projects/#{project.id}/external_approval_rules" }
let(:single_object_url) { "/projects/#{project.id}/external_approval_rules/#{rule.id}" }
describe 'DELETE projects/:id/external_approval_rules/:rule_id' do
before do
stub_licensed_features(compliance_approval_gates: true)
end
it 'deletes the specified rule' do
expect do
delete api(single_object_url, project.owner)
end.to change { ApprovalRules::ExternalApprovalRule.count }.by(-1)
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end
with_them do
before do
stub_feature_flags(ff_compliance_approval_gates: flag)
stub_licensed_features(compliance_approval_gates: licensed)
end
it 'returns the correct status code' do
delete api(single_object_url, (project_owner ? project.owner : build(:user)))
expect(response).to have_gitlab_http_status(status)
end
end
end
end
describe 'GET projects/:id/external_approval_rules' do
let_it_be(:rule) { create(:external_approval_rule, project: project, name: 'Rule 1', external_url: "http://rule1.example") }
let_it_be(:protected_branches) { create_list(:protected_branch, 3, project: project) }
before_all do
create(:external_approval_rule) # Creating an orphaned rule to make sure project scoping works as expected
end
before do
stub_licensed_features(compliance_approval_gates: true)
end
it 'responds with expected JSON', :aggregate_failures do
get api(collection_url, project.owner)
expect(json_response.size).to eq(2)
expect(json_response.map { |r| r['name'] }).to contain_exactly('Rule 1', 'Rule 2')
expect(json_response.map { |r| r['external_url'] }).to contain_exactly('http://rule1.example', 'https://rule2.example')
end
it 'paginates correctly' do
get api(collection_url, project.owner), params: { per_page: 1 }
expect_paginated_array_response([1])
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end
with_them do
before do
stub_feature_flags(ff_compliance_approval_gates: flag)
stub_licensed_features(compliance_approval_gates: licensed)
end
it 'returns the correct status code' do
get api(collection_url, (project_owner ? project.owner : build(:user)))
expect(response).to have_gitlab_http_status(status)
end
end
end
end
describe 'POST projects/:id/external_approval_rules' do
context 'successfully creating new external approval rule' do
before do
stub_feature_flags(ff_compliance_approval_gates: true)
stub_licensed_features(compliance_approval_gates: true)
end
subject do
post api("/projects/#{project.id}/external_approval_rules", project.owner), params: attributes_for(:external_approval_rule)
end
it 'creates a new external approval rule' do
expect { subject }.to change { ApprovalRules::ExternalApprovalRule.count }.by(1)
end
context 'with protected branches' do
let_it_be(:protected_branch) { create(:protected_branch, project: project) }
let(:params) do
{ name: 'New rule', external_url: 'https://gitlab.com/test/example.json', protected_branch_ids: protected_branch.id }
end
subject do
post api("/projects/#{project.id}/external_approval_rules", project.owner), params: params
end
it 'returns expected status code' do
subject
expect(response).to have_gitlab_http_status(:created)
end
it 'creates protected branch records' do
subject
expect(ApprovalRules::ExternalApprovalRule.last.protected_branches.count).to eq 1
end
it 'responds with expected JSON' do
subject
expect(json_response['id']).not_to be_nil
expect(json_response['name']).to eq('New rule')
expect(json_response['external_url']).to eq('https://gitlab.com/test/example.json')
expect(json_response['protected_branches'].size).to eq(1)
end
end
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :created
end
with_them do
before do
stub_feature_flags(ff_compliance_approval_gates: flag)
stub_licensed_features(compliance_approval_gates: licensed)
end
it 'returns the correct status code' do
post api("/projects/#{project.id}/external_approval_rules", (project_owner ? project.owner : build(:user))), params: attributes_for(:external_approval_rule)
expect(response).to have_gitlab_http_status(status)
end
end
end
end
describe 'PUT projects/:id/external_approval_rules/:rule_id' do
let(:params) { { external_url: 'http://newvalue.com', name: 'new name' } }
context 'successfully updating external approval rule' do
before do
stub_feature_flags(ff_compliance_approval_gates: true)
stub_licensed_features(compliance_approval_gates: true)
end
subject do
put api(single_object_url, project.owner), params: params
end
it 'updates an approval rule' do
expect { subject }.to change { rule.reload.external_url }.to eq('http://newvalue.com')
end
it 'responds with correct http status' do
subject
expect(response).to have_gitlab_http_status(:success)
end
context 'with protected branches' do
let_it_be(:protected_branch) { create(:protected_branch, project: project) }
let(:params) do
{ name: 'New rule', external_url: 'https://gitlab.com/test/example.json', protected_branch_ids: protected_branch.id }
end
subject do
put api(single_object_url, project.owner), params: params
end
it 'returns expected status code' do
subject
expect(response).to have_gitlab_http_status(:success)
end
it 'creates protected branch records' do
expect { subject }.to change { ApprovalRules::ExternalApprovalRule.last.protected_branches.count }.by(1)
end
it 'responds with expected JSON', :aggregate_failures do
subject
expect(json_response['id']).not_to be_nil
expect(json_response['name']).to eq('New rule')
expect(json_response['external_url']).to eq('https://gitlab.com/test/example.json')
expect(json_response['protected_branches'].size).to eq(1)
end
end
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end
with_them do
before do
stub_feature_flags(ff_compliance_approval_gates: flag)
stub_licensed_features(compliance_approval_gates: licensed)
end
it 'returns the correct status code' do
put api(single_object_url, (project_owner ? project.owner : build(:user))), params: attributes_for(:external_approval_rule)
expect(response).to have_gitlab_http_status(status)
end
end
end
end
end
This diff is collapsed.
......@@ -19,6 +19,16 @@ RSpec.describe MergeRequests::BaseService do
subject { MergeRequests::CreateService.new(project: project, current_user: project.owner, params: params) }
context 'project has external status checks' do
let_it_be(:status_checks) { create_list(:external_status_check, 3, project: project) }
it 'fires the correct number of compliance hooks' do
expect(project).to receive(:execute_external_compliance_hooks).once.and_call_original
subject.execute
end
end
describe '#filter_params' do
let(:params_filtering_service) { double(:params_filtering_service) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ExternalApprovalRules::CreateService do
RSpec.describe ExternalStatusChecks::CreateService do
let_it_be(:project) { create(:project) }
let_it_be(:protected_branch) { create(:protected_branch, project: project) }
let(:user) { project.owner }
......@@ -24,7 +24,7 @@ RSpec.describe ExternalApprovalRules::CreateService do
end
it 'does not create a new rule' do
expect { subject }.not_to change { ApprovalRules::ExternalApprovalRule.count }
expect { subject }.not_to change { MergeRequests::ExternalStatusCheck.count }
end
end
......@@ -36,7 +36,7 @@ RSpec.describe ExternalApprovalRules::CreateService do
end
it 'does not create a new rule' do
expect { subject }.not_to change { ApprovalRules::ExternalApprovalRule.count }
expect { subject }.not_to change { MergeRequests::ExternalStatusCheck.count }
end
it 'responds with the expected errors' do
......@@ -48,7 +48,7 @@ RSpec.describe ExternalApprovalRules::CreateService do
context 'successfully creating approval rule' do
it 'creates a new ExternalApprovalRule' do
expect { subject }.to change { ApprovalRules::ExternalApprovalRule.count }.by(1)
expect { subject }.to change { MergeRequests::ExternalStatusCheck.count }.by(1)
end
it 'is successful' do
......@@ -58,7 +58,7 @@ RSpec.describe ExternalApprovalRules::CreateService do
it 'includes the newly created rule in its payload' do
rule = subject.payload[:rule]
expect(rule).to be_a(ApprovalRules::ExternalApprovalRule)
expect(rule).to be_a(MergeRequests::ExternalStatusCheck)
expect(rule.project).to eq(project)
expect(rule.external_url).to eq('https://external_url.text/hello.json')
expect(rule.name).to eq 'Test'
......
......@@ -2,16 +2,16 @@
require 'spec_helper'
RSpec.describe ExternalApprovalRules::DestroyService do
RSpec.describe ExternalStatusChecks::DestroyService do
let_it_be(:project) { create(:project) }
let_it_be(:rule) { create(:external_approval_rule, project: project) }
let_it_be(:rule) { create(:external_status_check, project: project) }
let(:current_user) { project.owner }
subject { described_class.new(container: project, current_user: current_user).execute(rule) }
context 'when current user is project owner' do
it 'deletes an approval rule' do
expect { subject }.to change { ApprovalRules::ExternalApprovalRule.count }.by(-1)
expect { subject }.to change { MergeRequests::ExternalStatusCheck.count }.by(-1)
end
it 'is successful' do
......@@ -23,7 +23,7 @@ RSpec.describe ExternalApprovalRules::DestroyService do
let_it_be(:current_user) { create(:user) }
it 'does not delete an approval rule' do
expect { subject }.not_to change { ApprovalRules::ExternalApprovalRule.count }
expect { subject }.not_to change { MergeRequests::ExternalStatusCheck.count }
end
it 'is unsuccessful' do
......
......@@ -2,8 +2,8 @@
require 'spec_helper'
RSpec.describe ExternalApprovalRules::DispatchService do
let_it_be(:rule) { build_stubbed(:external_approval_rule, external_url: 'https://test.example.com/callback') }
RSpec.describe ExternalStatusChecks::DispatchService do
let_it_be(:rule) { build_stubbed(:external_status_check, external_url: 'https://test.example.com/callback') }
subject { described_class.new(rule, {}).execute }
......
......@@ -2,12 +2,12 @@
require 'spec_helper'
RSpec.describe ExternalApprovalRules::UpdateService do
RSpec.describe ExternalStatusChecks::UpdateService do
let_it_be(:project) { create(:project) }
let_it_be(:rule) { create(:external_approval_rule, project: project) }
let_it_be(:check) { create(:external_status_check, project: project) }
let_it_be(:protected_branch) { create(:protected_branch, project: project) }
let(:current_user) { project.owner }
let(:params) { { id: project.id, rule_id: rule.id, external_url: 'http://newvalue.com', name: 'new name', protected_branch_ids: [protected_branch.id] } }
let(:params) { { id: project.id, check_id: check.id, external_url: 'http://newvalue.com', name: 'new name', protected_branch_ids: [protected_branch.id] } }
subject { described_class.new(container: project, current_user: current_user, params: params).execute }
......@@ -15,11 +15,11 @@ RSpec.describe ExternalApprovalRules::UpdateService do
it 'updates an approval rule' do
subject
rule.reload
check.reload
expect(rule.external_url).to eq('http://newvalue.com')
expect(rule.name).to eq('new name')
expect(rule.protected_branches).to contain_exactly(protected_branch)
expect(check.external_url).to eq('http://newvalue.com')
expect(check.name).to eq('new name')
expect(check.protected_branches).to contain_exactly(protected_branch)
end
it 'is successful' do
......@@ -31,7 +31,7 @@ RSpec.describe ExternalApprovalRules::UpdateService do
let_it_be(:current_user) { create(:user) }
it 'does not change an approval rule' do
expect { subject }.not_to change { rule.name }
expect { subject }.not_to change { check.name }
end
it 'is unsuccessful' do
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ApprovalRules::ExternalApprovalRulePayloadWorker do
let_it_be(:rule) { create(:external_approval_rule, external_url: 'https://example.com/callback') }
let_it_be(:rule) { create(:external_status_check, external_url: 'https://example.com/callback') }
subject { described_class.new.perform(rule.id, {}) }
......
......@@ -74,6 +74,7 @@ RSpec.describe 'Database schema' do
slack_integrations: %w[team_id user_id],
snippets: %w[author_id],
spam_logs: %w[user_id],
status_check_responses: %w[external_approval_rule_id],
subscriptions: %w[user_id subscribable_id],
suggestions: %w[commit_id],
taggings: %w[tag_id taggable_id tagger_id],
......
......@@ -342,7 +342,7 @@ container_repositories:
- project
- name
project:
- external_approval_rules
- external_status_checks
- taggings
- base_tags
- topic_taggings
......
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