Commit f94e4488 authored by Alex Kalderimis's avatar Alex Kalderimis

Merge branch '333508-ajk-use-type-new' into 'master'

Integrations model: use `type_new` column, mark `type` for removal

See merge request gitlab-org/gitlab!80065
parents 9d161a73 3e6da3aa
...@@ -10,9 +10,20 @@ module Types ...@@ -10,9 +10,20 @@ module Types
# https://gitlab.com/gitlab-org/gitlab/-/issues/213088 # https://gitlab.com/gitlab-org/gitlab/-/issues/213088
field :type, GraphQL::Types::String, null: true, field :type, GraphQL::Types::String, null: true,
description: 'Class name of the service.' description: 'Class name of the service.'
field :service_type, ::Types::Projects::ServiceTypeEnum, null: true,
description: 'Type of the service.'
field :active, GraphQL::Types::Boolean, null: true, field :active, GraphQL::Types::Boolean, null: true,
description: 'Indicates if the service is active.' description: 'Indicates if the service is active.'
def type
enum = ::Types::Projects::ServiceTypeEnum.coerce_result(service_type, context)
enum.downcase.camelize
end
def service_type
object.type
end
definition_methods do definition_methods do
def resolve_type(object, context) def resolve_type(object, context)
if object.is_a?(::Integrations::Jira) if object.is_a?(::Integrations::Jira)
......
...@@ -8,7 +8,7 @@ module Types ...@@ -8,7 +8,7 @@ module Types
class << self class << self
private private
def type_description(type) def type_description(name, type)
"#{type} type" "#{type} type"
end end
end end
...@@ -16,8 +16,10 @@ module Types ...@@ -16,8 +16,10 @@ module Types
# This prepend must stay here because the dynamic block below depends on it. # This prepend must stay here because the dynamic block below depends on it.
prepend_mod # rubocop: disable Cop/InjectEnterpriseEditionModule prepend_mod # rubocop: disable Cop/InjectEnterpriseEditionModule
::Integration.available_integration_types(include_dev: false).each do |type| ::Integration.available_integration_names(include_dev: false).each do |name|
value type.underscore.upcase, value: type, description: type_description(type) type = "#{name.camelize}Service"
domain_value = Integration.integration_name_to_type(name)
value type.underscore.upcase, value: domain_value, description: type_description(name, type)
end end
end end
end end
......
...@@ -12,6 +12,11 @@ class Integration < ApplicationRecord ...@@ -12,6 +12,11 @@ class Integration < ApplicationRecord
include IgnorableColumns include IgnorableColumns
ignore_column :template, remove_with: '15.0', remove_after: '2022-04-22' ignore_column :template, remove_with: '15.0', remove_after: '2022-04-22'
ignore_column :type, remove_with: '15.0', remove_after: '2022-04-22'
UnknownType = Class.new(StandardError)
self.inheritance_column = :type_new
INTEGRATION_NAMES = %w[ INTEGRATION_NAMES = %w[
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
...@@ -44,7 +49,7 @@ class Integration < ApplicationRecord ...@@ -44,7 +49,7 @@ class Integration < ApplicationRecord
serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
attribute :type, Gitlab::Integrations::StiType.new alias_attribute :type, :type_new
default_value_for :active, false default_value_for :active, false
default_value_for :alert_events, true default_value_for :alert_events, true
...@@ -79,9 +84,10 @@ class Integration < ApplicationRecord ...@@ -79,9 +84,10 @@ class Integration < ApplicationRecord
validate :validate_belongs_to_project_or_group validate :validate_belongs_to_project_or_group
scope :external_issue_trackers, -> { where(category: 'issue_tracker').active } scope :external_issue_trackers, -> { where(category: 'issue_tracker').active }
scope :external_wikis, -> { where(type: 'ExternalWikiService').active } scope :by_name, ->(name) { by_type(integration_name_to_type(name)) }
scope :external_wikis, -> { by_name(:external_wiki).active }
scope :active, -> { where(active: true) } scope :active, -> { where(active: true) }
scope :by_type, -> (type) { where(type: type) } scope :by_type, ->(type) { where(type: type) } # INTERNAL USE ONLY: use by_name instead
scope :by_active_flag, -> (flag) { where(active: flag) } scope :by_active_flag, -> (flag) { where(active: flag) }
scope :inherit_from_id, -> (id) { where(inherit_from_id: id) } scope :inherit_from_id, -> (id) { where(inherit_from_id: id) }
scope :with_default_settings, -> { where.not(inherit_from_id: nil) } scope :with_default_settings, -> { where.not(inherit_from_id: nil) }
...@@ -231,7 +237,7 @@ class Integration < ApplicationRecord ...@@ -231,7 +237,7 @@ class Integration < ApplicationRecord
end end
# Returns a list of available integration types. # Returns a list of available integration types.
# Example: ["AsanaService", ...] # Example: ["Integrations::Asana", ...]
def self.available_integration_types(include_project_specific: true, include_dev: true) def self.available_integration_types(include_project_specific: true, include_dev: true)
available_integration_names(include_project_specific: include_project_specific, include_dev: include_dev).map do available_integration_names(include_project_specific: include_project_specific, include_dev: include_dev).map do
integration_name_to_type(_1) integration_name_to_type(_1)
...@@ -239,22 +245,27 @@ class Integration < ApplicationRecord ...@@ -239,22 +245,27 @@ class Integration < ApplicationRecord
end end
# Returns the model for the given integration name. # Returns the model for the given integration name.
# Example: "asana" => Integrations::Asana # Example: :asana => Integrations::Asana
def self.integration_name_to_model(name) def self.integration_name_to_model(name)
type = integration_name_to_type(name) type = integration_name_to_type(name)
integration_type_to_model(type) integration_type_to_model(type)
end end
# Returns the STI type for the given integration name. # Returns the STI type for the given integration name.
# Example: "asana" => "AsanaService" # Example: "asana" => "Integrations::Asana"
def self.integration_name_to_type(name) def self.integration_name_to_type(name)
"#{name}_service".camelize name = name.to_s
if available_integration_names.exclude?(name)
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(UnknownType.new(name.inspect))
else
"Integrations::#{name.camelize}"
end
end end
# Returns the model for the given STI type. # Returns the model for the given STI type.
# Example: "AsanaService" => Integrations::Asana # Example: "Integrations::Asana" => Integrations::Asana
def self.integration_type_to_model(type) def self.integration_type_to_model(type)
Gitlab::Integrations::StiType.new.cast(type).constantize type.constantize
end end
private_class_method :integration_type_to_model private_class_method :integration_type_to_model
...@@ -303,7 +314,7 @@ class Integration < ApplicationRecord ...@@ -303,7 +314,7 @@ class Integration < ApplicationRecord
from_union([ from_union([
active.where(instance: true), active.where(instance: true),
active.where(group_id: group_ids, inherit_from_id: nil) active.where(group_id: group_ids, inherit_from_id: nil)
]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], #{table_name}.group_id), instance DESC")).group_by(&:type).each do |type, records| ]).order(Arel.sql("type_new 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 build_from_integration(records.first, association => scope.id).save
end end
end end
...@@ -380,8 +391,10 @@ class Integration < ApplicationRecord ...@@ -380,8 +391,10 @@ class Integration < ApplicationRecord
%w[active] %w[active]
end end
# return a hash of columns => values suitable for passing to insert_all
def to_integration_hash def to_integration_hash
as_json(methods: :type, except: %w[id instance project_id group_id]) column = self.class.attribute_aliases.fetch('type', 'type')
as_json(except: %w[id instance project_id group_id]).merge(column => type)
end end
def to_data_fields_hash def to_data_fields_hash
......
...@@ -8726,6 +8726,7 @@ An emoji awarded by a user. ...@@ -8726,6 +8726,7 @@ An emoji awarded by a user.
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="baseserviceactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. | | <a id="baseserviceactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. |
| <a id="baseserviceservicetype"></a>`serviceType` | [`ServiceType`](#servicetype) | Type of the service. |
| <a id="baseservicetype"></a>`type` | [`String`](#string) | Class name of the service. | | <a id="baseservicetype"></a>`type` | [`String`](#string) | Class name of the service. |
### `Blob` ### `Blob`
...@@ -12028,6 +12029,7 @@ Represents an iteration cadence. ...@@ -12028,6 +12029,7 @@ Represents an iteration cadence.
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="jiraserviceactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. | | <a id="jiraserviceactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. |
| <a id="jiraserviceservicetype"></a>`serviceType` | [`ServiceType`](#servicetype) | Type of the service. |
| <a id="jiraservicetype"></a>`type` | [`String`](#string) | Class name of the service. | | <a id="jiraservicetype"></a>`type` | [`String`](#string) | Class name of the service. |
#### Fields with arguments #### Fields with arguments
...@@ -19284,6 +19286,7 @@ Implementations: ...@@ -19284,6 +19286,7 @@ Implementations:
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="serviceactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. | | <a id="serviceactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. |
| <a id="serviceservicetype"></a>`serviceType` | [`ServiceType`](#servicetype) | Type of the service. |
| <a id="servicetype"></a>`type` | [`String`](#string) | Class name of the service. | | <a id="servicetype"></a>`type` | [`String`](#string) | Class name of the service. |
#### `TimeboxReportInterface` #### `TimeboxReportInterface`
...@@ -12,14 +12,13 @@ module EE ...@@ -12,14 +12,13 @@ module EE
private private
override :type_description override :type_description
def type_description(type) def type_description(name, type)
description = super description = super
description = [description, ' (Gitlab.com only)'].join if saas_only?(type) description = [description, ' (Gitlab.com only)'].join if saas_only?(name)
description description
end end
def saas_only?(type) def saas_only?(name)
name = ::Integration.integration_type_to_name(type)
::Integration.saas_only_integration_names.include?(name) ::Integration.saas_only_integration_names.include?(name)
end end
end end
......
...@@ -286,12 +286,17 @@ module EE ...@@ -286,12 +286,17 @@ module EE
end end
def with_slack_application_disabled def with_slack_application_disabled
joins(<<~SQL) # Using Arel to avoid exposing what the column backing the type: attribute is
LEFT JOIN #{::Integration.table_name} ON #{::Integration.table_name}.project_id = projects.id # rubocop: disable GitlabSecurity/PublicSend
AND #{::Integration.table_name}.type = 'GitlabSlackApplicationService' with_active_slack = ::Integration.active.by_name(:gitlab_slack_application)
AND #{::Integration.table_name}.active IS true join_contraint = arel_table[:id].eq(::Integration.arel_table[:project_id])
SQL constraint = with_active_slack.where_clause.send(:predicates).reduce(join_contraint) { |a, b| a.and(b) }
.where(integrations: { id: nil }) join = arel_table.join(::Integration.arel_table, Arel::Nodes::OuterJoin).on(constraint).join_sources
# rubocop: enable GitlabSecurity/PublicSend
joins(join).where(integrations: { id: nil })
rescue ::Integration::UnknownType
all
end end
override :with_web_entity_associations override :with_web_entity_associations
......
...@@ -4,12 +4,12 @@ FactoryBot.define do ...@@ -4,12 +4,12 @@ FactoryBot.define do
factory :gitlab_slack_application_integration, class: 'Integrations::GitlabSlackApplication' do factory :gitlab_slack_application_integration, class: 'Integrations::GitlabSlackApplication' do
project project
active { true } active { true }
type { 'GitlabSlackApplicationService' } type { 'Integrations::GitlabSlackApplication' }
end end
factory :github_integration, class: 'Integrations::Github' do factory :github_integration, class: 'Integrations::Github' do
project project
type { 'GithubService' } type { 'Integrations::Github' }
active { true } active { true }
token { 'github-token' } token { 'github-token' }
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ServiceType'] do
it 'exposes all the EE project services' do
expect(described_class.values.keys).to include(*ee_service_enums)
end
def ee_service_enums
%w[
GITHUB_SERVICE
]
end
it 'coerces values correctly' do
integration = build(:github_integration)
expect(described_class.coerce_isolated_result(integration.type)).to eq 'GITHUB_SERVICE'
end
end
...@@ -1879,18 +1879,37 @@ RSpec.describe Project do ...@@ -1879,18 +1879,37 @@ RSpec.describe Project do
end end
describe '#with_slack_application_disabled' do describe '#with_slack_application_disabled' do
it 'returns projects where Slack application is disabled' do let(:project1) { create(:project) }
project1 = create(:project) let(:project2) { create(:project) }
project2 = create(:project) let(:project3) { create(:project) }
before do
create(:gitlab_slack_application_integration, project: project2) create(:gitlab_slack_application_integration, project: project2)
create(:gitlab_slack_application_integration, project: project3, active: false)
end
context 'when slack applications are available' do
it 'returns projects where Slack application is disabled or absent' do
projects = described_class.with_slack_application_disabled projects = described_class.with_slack_application_disabled
expect(projects).to include(project1) expect(projects).to include(project1, project3)
expect(projects).not_to include(project2) expect(projects).not_to include(project2)
end end
end end
context 'when slack applications are not available' do
before do
allow(::Gitlab).to receive(:dev_or_test_env?).and_return(false)
end
it 'returns projects where Slack application is disabled or absent' do
projects = described_class.with_slack_application_disabled
expect(projects).to include(project1, project2, project3)
end
end
end
describe '#licensed_features', :saas do describe '#licensed_features', :saas do
let(:plan_license) { :free } let(:plan_license) { :free }
let(:global_license) { create(:license) } let(:global_license) { create(:license) }
......
...@@ -12,6 +12,16 @@ FactoryBot.define do ...@@ -12,6 +12,16 @@ FactoryBot.define do
issue_tracker issue_tracker
end end
factory :jenkins_integration, class: 'Integrations::Jenkins' do
project
active { true }
type { 'Integrations::Jenkins' }
jenkins_url { 'http://jenkins.example.com/' }
project_name { 'my-project' }
username { 'jenkings-user' }
password { 'passw0rd' }
end
factory :datadog_integration, class: 'Integrations::Datadog' do factory :datadog_integration, class: 'Integrations::Datadog' do
project project
active { true } active { true }
...@@ -20,7 +30,7 @@ FactoryBot.define do ...@@ -20,7 +30,7 @@ FactoryBot.define do
factory :emails_on_push_integration, class: 'Integrations::EmailsOnPush' do factory :emails_on_push_integration, class: 'Integrations::EmailsOnPush' do
project project
type { 'EmailsOnPushService' } type { 'Integrations::EmailsOnPush' }
active { true } active { true }
push_events { true } push_events { true }
tag_push_events { true } tag_push_events { true }
...@@ -54,7 +64,7 @@ FactoryBot.define do ...@@ -54,7 +64,7 @@ FactoryBot.define do
factory :jira_integration, class: 'Integrations::Jira' do factory :jira_integration, class: 'Integrations::Jira' do
project project
active { true } active { true }
type { 'JiraService' } type { 'Integrations::Jira' }
transient do transient do
create_data { true } create_data { true }
...@@ -88,7 +98,7 @@ FactoryBot.define do ...@@ -88,7 +98,7 @@ FactoryBot.define do
factory :zentao_integration, class: 'Integrations::Zentao' do factory :zentao_integration, class: 'Integrations::Zentao' do
project project
active { true } active { true }
type { 'ZentaoService' } type { 'Integrations::Zentao' }
transient do transient do
create_data { true } create_data { true }
...@@ -167,7 +177,7 @@ FactoryBot.define do ...@@ -167,7 +177,7 @@ FactoryBot.define do
factory :external_wiki_integration, class: 'Integrations::ExternalWiki' do factory :external_wiki_integration, class: 'Integrations::ExternalWiki' do
project project
type { 'ExternalWikiService' } type { 'Integrations::ExternalWiki' }
active { true } active { true }
external_wiki_url { 'http://external-wiki-url.com' } external_wiki_url { 'http://external-wiki-url.com' }
end end
...@@ -178,24 +188,39 @@ FactoryBot.define do ...@@ -178,24 +188,39 @@ FactoryBot.define do
password { 'my-secret-password' } password { 'my-secret-password' }
end end
trait :chat_notification do
webhook { 'https://example.com/webhook' }
end
trait :inactive do
active { false }
end
factory :mattermost_integration, class: 'Integrations::Mattermost' do
chat_notification
project
type { 'Integrations::Mattermost' }
active { true }
end
# avoids conflict with slack_integration factory # avoids conflict with slack_integration factory
factory :integrations_slack, class: 'Integrations::Slack' do factory :integrations_slack, class: 'Integrations::Slack' do
chat_notification
project project
active { true } active { true }
webhook { 'https://slack.service.url' } type { 'Integrations::Slack' }
type { 'SlackService' }
end end
factory :slack_slash_commands_integration, class: 'Integrations::SlackSlashCommands' do factory :slack_slash_commands_integration, class: 'Integrations::SlackSlashCommands' do
project project
active { true } active { true }
type { 'SlackSlashCommandsService' } type { 'Integrations::SlackSlashCommands' }
end end
factory :pipelines_email_integration, class: 'Integrations::PipelinesEmail' do factory :pipelines_email_integration, class: 'Integrations::PipelinesEmail' do
project project
active { true } active { true }
type { 'PipelinesEmailService' } type { 'Integrations::PipelinesEmail' }
recipients { 'test@example.com' } recipients { 'test@example.com' }
end end
......
...@@ -5,8 +5,7 @@ FactoryBot.define do ...@@ -5,8 +5,7 @@ FactoryBot.define do
skip_create # non-model factories (i.e. without #save) skip_create # non-model factories (i.e. without #save)
initialize_with do initialize_with do
projects = create_list(:project, 3) projects = create_list(:project, 4, :repository)
projects << create(:project, :repository)
group = create(:group) group = create(:group)
create(:board, project: projects[0]) create(:board, project: projects[0])
create(:jira_integration, project: projects[0]) create(:jira_integration, project: projects[0])
...@@ -19,16 +18,21 @@ FactoryBot.define do ...@@ -19,16 +18,21 @@ FactoryBot.define do
create(:jira_import_state, :finished, project: projects[1], label: jira_label, imported_issues_count: 3) create(:jira_import_state, :finished, project: projects[1], label: jira_label, imported_issues_count: 3)
create(:jira_import_state, :scheduled, project: projects[1], label: jira_label) create(:jira_import_state, :scheduled, project: projects[1], label: jira_label)
create(:prometheus_integration, project: projects[1]) create(:prometheus_integration, project: projects[1])
create(:integration, project: projects[1], type: 'JenkinsService', active: true) create(:jenkins_integration, project: projects[1])
create(:integration, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:integration, project: projects[1], type: 'SlackService', active: true) # slack
create(:integration, project: projects[2], type: 'SlackService', active: true) create(:slack_slash_commands_integration, project: projects[0])
create(:integration, project: projects[2], type: 'MattermostService', active: false) create(:integrations_slack, project: projects[1])
create(:integration, group: group, project: nil, type: 'MattermostService', active: true) create(:integrations_slack, project: projects[2])
mattermost_instance = create(:integration, :instance, type: 'MattermostService', active: true)
create(:integration, project: projects[1], type: 'MattermostService', active: true, inherit_from_id: mattermost_instance.id) # mattermost
create(:integration, group: group, project: nil, type: 'SlackService', active: true, inherit_from_id: mattermost_instance.id) create(:mattermost_integration, project: projects[2], active: false)
create(:integration, project: projects[2], type: 'CustomIssueTrackerService', active: true) create(:mattermost_integration, group: group, project: nil)
mattermost_instance = create(:mattermost_integration, :instance)
create(:mattermost_integration, project: projects[1], inherit_from_id: mattermost_instance.id)
create(:integrations_slack, group: group, project: nil, active: true, inherit_from_id: mattermost_instance.id)
create(:custom_issue_tracker_integration, project: projects[2], active: true)
create(:project_error_tracking_setting, project: projects[0]) create(:project_error_tracking_setting, project: projects[0])
create(:project_error_tracking_setting, project: projects[1], enabled: false) create(:project_error_tracking_setting, project: projects[1], enabled: false)
alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot) alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot)
......
...@@ -96,10 +96,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do ...@@ -96,10 +96,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'view merge request with external CI service' do context 'view merge request with external CI service' do
before do before do
create(:integration, project: project, create(:drone_ci_integration, project: project)
active: true,
type: 'DroneCiService',
category: 'ci')
visit project_merge_request_path(project, merge_request) visit project_merge_request_path(project, merge_request)
end end
......
...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['BaseService'] do ...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['BaseService'] do
specify { expect(described_class.graphql_name).to eq('BaseService') } specify { expect(described_class.graphql_name).to eq('BaseService') }
it 'has basic expected fields' do it 'has basic expected fields' do
expect(described_class).to have_graphql_fields(:type, :active) expect(described_class).to have_graphql_fields(:type, :active, :service_type)
end end
specify { expect(described_class).to require_graphql_authorizations(:admin_project) } specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
......
...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['JiraService'] do ...@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['JiraService'] do
specify { expect(described_class.graphql_name).to eq('JiraService') } specify { expect(described_class.graphql_name).to eq('JiraService') }
it 'has basic expected fields' do it 'has basic expected fields' do
expect(described_class).to have_graphql_fields(:type, :active, :projects) expect(described_class).to have_graphql_fields(:type, :active, :projects, :service_type)
end end
specify { expect(described_class).to require_graphql_authorizations(:admin_project) } specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
......
...@@ -4,10 +4,52 @@ require 'spec_helper' ...@@ -4,10 +4,52 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['ServiceType'] do RSpec.describe GitlabSchema.types['ServiceType'] do
it 'exposes all the existing project services' do it 'exposes all the existing project services' do
expect(described_class.values.keys).to match_array(available_services_enum) expect(described_class.values.keys).to include(*core_service_enums)
end end
def available_services_enum def core_service_enums
::Integration.available_integration_types(include_dev: false).map(&:underscore).map(&:upcase) %w[
ASANA_SERVICE
ASSEMBLA_SERVICE
BAMBOO_SERVICE
BUGZILLA_SERVICE
BUILDKITE_SERVICE
CAMPFIRE_SERVICE
CONFLUENCE_SERVICE
CUSTOM_ISSUE_TRACKER_SERVICE
DATADOG_SERVICE
DISCORD_SERVICE
DRONE_CI_SERVICE
EMAILS_ON_PUSH_SERVICE
EWM_SERVICE
EXTERNAL_WIKI_SERVICE
FLOWDOCK_SERVICE
HANGOUTS_CHAT_SERVICE
IRKER_SERVICE
JENKINS_SERVICE
JIRA_SERVICE
MATTERMOST_SERVICE
MATTERMOST_SLASH_COMMANDS_SERVICE
MICROSOFT_TEAMS_SERVICE
PACKAGIST_SERVICE
PIPELINES_EMAIL_SERVICE
PIVOTALTRACKER_SERVICE
PROMETHEUS_SERVICE
PUSHOVER_SERVICE
REDMINE_SERVICE
SHIMO_SERVICE
SLACK_SERVICE
SLACK_SLASH_COMMANDS_SERVICE
TEAMCITY_SERVICE
UNIFY_CIRCUIT_SERVICE
WEBEX_TEAMS_SERVICE
YOUTRACK_SERVICE
ZENTAO_SERVICE
]
end
it 'coerces values correctly' do
integration = build(:jenkins_integration)
expect(described_class.coerce_isolated_result(integration.type)).to eq 'JENKINS_SERVICE'
end end
end end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Types::Projects::ServiceType do RSpec.describe Types::Projects::ServiceType do
specify { expect(described_class).to have_graphql_fields(:type, :active) } specify { expect(described_class).to have_graphql_fields(:type, :service_type, :active) }
describe ".resolve_type" do describe ".resolve_type" do
it 'resolves the corresponding type for objects' do it 'resolves the corresponding type for objects' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Integrations::StiType do
let(:types) { ['AsanaService', 'Integrations::Asana', Integrations::Asana] }
describe '#serialize' do
context 'SQL SELECT' do
let(:expected_sql) do
<<~SQL.strip
FROM "integrations" WHERE "integrations"."type" = 'AsanaService'
SQL
end
it 'forms SQL SELECT statements correctly' do
sql_statements = types.map do |type|
Integration.where(type: type).to_sql
end
expect(sql_statements).to all(end_with(expected_sql))
end
end
context 'SQL CREATE' do
let(:expected_sql) do
<<~SQL.strip
INSERT INTO "integrations" ("type") VALUES ('AsanaService')
SQL
end
it 'forms SQL CREATE statements correctly' do
sql_statements = types.map do |type|
record = ActiveRecord::QueryRecorder.new { Integration.insert({ type: type }) }
record.log.first
end
expect(sql_statements).to all(include(expected_sql))
end
end
context 'SQL UPDATE' do
let(:expected_sql) do
<<~SQL.strip
UPDATE "integrations" SET "type" = 'AsanaService'
SQL
end
let_it_be(:integration) { create(:integration) }
it 'forms SQL UPDATE statements correctly' do
sql_statements = types.map do |type|
record = ActiveRecord::QueryRecorder.new { integration.update_column(:type, type) }
record.log.first
end
expect(sql_statements).to all(include(expected_sql))
end
end
context 'SQL DELETE' do
let(:expected_sql) do
<<~SQL.strip
DELETE FROM "integrations" WHERE "integrations"."type" = 'AsanaService'
SQL
end
it 'forms SQL DELETE statements correctly' do
sql_statements = types.map do |type|
record = ActiveRecord::QueryRecorder.new { Integration.delete_by(type: type) }
record.log.first
end
expect(sql_statements).to all(match(expected_sql))
end
end
end
describe '#deserialize' do
specify 'it deserializes type correctly', :aggregate_failures do
types.each do |type|
service = create(:integration, type: type)
expect(service.type).to eq('AsanaService')
end
end
end
describe '#cast' do
it 'casts type as model correctly', :aggregate_failures do
create(:integration, type: 'AsanaService')
types.each do |type|
expect(Integration.find_by(type: type)).to be_kind_of(Integrations::Asana)
end
end
end
describe '#changed?' do
it 'detects changes correctly', :aggregate_failures do
service = create(:integration, type: 'AsanaService')
types.each do |type|
service.type = type
expect(service).not_to be_changed
end
service.type = 'NewType'
expect(service).to be_changed
end
end
end
...@@ -85,14 +85,14 @@ RSpec.describe Integration do ...@@ -85,14 +85,14 @@ RSpec.describe Integration do
subject { described_class.by_type(type) } subject { described_class.by_type(type) }
context 'when type is "JiraService"' do context 'when type is "Integrations::JiraService"' do
let(:type) { 'JiraService' } let(:type) { 'Integrations::Jira' }
it { is_expected.to match_array([integration1, integration2]) } it { is_expected.to match_array([integration1, integration2]) }
end end
context 'when type is "RedmineService"' do context 'when type is "Integrations::Redmine"' do
let(:type) { 'RedmineService' } let(:type) { 'Integrations::Redmine' }
it { is_expected.to match_array([integration3]) } it { is_expected.to match_array([integration3]) }
end end
...@@ -103,7 +103,7 @@ RSpec.describe Integration do ...@@ -103,7 +103,7 @@ RSpec.describe Integration do
let!(:integration2) { create(:jira_integration) } let!(:integration2) { create(:jira_integration) }
it 'returns the right group integration' do it 'returns the right group integration' do
expect(described_class.for_group(group)).to match_array([integration1]) expect(described_class.for_group(group)).to contain_exactly(integration1)
end end
end end
...@@ -376,22 +376,24 @@ RSpec.describe Integration do ...@@ -376,22 +376,24 @@ RSpec.describe Integration do
let_it_be(:instance_integration) { create(:jira_integration, :instance) } let_it_be(:instance_integration) { create(:jira_integration, :instance) }
it 'returns the instance integration' do it 'returns the instance integration' do
expect(described_class.default_integration('JiraService', project)).to eq(instance_integration) expect(described_class.default_integration('Integrations::Jira', project)).to eq(instance_integration)
end end
it 'returns nil for nonexistent integration type' do it 'returns nil for nonexistent integration type' do
expect(described_class.default_integration('HipchatService', project)).to eq(nil) expect(described_class.default_integration('Integrations::Hipchat', project)).to eq(nil)
end end
context 'with a group integration' do context 'with a group integration' do
let(:integration_name) { 'Integrations::Jira' }
let_it_be(:group_integration) { create(:jira_integration, group_id: group.id, project_id: nil) } let_it_be(:group_integration) { create(:jira_integration, group_id: group.id, project_id: nil) }
it 'returns the group integration for a project' do it 'returns the group integration for a project' do
expect(described_class.default_integration('JiraService', project)).to eq(group_integration) expect(described_class.default_integration(integration_name, project)).to eq(group_integration)
end end
it 'returns the instance integration for a group' do it 'returns the instance integration for a group' do
expect(described_class.default_integration('JiraService', group)).to eq(instance_integration) expect(described_class.default_integration(integration_name, group)).to eq(instance_integration)
end end
context 'with a subgroup' do context 'with a subgroup' do
...@@ -400,18 +402,18 @@ RSpec.describe Integration do ...@@ -400,18 +402,18 @@ RSpec.describe Integration do
let!(:project) { create(:project, group: subgroup) } let!(:project) { create(:project, group: subgroup) }
it 'returns the closest group integration for a project' do it 'returns the closest group integration for a project' do
expect(described_class.default_integration('JiraService', project)).to eq(group_integration) expect(described_class.default_integration(integration_name, project)).to eq(group_integration)
end end
it 'returns the closest group integration for a subgroup' do it 'returns the closest group integration for a subgroup' do
expect(described_class.default_integration('JiraService', subgroup)).to eq(group_integration) expect(described_class.default_integration(integration_name, subgroup)).to eq(group_integration)
end end
context 'having a integration with custom settings' do context 'having a integration with custom settings' do
let!(:subgroup_integration) { create(:jira_integration, group_id: subgroup.id, project_id: nil) } let!(:subgroup_integration) { create(:jira_integration, group_id: subgroup.id, project_id: nil) }
it 'returns the closest group integration for a project' do it 'returns the closest group integration for a project' do
expect(described_class.default_integration('JiraService', project)).to eq(subgroup_integration) expect(described_class.default_integration(integration_name, project)).to eq(subgroup_integration)
end end
end end
...@@ -419,7 +421,7 @@ RSpec.describe Integration do ...@@ -419,7 +421,7 @@ RSpec.describe Integration do
let!(:subgroup_integration) { create(:jira_integration, group_id: subgroup.id, project_id: nil, inherit_from_id: group_integration.id) } let!(:subgroup_integration) { create(:jira_integration, group_id: subgroup.id, project_id: nil, inherit_from_id: group_integration.id) }
it 'returns the closest group integration which does not inherit from its parent for a project' do it 'returns the closest group integration which does not inherit from its parent for a project' do
expect(described_class.default_integration('JiraService', project)).to eq(group_integration) expect(described_class.default_integration(integration_name, project)).to eq(group_integration)
end end
end end
end end
...@@ -556,19 +558,26 @@ RSpec.describe Integration do ...@@ -556,19 +558,26 @@ RSpec.describe Integration do
end end
end end
describe '.integration_name_to_model' do describe '.integration_name_to_type' do
it 'returns the model for the given integration name' do it 'handles a simple case' do
expect(described_class.integration_name_to_model('asana')).to eq(Integrations::Asana) expect(described_class.integration_name_to_type(:asana)).to eq 'Integrations::Asana'
end end
it 'raises an error if integration name is invalid' do it 'raises an error if the name is unknown' do
expect { described_class.integration_name_to_model('foo') }.to raise_exception(NameError, /uninitialized constant FooService/) expect { described_class.integration_name_to_type('foo') }
.to raise_exception(described_class::UnknownType, /foo/)
end
it 'handles all available_integration_names' do
types = described_class.available_integration_names.map { described_class.integration_name_to_type(_1) }
expect(types).to all(start_with('Integrations::'))
end end
end end
describe '.integration_name_to_type' do describe '.integration_name_to_model' do
it 'transforms the name to a type' do it 'raises an error if integration name is invalid' do
expect(described_class.integration_name_to_type('asana')).to eq('AsanaService') expect { described_class.integration_name_to_model('foo') }.to raise_exception(described_class::UnknownType, /foo/)
end end
end end
......
...@@ -7,7 +7,7 @@ RSpec.describe Integrations::Slack do ...@@ -7,7 +7,7 @@ RSpec.describe Integrations::Slack do
describe '#execute' do describe '#execute' do
before do before do
stub_request(:post, "https://slack.service.url/") stub_request(:post, slack_integration.webhook)
end end
let_it_be(:slack_integration) { create(:integrations_slack, branches_to_be_notified: 'all') } let_it_be(:slack_integration) { create(:integrations_slack, branches_to_be_notified: 'all') }
......
...@@ -1470,7 +1470,7 @@ RSpec.describe Project, factory_default: :keep do ...@@ -1470,7 +1470,7 @@ RSpec.describe Project, factory_default: :keep do
context 'when there is an active external issue tracker integration' do context 'when there is an active external issue tracker integration' do
let!(:integration) do let!(:integration) do
create(:integration, project: project, type: 'JiraService', category: 'issue_tracker', active: true) create(:jira_integration, project: project, category: 'issue_tracker')
end end
specify { is_expected.to eq(true) } specify { is_expected.to eq(true) }
...@@ -1489,7 +1489,7 @@ RSpec.describe Project, factory_default: :keep do ...@@ -1489,7 +1489,7 @@ RSpec.describe Project, factory_default: :keep do
context 'when there are two active external issue tracker integrations' do context 'when there are two active external issue tracker integrations' do
let_it_be(:second_integration) do let_it_be(:second_integration) do
create(:integration, project: project, type: 'CustomIssueTracker', category: 'issue_tracker', active: true) create(:custom_issue_tracker_integration, project: project, category: 'issue_tracker')
end end
it 'does not become false when external issue tracker integration is destroyed' do it 'does not become false when external issue tracker integration is destroyed' do
......
...@@ -16,6 +16,7 @@ RSpec.describe 'query Jira service' do ...@@ -16,6 +16,7 @@ RSpec.describe 'query Jira service' do
services(active: true, type: JIRA_SERVICE) { services(active: true, type: JIRA_SERVICE) {
nodes { nodes {
type type
serviceType
} }
} }
} }
...@@ -23,7 +24,7 @@ RSpec.describe 'query Jira service' do ...@@ -23,7 +24,7 @@ RSpec.describe 'query Jira service' do
) )
end end
let(:services) { graphql_data.dig('project', 'services', 'nodes')} let(:services) { graphql_data.dig('project', 'services', 'nodes') }
it_behaves_like 'unauthorized users cannot read services' it_behaves_like 'unauthorized users cannot read services'
...@@ -35,10 +36,8 @@ RSpec.describe 'query Jira service' do ...@@ -35,10 +36,8 @@ RSpec.describe 'query Jira service' do
it_behaves_like 'a working graphql query' it_behaves_like 'a working graphql query'
it 'retuns list of jira imports' do it 'returns list of jira integrations' do
service = services.first expect(services).to contain_exactly({ 'type' => 'JiraService', 'serviceType' => 'JIRA_SERVICE' })
expect(service['type']).to eq('JiraService')
end end
end end
end end
...@@ -13,7 +13,7 @@ RSpec.describe Deployments::HooksWorker do ...@@ -13,7 +13,7 @@ RSpec.describe Deployments::HooksWorker do
it 'executes project services for deployment_hooks' do it 'executes project services for deployment_hooks' do
deployment = create(:deployment, :running) deployment = create(:deployment, :running)
project = deployment.project project = deployment.project
service = create(:integration, type: 'SlackService', project: project, deployment_events: true, active: true) service = create(:integrations_slack, project: project, deployment_events: true)
expect(ProjectServiceWorker).to receive(:perform_async).with(service.id, an_instance_of(Hash)) expect(ProjectServiceWorker).to receive(:perform_async).with(service.id, an_instance_of(Hash))
...@@ -23,7 +23,7 @@ RSpec.describe Deployments::HooksWorker do ...@@ -23,7 +23,7 @@ RSpec.describe Deployments::HooksWorker do
it 'does not execute an inactive service' do it 'does not execute an inactive service' do
deployment = create(:deployment, :running) deployment = create(:deployment, :running)
project = deployment.project project = deployment.project
create(:integration, type: 'SlackService', project: project, deployment_events: true, active: false) create(:integrations_slack, project: project, deployment_events: true, active: false)
expect(ProjectServiceWorker).not_to receive(:perform_async) expect(ProjectServiceWorker).not_to receive(:perform_async)
......
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