Commit db64b768 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 27704ed5 c1e16db6
/* global DocumentTouch */
import sortableConfig from 'ee_else_ce/sortable/sortable_config';
import sortableConfig from '~/sortable/sortable_config';
export function sortableStart() {
document.body.classList.add('is-dragging');
......
......@@ -550,8 +550,8 @@ export default {
if (id.startsWith('#note_')) {
const noteId = id.replace('#note_', '');
const discussion = this.$store.state.notes.discussions.find((d) =>
d.notes.find((n) => n.id === noteId),
const discussion = this.$store.state.notes.discussions.find(
(d) => d.diff_file && d.notes.find((n) => n.id === noteId),
);
if (discussion) {
......
......@@ -99,7 +99,7 @@ export default {
v-gl-tooltip.hover
variant="default"
icon="file-tree"
class="gl-mr-3 js-toggle-tree-list"
class="gl-mr-3 js-toggle-tree-list btn-icon"
:title="toggleFileBrowserTitle"
:aria-label="toggleFileBrowserTitle"
:selected="showTreeList"
......@@ -109,7 +109,7 @@ export default {
{{ __('Viewing commit') }}
<gl-link :href="commit.commit_url" class="monospace">{{ commit.short_id }}</gl-link>
</div>
<div v-if="hasNeighborCommits" class="commit-nav-buttons ml-3">
<div v-if="hasNeighborCommits" class="commit-nav-buttons">
<gl-button-group>
<gl-button
:href="previousCommitUrl"
......@@ -160,20 +160,21 @@ export default {
/>
</template>
</gl-sprintf>
<gl-button
v-if="commit || startVersion"
:href="latestVersionPath"
variant="default"
class="js-latest-version"
:class="{ 'gl-ml-3': commit && !hasNeighborCommits }"
>
{{ __('Show latest version') }}
</gl-button>
<div v-if="hasChanges" class="inline-parallel-buttons d-none d-md-flex ml-auto">
<diff-stats
:diff-files-count-text="diffFilesCountText"
:added-lines="addedLines"
:removed-lines="removedLines"
/>
<gl-button
v-if="commit || startVersion"
:href="latestVersionPath"
variant="default"
class="gl-mr-3 js-latest-version"
>
{{ __('Show latest version') }}
</gl-button>
<gl-button
v-show="whichCollapsedTypes.any"
variant="default"
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import Sortable from 'sortablejs';
import sortableConfig from 'ee_else_ce/sortable/sortable_config';
import sortableConfig from '~/sortable/sortable_config';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
export default {
......
......@@ -183,6 +183,8 @@
}
.commit-nav-buttons {
margin: 0 0.5rem;
a.btn,
button {
// See: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/730
......
......@@ -706,7 +706,7 @@ $tabs-holder-z-index: 250;
.mr-version-dropdown,
.mr-version-compare-dropdown {
margin: 0 7px;
margin: 0 0.5rem;
}
.dropdown-title {
......@@ -715,7 +715,7 @@ $tabs-holder-z-index: 250;
// Shortening button height by 1px to make compare-versions
// header 56px and fit into our 8px design grid
button {
.btn {
height: 34px;
}
......
......@@ -19,7 +19,7 @@ module HasIntegrations
def without_integration(integration)
integrations = Integration
.select('1')
.where('services.project_id = projects.id')
.where("#{Integration.table_name}.project_id = projects.id")
.where(type: integration.type)
Project
......
......@@ -167,7 +167,7 @@ class Group < Namespace
def without_integration(integration)
integrations = Integration
.select('1')
.where('services.group_id = namespaces.id')
.where("#{Integration.table_name}.group_id = namespaces.id")
.where(type: integration.type)
where('NOT EXISTS (?)', integrations)
......
......@@ -10,9 +10,6 @@ class Integration < ApplicationRecord
include FromUnion
include EachBatch
# TODO Rename the table: https://gitlab.com/gitlab-org/gitlab/-/issues/201856
self.table_name = 'services'
INTEGRATION_NAMES = %w[
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat irker jira
......@@ -299,7 +296,7 @@ class Integration < ApplicationRecord
array = group_ids.to_sql.present? ? "array(#{group_ids.to_sql})" : 'ARRAY[]'
where(type: type, group_id: group_ids, inherit_from_id: nil)
.order(Arel.sql("array_position(#{array}::bigint[], services.group_id)"))
.order(Arel.sql("array_position(#{array}::bigint[], #{table_name}.group_id)"))
.first
end
private_class_method :closest_group_integration
......@@ -317,7 +314,7 @@ class Integration < ApplicationRecord
with_templates ? active.where(template: true) : none,
active.where(instance: true),
active.where(group_id: group_ids, inherit_from_id: nil)
]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], services.group_id), instance DESC")).group_by(&:type).each do |type, records|
]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], #{table_name}.group_id), instance DESC")).group_by(&:type).each do |type, records|
build_from_integration(records.first, association => scope.id).save
end
end
......
# frozen_string_literal: true
class RenameServicesToIntegrations < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::SchemaHelpers
# Function and trigger names match those migrated in:
# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49916
# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51852
WIKI_FUNCTION_NAME = 'set_has_external_wiki'
TRACKER_FUNCTION_NAME = 'set_has_external_issue_tracker'
WIKI_TRIGGER_ON_INSERT_NAME = 'trigger_has_external_wiki_on_insert'
WIKI_TRIGGER_ON_UPDATE_NAME = 'trigger_has_external_wiki_on_update'
WIKI_TRIGGER_ON_DELETE_NAME = 'trigger_has_external_wiki_on_delete'
TRACKER_TRIGGER_ON_INSERT_NAME = 'trigger_has_external_issue_tracker_on_insert'
TRACKER_TRIGGER_ON_UPDATE_NAME = 'trigger_has_external_issue_tracker_on_update'
TRACKER_TRIGGER_ON_DELETE_NAME = 'trigger_has_external_issue_tracker_on_delete'
ALL_TRIGGERS = [
WIKI_TRIGGER_ON_INSERT_NAME,
WIKI_TRIGGER_ON_UPDATE_NAME,
WIKI_TRIGGER_ON_DELETE_NAME,
TRACKER_TRIGGER_ON_INSERT_NAME,
TRACKER_TRIGGER_ON_UPDATE_NAME,
TRACKER_TRIGGER_ON_DELETE_NAME
].freeze
def up
execute('LOCK services IN ACCESS EXCLUSIVE MODE')
drop_all_triggers(:services)
rename_table_safely(:services, :integrations)
recreate_all_triggers(:integrations)
end
def down
execute('LOCK integrations IN ACCESS EXCLUSIVE MODE')
drop_all_triggers(:integrations)
undo_rename_table_safely(:services, :integrations)
recreate_all_triggers(:services)
end
private
def drop_all_triggers(table_name)
ALL_TRIGGERS.each do |trigger_name|
drop_trigger(table_name, trigger_name)
end
end
def recreate_all_triggers(table_name)
wiki_create_insert_trigger(table_name)
wiki_create_update_trigger(table_name)
wiki_create_delete_trigger(table_name)
tracker_replace_trigger_function(table_name)
tracker_create_insert_trigger(table_name)
tracker_create_update_trigger(table_name)
tracker_create_delete_trigger(table_name)
end
def wiki_create_insert_trigger(table_name)
execute(<<~SQL)
CREATE TRIGGER #{WIKI_TRIGGER_ON_INSERT_NAME}
AFTER INSERT ON #{table_name}
FOR EACH ROW
WHEN (NEW.active = TRUE AND NEW.type = 'ExternalWikiService' AND NEW.project_id IS NOT NULL)
EXECUTE FUNCTION #{WIKI_FUNCTION_NAME}();
SQL
end
def wiki_create_update_trigger(table_name)
execute(<<~SQL)
CREATE TRIGGER #{WIKI_TRIGGER_ON_UPDATE_NAME}
AFTER UPDATE ON #{table_name}
FOR EACH ROW
WHEN (NEW.type = 'ExternalWikiService' AND OLD.active != NEW.active AND NEW.project_id IS NOT NULL)
EXECUTE FUNCTION #{WIKI_FUNCTION_NAME}();
SQL
end
def wiki_create_delete_trigger(table_name)
execute(<<~SQL)
CREATE TRIGGER #{WIKI_TRIGGER_ON_DELETE_NAME}
AFTER DELETE ON #{table_name}
FOR EACH ROW
WHEN (OLD.type = 'ExternalWikiService' AND OLD.project_id IS NOT NULL)
EXECUTE FUNCTION #{WIKI_FUNCTION_NAME}();
SQL
end
# Using `replace: true` to rewrite the existing function
def tracker_replace_trigger_function(table_name)
create_trigger_function(TRACKER_FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE projects SET has_external_issue_tracker = (
EXISTS
(
SELECT 1
FROM #{table_name}
WHERE project_id = COALESCE(NEW.project_id, OLD.project_id)
AND active = TRUE
AND category = 'issue_tracker'
)
)
WHERE projects.id = COALESCE(NEW.project_id, OLD.project_id);
RETURN NULL;
SQL
end
end
def tracker_create_insert_trigger(table_name)
execute(<<~SQL)
CREATE TRIGGER #{TRACKER_TRIGGER_ON_INSERT_NAME}
AFTER INSERT ON #{table_name}
FOR EACH ROW
WHEN (NEW.category = 'issue_tracker' AND NEW.active = TRUE AND NEW.project_id IS NOT NULL)
EXECUTE FUNCTION #{TRACKER_FUNCTION_NAME}();
SQL
end
def tracker_create_update_trigger(table_name)
execute(<<~SQL)
CREATE TRIGGER #{TRACKER_TRIGGER_ON_UPDATE_NAME}
AFTER UPDATE ON #{table_name}
FOR EACH ROW
WHEN (NEW.category = 'issue_tracker' AND OLD.active != NEW.active AND NEW.project_id IS NOT NULL)
EXECUTE FUNCTION #{TRACKER_FUNCTION_NAME}();
SQL
end
def tracker_create_delete_trigger(table_name)
execute(<<~SQL)
CREATE TRIGGER #{TRACKER_TRIGGER_ON_DELETE_NAME}
AFTER DELETE ON #{table_name}
FOR EACH ROW
WHEN (OLD.category = 'issue_tracker' AND OLD.active = TRUE AND OLD.project_id IS NOT NULL)
EXECUTE FUNCTION #{TRACKER_FUNCTION_NAME}();
SQL
end
end
# frozen_string_literal: true
class RenameServicesIndexesToIntegrations < ActiveRecord::Migration[6.1]
INDEXES = %w(
project_and_type_where_inherit_null
project_id_and_type_unique
template
type
type_and_instance_partial
type_and_template_partial
type_id_when_active_and_project_id_not_null
unique_group_id_and_type
).freeze
def up
INDEXES.each do |index|
execute(<<~SQL)
ALTER INDEX IF EXISTS "index_services_on_#{index}" RENAME TO "index_integrations_on_#{index}"
SQL
end
end
def down
INDEXES.each do |index|
execute(<<~SQL)
ALTER INDEX IF EXISTS "index_integrations_on_#{index}" RENAME TO "index_services_on_#{index}"
SQL
end
end
end
# frozen_string_literal: true
class StealBackgroundJobsThatReferenceServices < ActiveRecord::Migration[6.1]
def up
Gitlab::BackgroundMigration.steal('BackfillJiraTrackerDeploymentType2')
Gitlab::BackgroundMigration.steal('FixProjectsWithoutPrometheusService')
Gitlab::BackgroundMigration.steal('MigrateIssueTrackersSensitiveData')
Gitlab::BackgroundMigration.steal('RemoveDuplicateServices')
end
def down
# no-op
end
end
# frozen_string_literal: true
class FinalizeRenameServicesToIntegrations < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
def up
finalize_table_rename(:services, :integrations)
end
def down
undo_finalize_table_rename(:services, :integrations)
end
end
cfe35a1297c4a92c4b5e62757ed74c11ffd6f207777291c11b05a4e3cee91618
\ No newline at end of file
64babbed04b9e3bf59bb723b43e3c30730527f0e0e09906073b5bd9379067ab6
\ No newline at end of file
07d0de05b6a59ba0d1f464ae488f5ead812bc643984ac3dc662c78a02a978f7f
\ No newline at end of file
eeee178019c259a6fff85219490abf62f2694227cc2facf454d93e57c373833b
\ No newline at end of file
......@@ -18,7 +18,7 @@ UPDATE projects SET has_external_issue_tracker = (
EXISTS
(
SELECT 1
FROM services
FROM integrations
WHERE project_id = COALESCE(NEW.project_id, OLD.project_id)
AND active = TRUE
AND category = 'issue_tracker'
......@@ -14010,6 +14010,45 @@ CREATE SEQUENCE insights_id_seq
ALTER SEQUENCE insights_id_seq OWNED BY insights.id;
CREATE TABLE integrations (
id integer NOT NULL,
type character varying,
project_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
active boolean DEFAULT false NOT NULL,
properties text,
push_events boolean DEFAULT true,
issues_events boolean DEFAULT true,
merge_requests_events boolean DEFAULT true,
tag_push_events boolean DEFAULT true,
note_events boolean DEFAULT true NOT NULL,
category character varying DEFAULT 'common'::character varying NOT NULL,
wiki_page_events boolean DEFAULT true,
pipeline_events boolean DEFAULT false NOT NULL,
confidential_issues_events boolean DEFAULT true NOT NULL,
commit_events boolean DEFAULT true NOT NULL,
job_events boolean DEFAULT false NOT NULL,
confidential_note_events boolean DEFAULT true,
deployment_events boolean DEFAULT false NOT NULL,
comment_on_event_enabled boolean DEFAULT true NOT NULL,
template boolean DEFAULT false,
instance boolean DEFAULT false NOT NULL,
comment_detail smallint,
inherit_from_id bigint,
alert_events boolean,
group_id bigint
);
CREATE SEQUENCE integrations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE integrations_id_seq OWNED BY integrations.id;
CREATE TABLE internal_ids (
id bigint NOT NULL,
project_id integer,
......@@ -17980,45 +18019,6 @@ CREATE TABLE service_desk_settings (
project_key character varying(255)
);
CREATE TABLE services (
id integer NOT NULL,
type character varying,
project_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
active boolean DEFAULT false NOT NULL,
properties text,
push_events boolean DEFAULT true,
issues_events boolean DEFAULT true,
merge_requests_events boolean DEFAULT true,
tag_push_events boolean DEFAULT true,
note_events boolean DEFAULT true NOT NULL,
category character varying DEFAULT 'common'::character varying NOT NULL,
wiki_page_events boolean DEFAULT true,
pipeline_events boolean DEFAULT false NOT NULL,
confidential_issues_events boolean DEFAULT true NOT NULL,
commit_events boolean DEFAULT true NOT NULL,
job_events boolean DEFAULT false NOT NULL,
confidential_note_events boolean DEFAULT true,
deployment_events boolean DEFAULT false NOT NULL,
comment_on_event_enabled boolean DEFAULT true NOT NULL,
template boolean DEFAULT false,
instance boolean DEFAULT false NOT NULL,
comment_detail smallint,
inherit_from_id bigint,
alert_events boolean,
group_id bigint
);
CREATE SEQUENCE services_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE services_id_seq OWNED BY services.id;
CREATE TABLE shards (
id integer NOT NULL,
name character varying NOT NULL
......@@ -20094,6 +20094,8 @@ ALTER TABLE ONLY index_statuses ALTER COLUMN id SET DEFAULT nextval('index_statu
ALTER TABLE ONLY insights ALTER COLUMN id SET DEFAULT nextval('insights_id_seq'::regclass);
ALTER TABLE ONLY integrations ALTER COLUMN id SET DEFAULT nextval('integrations_id_seq'::regclass);
ALTER TABLE ONLY internal_ids ALTER COLUMN id SET DEFAULT nextval('internal_ids_id_seq'::regclass);
ALTER TABLE ONLY ip_restrictions ALTER COLUMN id SET DEFAULT nextval('ip_restrictions_id_seq'::regclass);
......@@ -20406,8 +20408,6 @@ ALTER TABLE ONLY sent_notifications ALTER COLUMN id SET DEFAULT nextval('sent_no
ALTER TABLE ONLY sentry_issues ALTER COLUMN id SET DEFAULT nextval('sentry_issues_id_seq'::regclass);
ALTER TABLE ONLY services ALTER COLUMN id SET DEFAULT nextval('services_id_seq'::regclass);
ALTER TABLE ONLY shards ALTER COLUMN id SET DEFAULT nextval('shards_id_seq'::regclass);
ALTER TABLE ONLY slack_integrations ALTER COLUMN id SET DEFAULT nextval('slack_integrations_id_seq'::regclass);
......@@ -21483,6 +21483,9 @@ ALTER TABLE ONLY index_statuses
ALTER TABLE ONLY insights
ADD CONSTRAINT insights_pkey PRIMARY KEY (id);
ALTER TABLE ONLY integrations
ADD CONSTRAINT integrations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY internal_ids
ADD CONSTRAINT internal_ids_pkey PRIMARY KEY (id);
......@@ -22050,9 +22053,6 @@ ALTER TABLE ONLY serverless_domain_cluster
ALTER TABLE ONLY service_desk_settings
ADD CONSTRAINT service_desk_settings_pkey PRIMARY KEY (project_id);
ALTER TABLE ONLY services
ADD CONSTRAINT services_pkey PRIMARY KEY (id);
ALTER TABLE ONLY shards
ADD CONSTRAINT shards_pkey PRIMARY KEY (id);
......@@ -23701,6 +23701,24 @@ CREATE INDEX index_insights_on_namespace_id ON insights USING btree (namespace_i
CREATE INDEX index_insights_on_project_id ON insights USING btree (project_id);
CREATE INDEX index_integrations_on_inherit_from_id ON integrations USING btree (inherit_from_id);
CREATE INDEX index_integrations_on_project_and_type_where_inherit_null ON integrations USING btree (project_id, type) WHERE (inherit_from_id IS NULL);
CREATE UNIQUE INDEX index_integrations_on_project_id_and_type_unique ON integrations USING btree (project_id, type);
CREATE INDEX index_integrations_on_template ON integrations USING btree (template);
CREATE INDEX index_integrations_on_type ON integrations USING btree (type);
CREATE UNIQUE INDEX index_integrations_on_type_and_instance_partial ON integrations USING btree (type, instance) WHERE (instance = true);
CREATE UNIQUE INDEX index_integrations_on_type_and_template_partial ON integrations USING btree (type, template) WHERE (template = true);
CREATE INDEX index_integrations_on_type_id_when_active_and_project_id_not_nu ON integrations USING btree (type, id) WHERE ((active = true) AND (project_id IS NOT NULL));
CREATE UNIQUE INDEX index_integrations_on_unique_group_id_and_type ON integrations USING btree (group_id, type);
CREATE INDEX index_internal_ids_on_namespace_id ON internal_ids USING btree (namespace_id);
CREATE INDEX index_internal_ids_on_project_id ON internal_ids USING btree (project_id);
......@@ -24741,24 +24759,6 @@ CREATE INDEX index_serverless_domain_cluster_on_pages_domain_id ON serverless_do
CREATE INDEX index_service_desk_enabled_projects_on_id_creator_id_created_at ON projects USING btree (id, creator_id, created_at) WHERE (service_desk_enabled = true);
CREATE INDEX index_services_on_inherit_from_id ON services USING btree (inherit_from_id);
CREATE INDEX index_services_on_project_and_type_where_inherit_null ON services USING btree (project_id, type) WHERE (inherit_from_id IS NULL);
CREATE UNIQUE INDEX index_services_on_project_id_and_type_unique ON services USING btree (project_id, type);
CREATE INDEX index_services_on_template ON services USING btree (template);
CREATE INDEX index_services_on_type ON services USING btree (type);
CREATE UNIQUE INDEX index_services_on_type_and_instance_partial ON services USING btree (type, instance) WHERE (instance = true);
CREATE UNIQUE INDEX index_services_on_type_and_template_partial ON services USING btree (type, template) WHERE (template = true);
CREATE INDEX index_services_on_type_id_when_active_and_project_id_not_null ON services USING btree (type, id) WHERE ((active = true) AND (project_id IS NOT NULL));
CREATE UNIQUE INDEX index_services_on_unique_group_id_and_type ON services USING btree (group_id, type);
CREATE UNIQUE INDEX index_shards_on_name ON shards USING btree (name);
CREATE UNIQUE INDEX index_site_profile_secret_variables_on_site_profile_id_and_key ON dast_site_profile_secret_variables USING btree (dast_site_profile_id, key);
......@@ -25579,20 +25579,20 @@ CREATE TRIGGER trigger_cf2f9e35f002 BEFORE INSERT OR UPDATE ON ci_build_trace_ch
CREATE TRIGGER trigger_f1ca8ec18d78 BEFORE INSERT OR UPDATE ON geo_job_artifact_deleted_events FOR EACH ROW EXECUTE FUNCTION trigger_f1ca8ec18d78();
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON integrations FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
CREATE TRIGGER trigger_has_external_issue_tracker_on_insert AFTER INSERT ON services FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (new.active = true) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
CREATE TRIGGER trigger_has_external_issue_tracker_on_insert AFTER INSERT ON integrations FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (new.active = true) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
CREATE TRIGGER trigger_has_external_issue_tracker_on_update AFTER UPDATE ON services FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
CREATE TRIGGER trigger_has_external_issue_tracker_on_update AFTER UPDATE ON integrations FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
CREATE TRIGGER trigger_has_external_wiki_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.type)::text = 'ExternalWikiService'::text) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_has_external_wiki_on_delete AFTER DELETE ON integrations FOR EACH ROW WHEN ((((old.type)::text = 'ExternalWikiService'::text) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_has_external_wiki_on_insert AFTER INSERT ON services FOR EACH ROW WHEN (((new.active = true) AND ((new.type)::text = 'ExternalWikiService'::text) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_has_external_wiki_on_insert AFTER INSERT ON integrations FOR EACH ROW WHEN (((new.active = true) AND ((new.type)::text = 'ExternalWikiService'::text) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_has_external_wiki_on_update AFTER UPDATE ON services FOR EACH ROW WHEN ((((new.type)::text = 'ExternalWikiService'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_has_external_wiki_on_update AFTER UPDATE ON integrations FOR EACH ROW WHEN ((((new.type)::text = 'ExternalWikiService'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
ALTER TABLE ONLY chat_names
ADD CONSTRAINT fk_00797a2bf9 FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_00797a2bf9 FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY deployments
ADD CONSTRAINT fk_009fd21147 FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE NOT VALID;
......@@ -25870,7 +25870,7 @@ ALTER TABLE ONLY terraform_state_versions
ALTER TABLE ONLY protected_branch_push_access_levels
ADD CONSTRAINT fk_7111b68cdb FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY services
ALTER TABLE ONLY integrations
ADD CONSTRAINT fk_71cce407f9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_interacted_projects
......@@ -26216,7 +26216,7 @@ ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_d3130c9a7f FOREIGN KEY (commit_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY web_hooks
ADD CONSTRAINT fk_d47999a98a FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_d47999a98a FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_sources_pipelines
ADD CONSTRAINT fk_d4e29af7d7 FOREIGN KEY (source_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
......@@ -26299,7 +26299,7 @@ ALTER TABLE ONLY vulnerability_statistics
ALTER TABLE ONLY ci_triggers
ADD CONSTRAINT fk_e8e10d1964 FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY services
ALTER TABLE ONLY integrations
ADD CONSTRAINT fk_e8fe908a34 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY pages_domains
......@@ -26543,7 +26543,7 @@ ALTER TABLE ONLY group_wiki_repositories
ADD CONSTRAINT fk_rails_19755e374b FOREIGN KEY (shard_id) REFERENCES shards(id) ON DELETE RESTRICT;
ALTER TABLE ONLY open_project_tracker_data
ADD CONSTRAINT fk_rails_1987546e48 FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_rails_1987546e48 FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY gpg_signatures
ADD CONSTRAINT fk_rails_19d4f1c6f9 FOREIGN KEY (gpg_key_subkey_id) REFERENCES gpg_key_subkeys(id) ON DELETE SET NULL;
......@@ -27125,7 +27125,7 @@ ALTER TABLE ONLY vulnerability_finding_evidence_requests
ADD CONSTRAINT fk_rails_72c87c8eb6 FOREIGN KEY (vulnerability_finding_evidence_id) REFERENCES vulnerability_finding_evidences(id) ON DELETE CASCADE;
ALTER TABLE ONLY slack_integrations
ADD CONSTRAINT fk_rails_73db19721a FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_rails_73db19721a FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY custom_emoji
ADD CONSTRAINT fk_rails_745925b412 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
......@@ -27392,7 +27392,7 @@ ALTER TABLE ONLY todos
ADD CONSTRAINT fk_rails_a27c483435 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY jira_tracker_data
ADD CONSTRAINT fk_rails_a299066916 FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_rails_a299066916 FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY protected_environments
ADD CONSTRAINT fk_rails_a354313d11 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -27647,7 +27647,7 @@ ALTER TABLE ONLY operations_strategies_user_lists
ADD CONSTRAINT fk_rails_ccb7e4bc0b FOREIGN KEY (user_list_id) REFERENCES operations_user_lists(id) ON DELETE CASCADE;
ALTER TABLE ONLY issue_tracker_data
ADD CONSTRAINT fk_rails_ccc0840427 FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ADD CONSTRAINT fk_rails_ccc0840427 FOREIGN KEY (service_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_finding_evidence_headers
ADD CONSTRAINT fk_rails_ce7f121a03 FOREIGN KEY (vulnerability_finding_evidence_request_id) REFERENCES vulnerability_finding_evidence_requests(id) ON DELETE CASCADE;
......@@ -27949,8 +27949,8 @@ ALTER TABLE ONLY resource_label_events
ALTER TABLE ONLY ci_builds_metadata
ADD CONSTRAINT fk_rails_ffcf702a02 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY services
ADD CONSTRAINT fk_services_inherit_from_id FOREIGN KEY (inherit_from_id) REFERENCES services(id) ON DELETE CASCADE;
ALTER TABLE ONLY integrations
ADD CONSTRAINT fk_services_inherit_from_id FOREIGN KEY (inherit_from_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_source_project FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE SET NULL;
......@@ -191,6 +191,10 @@ changes.
You can always change the **Security policy project** by navigating to your project's
**Security & Compliance > Policies** and modifying the selected project.
NOTE:
Only project Owners have the [permissions](../../permissions.md#project-members-permissions)
to select Security Policy Project.
## Roadmap
See the [Category Direction page](https://about.gitlab.com/direction/protect/container_network_security/)
......
......@@ -168,6 +168,8 @@ The following table lists project permissions available for each role:
| Manage Project Operations | | | | ✓ | ✓ |
| Manage Terraform state | | | | ✓ | ✓ |
| Manage license policy **(ULTIMATE)** | | | | ✓ | ✓ |
| Manage security policy **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Create or assign security policy project **(ULTIMATE)** | | | | | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ |
| Reposition comments on images (posted by any user)|✓ (*10*) | ✓ (*10*) | ✓ (*10*) | ✓ | ✓ |
| Manage Error Tracking | | | | ✓ | ✓ |
......
/* global DocumentTouch */
import sortableConfig from 'ee_else_ce/sortable/sortable_config';
import sortableConfig from '~/sortable/sortable_config';
import { NO_DRAG_CLASS } from '../constants';
export default () => {
......
export default {
animation: 200,
forceFallback: true,
fallbackClass: 'is-dragging',
fallbackOnBody: true,
ghostClass: 'is-ghost',
fallbackTolerance: 1,
};
......@@ -5,9 +5,12 @@ module Projects
class PoliciesController < Projects::ApplicationController
include SecurityAndCompliancePermissions
before_action :authorize_security_orchestration_policies!
before_action :authorize_update_security_orchestration_policy_project!, only: [:assign]
before_action do
push_frontend_feature_flag(:security_orchestration_policies_configuration, project)
check_permissions!
check_feature_flag!
end
feature_category :security_orchestration
......@@ -32,8 +35,8 @@ module Projects
private
def check_permissions!
render_404 unless Feature.enabled?(:security_orchestration_policies_configuration, project) && can?(current_user, :security_orchestration_policies, project)
def check_feature_flag!
render_404 if Feature.disabled?(:security_orchestration_policies_configuration, project)
end
def policy_project_params
......
......@@ -7,7 +7,7 @@ module Mutations
graphql_name 'SecurityPolicyProjectAssign'
authorize :security_orchestration_policies
authorize :update_security_orchestration_policy_project
argument :project_path, GraphQL::ID_TYPE,
required: true,
......
......@@ -7,7 +7,7 @@ module Mutations
graphql_name 'SecurityPolicyProjectCreate'
authorize :security_orchestration_policies
authorize :update_security_orchestration_policy_project
argument :project_path, GraphQL::ID_TYPE,
required: true,
......
......@@ -195,6 +195,10 @@ module EE
end
end
def can_update_security_orchestration_policy_project?(project)
can?(current_user, :update_security_orchestration_policy_project, project)
end
def can_create_feedback?(project, feedback_type)
feedback = Vulnerabilities::Feedback.new(project: project, feedback_type: feedback_type)
can?(current_user, :create_vulnerability_feedback, feedback)
......
......@@ -37,11 +37,12 @@ module EE
@project&.namespace || @group
end
def license_message(signed_in: signed_in?, is_admin: current_user&.admin?, license: License.current)
def license_message(signed_in: signed_in?, is_admin: current_user&.admin?, license: License.current, force_notification: false)
::Gitlab::ExpiringSubscriptionMessage.new(
subscribable: license,
signed_in: signed_in,
is_admin: is_admin
is_admin: is_admin,
force_notification: force_notification
).message
end
......
......@@ -292,8 +292,12 @@ module EE
end
def with_slack_application_disabled
joins('LEFT JOIN services ON services.project_id = projects.id AND services.type = \'GitlabSlackApplicationService\' AND services.active IS true')
.where(services: { id: nil })
joins(<<~SQL)
LEFT JOIN #{::Integration.table_name} ON #{::Integration.table_name}.project_id = projects.id
AND #{::Integration.table_name}.type = 'GitlabSlackApplicationService'
AND #{::Integration.table_name}.active IS true
SQL
.where(integrations: { id: nil })
end
override :with_web_entity_associations
......
......@@ -195,6 +195,10 @@ module EE
enable :security_orchestration_policies
end
rule { security_orchestration_policies_enabled & can?(:owner_access) }.policy do
enable :update_security_orchestration_policy_project
end
rule { security_dashboard_enabled & can?(:developer_access) }.policy do
enable :read_security_resource
enable :read_vulnerability_scanner
......
......@@ -7,8 +7,11 @@
= s_('SecurityOrchestration|Security policy project')
%p
= project_select_tag('orchestration[policy_project_id]', class: 'hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: _('Select project'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, user_id: current_user.id }, value: @assigned_policy_id)
placeholder: _('Select project'), idAttribute: 'id', disabled: !can_update_security_orchestration_policy_project?(@project), data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, user_id: current_user.id }, value: @assigned_policy_id)
.text-muted
= html_escape(s_('SecurityOrchestration|A security policy project can be used enforce policies for a given project, group, or instance. It allows you to specify security policies that are important to you and enforce them with every commit.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
= link_to _('More information'), help_page_path('user/project/clusters/protect/container_network_security/quick_start_guide'), target: '_blank'
= field.submit _('Save changes'), class: 'btn gl-button btn-success'
- if can_update_security_orchestration_policy_project?(@project)
= field.submit _('Save changes'), class: 'btn gl-button btn-success'
- else
= field.submit _('Save changes'), class: 'btn gl-button btn-success has-tooltip', disabled: true, title: _('Only owners can update Security Policy Project')
......@@ -157,7 +157,7 @@ module EE
def check_if_license_blocks_changes!
if ::License.block_changes?
message = license_message(signed_in: true, is_admin: (user && user.admin?))
message = license_message(signed_in: true, is_admin: (user && user.admin?), force_notification: true)
raise ::Gitlab::GitAccess::ForbiddenError, strip_tags(message)
end
end
......
......@@ -7,15 +7,16 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
include ActionView::Helpers::TextHelper
attr_reader :subscribable, :signed_in, :is_admin, :namespace
attr_reader :subscribable, :signed_in, :is_admin, :namespace, :force_notification
delegate :auto_renew, to: :subscribable
def initialize(subscribable:, signed_in:, is_admin:, namespace: nil)
def initialize(subscribable:, signed_in:, is_admin:, namespace: nil, force_notification: false)
@subscribable = subscribable
@signed_in = signed_in
@is_admin = is_admin
@namespace = namespace
@force_notification = force_notification
end
def message
......@@ -39,7 +40,7 @@ module Gitlab
end
def expired_subject
if show_downgrade_messaging?
if show_downgrade_messaging? && namespace
if auto_renew
_('Something went wrong with your automatic subscription renewal.')
else
......@@ -71,7 +72,7 @@ module Gitlab
def block_changes_message
return namespace_block_changes_message if namespace
_('You didn\'t renew your subscription so it was downgraded to the GitLab Core Plan.')
_('Please delete your current license if you want to downgrade to the free plan.')
end
def namespace_block_changes_message
......@@ -127,6 +128,7 @@ module Gitlab
def require_notification?
return false if expiring_auto_renew? || ::License.future_dated.present?
return true if force_notification && subscribable.block_changes?
auto_renew_choice_exists? && expired_subscribable_within_notification_window? && !subscription_future_renewal?
end
......@@ -154,7 +156,11 @@ module Gitlab
end
def show_downgrade_messaging?
subscribable.block_changes? && (self_managed? || plan_downgraded?)
if self_managed?
subscribable.block_changes?
else
subscribable.block_changes? && plan_downgraded?
end
end
def strong
......
......@@ -48,7 +48,7 @@ RSpec.describe "Admin views license" do
travel_to(reference_date) do
visit(admin_license_path)
expect(page).to have_content "You have 7 days to renew your subscription."
expect(page).to have_content 'You have 7 days to renew your subscription.'
expect(page).to have_link 'Renew subscription', href: "#{EE::SUBSCRIPTIONS_URL}/subscriptions"
end
end
......@@ -61,7 +61,7 @@ RSpec.describe "Admin views license" do
travel_to(reference_date) do
visit(admin_license_path)
expect(page).to have_content "You didn't renew your subscription so it was downgraded to the GitLab Core Plan"
expect(page).to have_content 'Please delete your current license if you want to downgrade to the free plan'
expect(page).to have_link 'Upgrade your plan', href: "#{EE::SUBSCRIPTIONS_URL}/subscriptions"
end
end
......
......@@ -3,14 +3,17 @@
require 'spec_helper'
RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
describe '#resolve' do
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let_it_be(:project) { create(:project, namespace: owner.namespace) }
let_it_be(:policy_project) { create(:project) }
let_it_be(:policy_project_id) { GitlabSchema.id_from_object(policy_project) }
let(:current_user) { owner }
subject { mutation.resolve(project_path: project.full_path, security_policy_project_id: policy_project_id) }
context 'when feature is enabled and permission is set for user' do
......@@ -19,12 +22,26 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
stub_feature_flags(security_orchestration_policies_configuration: true)
end
it 'assigns the security policy project' do
result = subject
context 'when user is an owner of the project' do
it 'assigns the security policy project' do
result = subject
expect(result[:errors]).to be_empty
expect(project.security_orchestration_policy_configuration).not_to be_nil
expect(project.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
end
end
context 'when user is not an owner' do
let(:current_user) { user }
before do
project.add_maintainer(user)
end
expect(result[:errors]).to be_empty
expect(project.security_orchestration_policy_configuration).not_to be_nil
expect(project.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
......@@ -47,7 +64,7 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
end
end
context 'when permission is not enabled' do
context 'when feature is not licensed' do
before do
stub_licensed_features(security_orchestration_policies: false)
end
......
......@@ -2,27 +2,44 @@
require 'spec_helper'
RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
describe '#resolve' do
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let_it_be(:project) { create(:project, namespace: owner.namespace) }
let(:current_user) { owner }
subject { mutation.resolve(project_path: project.full_path) }
context 'when feature is enabled and permission is set for user' do
before do
project.add_maintainer(user)
stub_licensed_features(security_orchestration_policies: true)
stub_feature_flags(security_orchestration_policies_configuration: true)
end
it 'returns project' do
result = subject
context 'when user is an owner of the project' do
let(:current_user) { owner }
it 'returns project' do
result = subject
expect(result[:errors]).to be_empty
expect(result[:project]).to eq(Project.last)
end
end
context 'when user is not an owner' do
let(:current_user) { user }
before do
project.add_maintainer(user)
end
expect(result[:errors]).to be_empty
expect(result[:project]).to eq(Project.last)
it 'raises exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
......@@ -37,7 +54,7 @@ RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do
end
end
context 'when permission is not enabled' do
context 'when feature is not licensed' do
before do
stub_licensed_features(security_orchestration_policies: false)
end
......
......@@ -188,7 +188,8 @@ RSpec.describe EE::SubscribableBannerHelper do
expect(Gitlab::ExpiringSubscriptionMessage).to receive(:new).with(
subscribable: license,
signed_in: true,
is_admin: false
is_admin: false,
force_notification: false
).and_return(message_mock)
expect(message_mock).to receive(:message)
......
......@@ -20,6 +20,24 @@ RSpec.describe ProjectsHelper do
end
end
describe '#can_update_security_orchestration_policy_project?' do
let(:owner) { project.owner }
before do
allow(helper).to receive(:current_user) { owner }
end
it 'returns false when user cannot update security orchestration policy project' do
allow(helper).to receive(:can?).with(owner, :update_security_orchestration_policy_project, project) { false }
expect(helper.can_update_security_orchestration_policy_project?(project)).to eq false
end
it 'returns true when user can update security orchestration policy project' do
allow(helper).to receive(:can?).with(owner, :update_security_orchestration_policy_project, project) { true }
expect(helper.can_update_security_orchestration_policy_project?(project)).to eq true
end
end
describe '#can_import_members?' do
let(:owner) { project.owner }
......
......@@ -12,12 +12,14 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
let(:subscribable) { double(:license) }
let(:namespace) { nil }
let(:force_notification) { false }
let(:message) do
described_class.new(
subscribable: subscribable,
signed_in: true,
is_admin: true,
namespace: namespace
namespace: namespace,
force_notification: force_notification
).message
end
......@@ -100,12 +102,12 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
end
it 'has a nice subject' do
expect(subject).to include('Your subscription has been downgraded.')
expect(subject).to include('Your subscription expired!')
end
context 'no namespace' do
it 'has an expiration blocking message' do
expect(subject).to include("You didn't renew your subscription so it was downgraded to the GitLab Core Plan")
expect(subject).to include('Please delete your current license if you want to downgrade to the free plan')
end
end
......@@ -316,7 +318,7 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
let(:grace_period_effective_from) { today.to_date - 40.days }
it 'has a nice subject' do
expect(subject).to include('Your subscription has been downgraded')
expect(subject).to include('Your subscription expired!')
end
end
......@@ -326,6 +328,14 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
it 'stops displaying' do
expect(subject).to be nil
end
context 'and force_notification is true' do
let(:force_notification) { true }
it 'returns a message' do
expect(subject).to include('Your subscription expired!')
end
end
end
context 'and not past the cutoff date' do
......
......@@ -748,13 +748,25 @@ RSpec.describe ProjectPolicy do
stub_licensed_features(security_orchestration_policies: true)
end
context 'with developer or higher role' do
where(role: %w[owner maintainer developer])
context 'with developer or maintainer role' do
where(role: %w[maintainer developer])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:security_orchestration_policies) }
it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
end
end
context 'with owner role' do
where(role: %w[owner])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:security_orchestration_policies) }
it { is_expected.to be_allowed(:update_security_orchestration_policy_project) }
end
end
end
......
......@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe Projects::Security::PoliciesController, type: :request do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, namespace: owner.namespace) }
before do
project.add_developer(user)
......@@ -45,17 +46,32 @@ RSpec.describe Projects::Security::PoliciesController, type: :request do
stub_licensed_features(security_orchestration_policies: true)
end
it 'assigns policy project to project' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: policy_project.id } }
context 'when user is not an owner of the project' do
it 'returns error message' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: policy_project.id } }
expect(response).to redirect_to(project_security_policy_url(project))
expect(project.security_orchestration_policy_configuration.security_policy_management_project_id).to eq(policy_project.id)
expect(response).to have_gitlab_http_status(:not_found)
expect(response).not_to render_template('new')
end
end
it 'returns error message for invalid input' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: nil } }
context 'when user is an owner of the project' do
before do
login_as(owner)
end
it 'assigns policy project to project' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: policy_project.id } }
expect(flash[:alert]).to eq 'Policy project doesn\'t exist'
expect(response).to redirect_to(project_security_policy_url(project))
expect(project.security_orchestration_policy_configuration.security_policy_management_project_id).to eq(policy_project.id)
end
it 'returns error message for invalid input' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: nil } }
expect(flash[:alert]).to eq 'Policy project doesn\'t exist'
end
end
end
end
......@@ -10,9 +10,7 @@ module Gitlab
# TABLES_TO_BE_RENAMED = {
# 'old_name' => 'new_name'
# }.freeze
TABLES_TO_BE_RENAMED = {
'services' => 'integrations'
}.freeze
TABLES_TO_BE_RENAMED = {}.freeze
# Minimum PostgreSQL version requirement per documentation:
# https://docs.gitlab.com/ee/install/requirements.html#postgresql-requirements
......
......@@ -15,7 +15,12 @@ module Gitlab
context_for_args = worker_class.context_for_arguments(job['args'])
wrap_in_optional_context(context_for_args, &block)
wrap_in_optional_context(context_for_args) do
# This should be inside the context for the arguments so
# that we don't override the feature category on the worker
# with the one from the caller.
Gitlab::ApplicationContext.with_context(feature_category: worker_class.get_feature_category.to_s, &block)
end
end
end
end
......
......@@ -22972,6 +22972,9 @@ msgstr ""
msgid "Only include features new to your current subscription tier."
msgstr ""
msgid "Only owners can update Security Policy Project"
msgstr ""
msgid "Only policy:"
msgstr ""
......@@ -24463,6 +24466,9 @@ msgstr ""
msgid "Please create an index before enabling indexing"
msgstr ""
msgid "Please delete your current license if you want to downgrade to the free plan."
msgstr ""
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
......@@ -37421,9 +37427,6 @@ msgstr ""
msgid "You didn't renew your subscription for %{strong}%{namespace_name}%{strong_close} so it was downgraded to the free plan."
msgstr ""
msgid "You didn't renew your subscription so it was downgraded to the GitLab Core Plan."
msgstr ""
msgid "You do not have an active license"
msgstr ""
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl do
RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl, schema: 20210421163509 do
let(:services_table) { table(:services) }
let(:service_jira_cloud) { services_table.create!(id: 1, type: 'JiraService') }
let(:service_jira_server) { services_table.create!(id: 2, type: 'JiraService') }
......
......@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Integrations::StiType do
context 'SQL SELECT' do
let(:expected_sql) do
<<~SQL.strip
SELECT "services".* FROM "services" WHERE "services"."type" = 'AsanaService'
SELECT "integrations".* FROM "integrations" WHERE "integrations"."type" = 'AsanaService'
SQL
end
......@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Integrations::StiType do
context 'SQL CREATE' do
let(:expected_sql) do
<<~SQL.strip
INSERT INTO "services" ("type") VALUES ('AsanaService')
INSERT INTO "integrations" ("type") VALUES ('AsanaService')
SQL
end
......@@ -42,7 +42,7 @@ RSpec.describe Gitlab::Integrations::StiType do
context 'SQL UPDATE' do
let(:expected_sql) do
<<~SQL.strip
UPDATE "services" SET "type" = 'AsanaService'
UPDATE "integrations" SET "type" = 'AsanaService'
SQL
end
......@@ -61,7 +61,7 @@ RSpec.describe Gitlab::Integrations::StiType do
context 'SQL DELETE' do
let(:expected_sql) do
<<~SQL.strip
DELETE FROM "services" WHERE "services"."type" = 'AsanaService'
DELETE FROM "integrations" WHERE "integrations"."type" = 'AsanaService'
SQL
end
......
......@@ -11,6 +11,8 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
include ApplicationWorker
feature_category :issue_tracking
def self.job_for_args(args)
jobs.find { |job| job['args'] == args }
end
......@@ -41,5 +43,39 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
expect(job1['meta.user']).to eq(user_per_job['job1'].username)
expect(job2['meta.user']).to eq(user_per_job['job2'].username)
end
context 'when the feature category is set in the context_proc' do
it 'takes the feature category from the worker, not the caller' do
TestWithContextWorker.bulk_perform_async_with_contexts(
%w(job1 job2),
arguments_proc: -> (name) { [name, 1, 2, 3] },
context_proc: -> (_) { { feature_category: 'code_review' } }
)
job1 = TestWithContextWorker.job_for_args(['job1', 1, 2, 3])
job2 = TestWithContextWorker.job_for_args(['job2', 1, 2, 3])
expect(job1['meta.feature_category']).to eq('issue_tracking')
expect(job2['meta.feature_category']).to eq('issue_tracking')
end
end
context 'when the feature category is already set in the surrounding block' do
it 'takes the feature category from the worker, not the caller' do
Gitlab::ApplicationContext.with_context(feature_category: 'authentication_and_authorization') do
TestWithContextWorker.bulk_perform_async_with_contexts(
%w(job1 job2),
arguments_proc: -> (name) { [name, 1, 2, 3] },
context_proc: -> (_) { {} }
)
end
job1 = TestWithContextWorker.job_for_args(['job1', 1, 2, 3])
job2 = TestWithContextWorker.job_for_args(['job2', 1, 2, 3])
expect(job1['meta.feature_category']).to eq('issue_tracking')
expect(job2['meta.feature_category']).to eq('issue_tracking')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe RenameServicesToIntegrations do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:integrations) { table(:integrations) }
let(:services) { table(:services) }
before do
@namespace = namespaces.create!(name: 'foo', path: 'foo')
@project = projects.create!(namespace_id: @namespace.id)
end
RSpec.shared_examples 'a table (or view) with triggers' do
describe 'INSERT tracker trigger' do
it 'sets `has_external_issue_tracker` to true when active `issue_tracker` is inserted' do
expect do
subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
end.to change { @project.reload.has_external_issue_tracker }.to(true)
end
it 'does not set `has_external_issue_tracker` to true when integration is for a different project' do
different_project = projects.create!(namespace_id: @namespace.id)
expect do
subject.create!(category: 'issue_tracker', active: true, project_id: different_project.id)
end.not_to change { @project.reload.has_external_issue_tracker }
end
it 'does not set `has_external_issue_tracker` to true when inactive `issue_tracker` is inserted' do
expect do
subject.create!(category: 'issue_tracker', active: false, project_id: @project.id)
end.not_to change { @project.reload.has_external_issue_tracker }
end
it 'does not set `has_external_issue_tracker` to true when a non-`issue tracker` active integration is inserted' do
expect do
subject.create!(category: 'my_type', active: true, project_id: @project.id)
end.not_to change { @project.reload.has_external_issue_tracker }
end
end
describe 'UPDATE tracker trigger' do
it 'sets `has_external_issue_tracker` to true when `issue_tracker` is made active' do
integration = subject.create!(category: 'issue_tracker', active: false, project_id: @project.id)
expect do
integration.update!(active: true)
end.to change { @project.reload.has_external_issue_tracker }.to(true)
end
it 'sets `has_external_issue_tracker` to false when `issue_tracker` is made inactive' do
integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
expect do
integration.update!(active: false)
end.to change { @project.reload.has_external_issue_tracker }.to(false)
end
it 'sets `has_external_issue_tracker` to false when `issue_tracker` is made inactive, and an inactive `issue_tracker` exists' do
subject.create!(category: 'issue_tracker', active: false, project_id: @project.id)
integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
expect do
integration.update!(active: false)
end.to change { @project.reload.has_external_issue_tracker }.to(false)
end
it 'does not change `has_external_issue_tracker` when `issue_tracker` is made inactive, if an active `issue_tracker` exists' do
subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
expect do
integration.update!(active: false)
end.not_to change { @project.reload.has_external_issue_tracker }
end
it 'does not change `has_external_issue_tracker` when integration is for a different project' do
different_project = projects.create!(namespace_id: @namespace.id)
integration = subject.create!(category: 'issue_tracker', active: false, project_id: different_project.id)
expect do
integration.update!(active: true)
end.not_to change { @project.reload.has_external_issue_tracker }
end
end
describe 'DELETE tracker trigger' do
it 'sets `has_external_issue_tracker` to false when `issue_tracker` is deleted' do
integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
expect do
integration.delete
end.to change { @project.reload.has_external_issue_tracker }.to(false)
end
it 'sets `has_external_issue_tracker` to false when `issue_tracker` is deleted, if an inactive `issue_tracker` still exists' do
subject.create!(category: 'issue_tracker', active: false, project_id: @project.id)
integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
expect do
integration.delete
end.to change { @project.reload.has_external_issue_tracker }.to(false)
end
it 'does not change `has_external_issue_tracker` when `issue_tracker` is deleted, if an active `issue_tracker` still exists' do
subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
integration = subject.create!(category: 'issue_tracker', active: true, project_id: @project.id)
expect do
integration.delete
end.not_to change { @project.reload.has_external_issue_tracker }
end
it 'does not change `has_external_issue_tracker` when integration is for a different project' do
different_project = projects.create!(namespace_id: @namespace.id)
integration = subject.create!(category: 'issue_tracker', active: true, project_id: different_project.id)
expect do
integration.delete
end.not_to change { @project.reload.has_external_issue_tracker }
end
end
describe 'INSERT wiki trigger' do
it 'sets `has_external_wiki` to true when active `ExternalWikiService` is inserted' do
expect do
subject.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
end.to change { @project.reload.has_external_wiki }.to(true)
end
it 'does not set `has_external_wiki` to true when integration is for a different project' do
different_project = projects.create!(namespace_id: @namespace.id)
expect do
subject.create!(type: 'ExternalWikiService', active: true, project_id: different_project.id)
end.not_to change { @project.reload.has_external_wiki }
end
it 'does not set `has_external_wiki` to true when inactive `ExternalWikiService` is inserted' do
expect do
subject.create!(type: 'ExternalWikiService', active: false, project_id: @project.id)
end.not_to change { @project.reload.has_external_wiki }
end
it 'does not set `has_external_wiki` to true when active other integration is inserted' do
expect do
subject.create!(type: 'MyService', active: true, project_id: @project.id)
end.not_to change { @project.reload.has_external_wiki }
end
end
describe 'UPDATE wiki trigger' do
it 'sets `has_external_wiki` to true when `ExternalWikiService` is made active' do
integration = subject.create!(type: 'ExternalWikiService', active: false, project_id: @project.id)
expect do
integration.update!(active: true)
end.to change { @project.reload.has_external_wiki }.to(true)
end
it 'sets `has_external_wiki` to false when `ExternalWikiService` is made inactive' do
integration = subject.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
expect do
integration.update!(active: false)
end.to change { @project.reload.has_external_wiki }.to(false)
end
it 'does not change `has_external_wiki` when integration is for a different project' do
different_project = projects.create!(namespace_id: @namespace.id)
integration = subject.create!(type: 'ExternalWikiService', active: false, project_id: different_project.id)
expect do
integration.update!(active: true)
end.not_to change { @project.reload.has_external_wiki }
end
end
describe 'DELETE wiki trigger' do
it 'sets `has_external_wiki` to false when `ExternalWikiService` is deleted' do
integration = subject.create!(type: 'ExternalWikiService', active: true, project_id: @project.id)
expect do
integration.delete
end.to change { @project.reload.has_external_wiki }.to(false)
end
it 'does not change `has_external_wiki` when integration is for a different project' do
different_project = projects.create!(namespace_id: @namespace.id)
integration = subject.create!(type: 'ExternalWikiService', active: true, project_id: different_project.id)
expect do
integration.delete
end.not_to change { @project.reload.has_external_wiki }
end
end
end
RSpec.shared_examples 'a table (or view) without triggers' do
specify do
number_of_triggers = ActiveRecord::Base.connection
.execute("SELECT count(*) FROM information_schema.triggers WHERE event_object_table = '#{subject.table_name}'")
.first['count']
expect(number_of_triggers).to eq(0)
end
end
describe '#up' do
before do
# LOCK TABLE statements must be in a transaction
ActiveRecord::Base.transaction { migrate! }
end
context 'the integrations table' do
subject { integrations }
it_behaves_like 'a table (or view) with triggers'
end
context 'the services table' do
subject { services }
it_behaves_like 'a table (or view) without triggers'
end
end
describe '#down' do
before do
# LOCK TABLE statements must be in a transaction
ActiveRecord::Base.transaction do
migration.up
migration.down
end
end
context 'the services table' do
subject { services }
it_behaves_like 'a table (or view) with triggers'
end
context 'the integrations table' do
subject { integrations }
it_behaves_like 'a table (or view) without triggers'
end
end
end
......@@ -139,61 +139,40 @@ RSpec.describe Integration do
end
end
describe "Test Button" do
let(:integration) { build(:service, project: project) }
describe '#can_test?' do
subject { integration.can_test? }
context 'when project-level integration' do
let(:project) { create(:project) }
it { is_expected.to be true }
end
context 'when instance-level integration' do
Integration.available_integration_types.each do |type|
let(:integration) do
described_class.send(:integration_type_to_model, type).new(instance: true)
end
it { is_expected.to be false }
end
end
describe '#can_test?' do
subject { integration.can_test? }
context 'when group-level integration' do
Integration.available_integration_types.each do |type|
let(:integration) do
described_class.send(:integration_type_to_model, type).new(group_id: group.id)
end
context 'when integration is project-level' do
let(:integration) { build(:service, project: project) }
it { is_expected.to be false }
end
end
it { is_expected.to be true }
end
describe '#test' do
let(:data) { 'test' }
context 'when integration is not project-level' do
let(:integration) { build(:service, project: nil) }
context 'when repository is not empty' do
let(:project) { build(:project, :repository) }
it { is_expected.to be false }
end
end
it 'test runs execute' do
expect(integration).to receive(:execute).with(data)
describe '#test' do
let(:integration) { build(:service, project: project) }
let(:data) { 'test' }
integration.test(data)
end
end
it 'calls #execute' do
expect(integration).to receive(:execute).with(data)
context 'when repository is empty' do
let(:project) { build(:project) }
integration.test(data)
end
it 'test runs execute' do
expect(integration).to receive(:execute).with(data)
it 'returns a result' do
result = 'foo'
allow(integration).to receive(:execute).with(data).and_return(result)
integration.test(data)
end
end
expect(integration.test(data)).to eq(
success: true,
result: result
)
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment