Commit d03f4c72 authored by Alex Kalderimis's avatar Alex Kalderimis

Change a number of meta-methods

This changes a number of ancillary methods with 'service' in them to
refer to 'integration' instead.

hamllint would have us prefer format to percent formatting.

This updates the spec definitions to use the new factory names. Rather
than inferring the factory names, they are provided explicitly.
parent fa426857
...@@ -12,19 +12,19 @@ module Resolvers ...@@ -12,19 +12,19 @@ module Resolvers
argument :active, argument :active,
GraphQL::BOOLEAN_TYPE, GraphQL::BOOLEAN_TYPE,
required: false, required: false,
description: 'Indicates if the service is active.' description: 'Indicates if the integration is active.'
argument :type, argument :type,
Types::Projects::ServiceTypeEnum, Types::Projects::ServiceTypeEnum,
required: false, required: false,
description: 'Class name of the service.' description: 'Type of integration.'
alias_method :project, :object alias_method :project, :object
def resolve(active: nil, type: nil) def resolve(active: nil, type: nil)
servs = project.integrations items = project.integrations
servs = servs.by_active_flag(active) unless active.nil? items = items.by_active_flag(active) unless active.nil?
servs = servs.by_type(type) unless type.blank? items = items.by_type(type) unless type.blank?
servs items
end end
end end
end end
......
...@@ -5,14 +5,8 @@ module Types ...@@ -5,14 +5,8 @@ module Types
class ServiceTypeEnum < BaseEnum class ServiceTypeEnum < BaseEnum
graphql_name 'ServiceType' graphql_name 'ServiceType'
::Integration.available_services_types(include_dev: false).each do |service_type| ::Integration.available_services_types(include_dev: false).each do |type|
replacement = Integration.integration_type_for_service_type(service_type) value type.underscore.upcase, value: type, description: "#{type} type"
value service_type.underscore.upcase, value: service_type, description: "#{service_type} type"
end
::Integration.available_integration_types(include_dev: false).each do |type|
value type.underscore.upcase, value: type, description: "#{type} integration"
end end
end end
end end
......
...@@ -201,7 +201,7 @@ class Integration < ApplicationRecord ...@@ -201,7 +201,7 @@ class Integration < ApplicationRecord
def self.find_or_initialize_non_project_specific_integration(name, instance: false, group_id: nil) def self.find_or_initialize_non_project_specific_integration(name, instance: false, group_id: nil)
return unless name.in?(available_services_names(include_project_specific: false)) return unless name.in?(available_services_names(include_project_specific: false))
service_name_to_model(name).find_or_initialize_by(instance: instance, group_id: group_id) integration_name_to_model(name).find_or_initialize_by(instance: instance, group_id: group_id)
end end
def self.find_or_initialize_all_non_project_specific(scope) def self.find_or_initialize_all_non_project_specific(scope)
...@@ -210,7 +210,7 @@ class Integration < ApplicationRecord ...@@ -210,7 +210,7 @@ class Integration < ApplicationRecord
def self.build_nonexistent_services_for(scope) def self.build_nonexistent_services_for(scope)
nonexistent_services_types_for(scope).map do |service_type| nonexistent_services_types_for(scope).map do |service_type|
service_type_to_model(service_type).new integration_type_to_model(service_type).new
end end
end end
private_class_method :build_nonexistent_services_for private_class_method :build_nonexistent_services_for
...@@ -235,11 +235,6 @@ class Integration < ApplicationRecord ...@@ -235,11 +235,6 @@ class Integration < ApplicationRecord
service_names.sort_by(&:downcase) service_names.sort_by(&:downcase)
end end
def self.available_integration_names(**args)
available_services_names(**args)
.select { RENAMED_TO_INTEGRATION.include?(_1) }
end
def self.integration_names def self.integration_names
INTEGRATION_NAMES INTEGRATION_NAMES
end end
...@@ -262,45 +257,29 @@ class Integration < ApplicationRecord ...@@ -262,45 +257,29 @@ class Integration < ApplicationRecord
# Example: ["AsanaService", ...] # Example: ["AsanaService", ...]
def self.available_services_types(include_project_specific: true, include_dev: true) def self.available_services_types(include_project_specific: true, include_dev: true)
available_services_names(include_project_specific: include_project_specific, include_dev: include_dev).map do |service_name| available_services_names(include_project_specific: include_project_specific, include_dev: include_dev).map do |service_name|
service_name_to_type(service_name) integration_name_to_type(service_name)
end
end
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 |name|
integration_name_to_type(name)
end
end end
def self.integration_type_for_service_type(type_name)
name = type_name.chomp(type_name)
integration_name_to_type(name) if RENAMED_TO_INTEGRATION.include?(name)
end end
# Returns the model for the given service name. # Returns the model for the given service name.
# Example: "asana" => Integrations::Asana # Example: "asana" => Integrations::Asana
def self.service_name_to_model(name) def self.integration_name_to_model(name)
type = service_name_to_type(name) type = integration_name_to_type(name)
service_type_to_model(type) integration_type_to_model(type)
end end
# Returns the STI type for the given service name. # Returns the STI type for the given service name.
# Example: "asana" => "AsanaService" # Example: "asana" => "AsanaService"
def self.service_name_to_type(name)
"#{name}_service".camelize
end
def self.integration_name_to_type(name) def self.integration_name_to_type(name)
"#{name}_integration".camelize "#{name}_service".camelize
end end
# Returns the model for the given STI type. # Returns the model for the given STI type.
# Example: "AsanaService" => Integrations::Asana # Example: "AsanaService" => Integrations::Asana
def self.service_type_to_model(type) def self.integration_type_to_model(type)
Gitlab::Integrations::StiType.new.cast(type).constantize Gitlab::Integrations::StiType.new.cast(type).constantize
end end
private_class_method :service_type_to_model private_class_method :integration_type_to_model
def self.build_from_integration(integration, project_id: nil, group_id: nil) def self.build_from_integration(integration, project_id: nil, group_id: nil)
new_integration = integration.dup new_integration = integration.dup
......
...@@ -2645,7 +2645,7 @@ class Project < ApplicationRecord ...@@ -2645,7 +2645,7 @@ class Project < ApplicationRecord
end end
def build_service(name) def build_service(name)
Integration.service_name_to_model(name).new(project_id: id) Integration.integration_name_to_model(name).new(project_id: id)
end end
def services_templates def services_templates
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
= s_('WikiEmpty|Confluence is enabled') = s_('WikiEmpty|Confluence is enabled')
%p %p
- wiki_confluence_epic_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/3629' - wiki_confluence_epic_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/3629'
- wiki_confluence_epic_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: wiki_confluence_epic_link_url } - wiki_confluence_epic_link_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe, url: wiki_confluence_epic_link_url)
= s_("WikiEmpty|You've enabled the Confluence Workspace integration. Your wiki will be viewable directly within Confluence. We are hard at work integrating Confluence more seamlessly into GitLab. If you'd like to stay up to date, follow our %{wiki_confluence_epic_link_start}Confluence epic%{wiki_confluence_epic_link_end}.").html_safe % { wiki_confluence_epic_link_start: wiki_confluence_epic_link_start, wiki_confluence_epic_link_end: '</a>'.html_safe } = format(s_("WikiEmpty|You've enabled the Confluence Workspace integration. Your wiki will be viewable directly within Confluence. We are hard at work integrating Confluence more seamlessly into GitLab. If you'd like to stay up to date, follow our %{wiki_confluence_epic_link_start}Confluence epic%{wiki_confluence_epic_link_end}.").html_safe, wiki_confluence_epic_link_start: wiki_confluence_epic_link_start, wiki_confluence_epic_link_end: '</a>'.html_safe)
= link_to @project.confluence_integration.confluence_url, target: '_blank', rel: 'noopener noreferrer', class: 'gl-button btn btn-success external-url', title: s_('WikiEmpty|Go to Confluence') do = link_to @project.confluence_integration.confluence_url, target: '_blank', rel: 'noopener noreferrer', class: 'gl-button btn btn-success external-url', title: s_('WikiEmpty|Go to Confluence') do
= sprite_icon('external-link') = sprite_icon('external-link')
= s_('WikiEmpty|Go to Confluence') = s_('WikiEmpty|Go to Confluence')
......
...@@ -11928,8 +11928,8 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -11928,8 +11928,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="projectservicesactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the service is active. | | <a id="projectservicesactive"></a>`active` | [`Boolean`](#boolean) | Indicates if the integration is active. |
| <a id="projectservicestype"></a>`type` | [`ServiceType`](#servicetype) | Class name of the service. | | <a id="projectservicestype"></a>`type` | [`ServiceType`](#servicetype) | Type of integration. |
##### `Project.snippets` ##### `Project.snippets`
...@@ -14852,18 +14852,12 @@ State of a Sentry error. ...@@ -14852,18 +14852,12 @@ State of a Sentry error.
| Value | Description | | Value | Description |
| ----- | ----------- | | ----- | ----------- |
| <a id="servicetypeasana_integration"></a>`ASANA_INTEGRATION` | AsanaIntegration integration. |
| <a id="servicetypeasana_service"></a>`ASANA_SERVICE` | AsanaService type. | | <a id="servicetypeasana_service"></a>`ASANA_SERVICE` | AsanaService type. |
| <a id="servicetypeassembla_integration"></a>`ASSEMBLA_INTEGRATION` | AssemblaIntegration integration. |
| <a id="servicetypeassembla_service"></a>`ASSEMBLA_SERVICE` | AssemblaService type. | | <a id="servicetypeassembla_service"></a>`ASSEMBLA_SERVICE` | AssemblaService type. |
| <a id="servicetypebamboo_integration"></a>`BAMBOO_INTEGRATION` | BambooIntegration integration. |
| <a id="servicetypebamboo_service"></a>`BAMBOO_SERVICE` | BambooService type. | | <a id="servicetypebamboo_service"></a>`BAMBOO_SERVICE` | BambooService type. |
| <a id="servicetypebugzilla_integration"></a>`BUGZILLA_INTEGRATION` | BugzillaIntegration integration. |
| <a id="servicetypebugzilla_service"></a>`BUGZILLA_SERVICE` | BugzillaService type. | | <a id="servicetypebugzilla_service"></a>`BUGZILLA_SERVICE` | BugzillaService type. |
| <a id="servicetypebuildkite_service"></a>`BUILDKITE_SERVICE` | BuildkiteService type. | | <a id="servicetypebuildkite_service"></a>`BUILDKITE_SERVICE` | BuildkiteService type. |
| <a id="servicetypecampfire_integration"></a>`CAMPFIRE_INTEGRATION` | CampfireIntegration integration. |
| <a id="servicetypecampfire_service"></a>`CAMPFIRE_SERVICE` | CampfireService type. | | <a id="servicetypecampfire_service"></a>`CAMPFIRE_SERVICE` | CampfireService type. |
| <a id="servicetypeconfluence_integration"></a>`CONFLUENCE_INTEGRATION` | ConfluenceIntegration integration. |
| <a id="servicetypeconfluence_service"></a>`CONFLUENCE_SERVICE` | ConfluenceService type. | | <a id="servicetypeconfluence_service"></a>`CONFLUENCE_SERVICE` | ConfluenceService type. |
| <a id="servicetypecustom_issue_tracker_service"></a>`CUSTOM_ISSUE_TRACKER_SERVICE` | CustomIssueTrackerService type. | | <a id="servicetypecustom_issue_tracker_service"></a>`CUSTOM_ISSUE_TRACKER_SERVICE` | CustomIssueTrackerService type. |
| <a id="servicetypedatadog_service"></a>`DATADOG_SERVICE` | DatadogService type. | | <a id="servicetypedatadog_service"></a>`DATADOG_SERVICE` | DatadogService type. |
......
...@@ -403,15 +403,15 @@ module Gitlab ...@@ -403,15 +403,15 @@ module Gitlab
def services_usage def services_usage
# rubocop: disable UsageData/LargeTable: # rubocop: disable UsageData/LargeTable:
Integration.available_services_names(include_dev: false).each_with_object({}) do |service_name, response| Integration.available_services_names(include_dev: false).each_with_object({}) do |name, response|
service_type = Integration.service_name_to_type(service_name) type = Integration.integration_name_to_type(name)
response["projects_#{service_name}_active".to_sym] = count(Integration.active.where.not(project: nil).where(type: service_type)) response[:"projects_#{name}_active"] = count(Integration.active.where.not(project: nil).where(type: type))
response["groups_#{service_name}_active".to_sym] = count(Integration.active.where.not(group: nil).where(type: service_type)) response[:"groups_#{name}_active"] = count(Integration.active.where.not(group: nil).where(type: type))
response["templates_#{service_name}_active".to_sym] = count(Integration.active.where(template: true, type: service_type)) response[:"templates_#{name}_active"] = count(Integration.active.where(template: true, type: type))
response["instances_#{service_name}_active".to_sym] = count(Integration.active.where(instance: true, type: service_type)) response[:"instances_#{name}_active"] = count(Integration.active.where(instance: true, type: type))
response["projects_inheriting_#{service_name}_active".to_sym] = count(Integration.active.where.not(project: nil).where.not(inherit_from_id: nil).where(type: service_type)) response[:"projects_inheriting_#{name}_active"] = count(Integration.active.where.not(project: nil).where.not(inherit_from_id: nil).where(type: type))
response["groups_inheriting_#{service_name}_active".to_sym] = count(Integration.active.where.not(group: nil).where.not(inherit_from_id: nil).where(type: service_type)) response[:"groups_inheriting_#{name}_active"] = count(Integration.active.where.not(group: nil).where.not(inherit_from_id: nil).where(type: type))
end.merge(jira_usage, jira_import_usage) end.merge(jira_usage, jira_import_usage)
# rubocop: enable UsageData/LargeTable: # rubocop: enable UsageData/LargeTable:
end end
......
...@@ -3,13 +3,11 @@ ...@@ -3,13 +3,11 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['ServiceType'] do RSpec.describe GitlabSchema.types['ServiceType'] do
specify { expect(described_class.graphql_name).to eq('ServiceType') }
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 match_array(available_services_enum)
end end
end
def available_services_enum def available_services_enum
::Integration.available_services_types(include_dev: false).map(&:underscore).map(&:upcase) ::Integration.available_services_types(include_dev: false).map(&:underscore).map(&:upcase)
end
end end
...@@ -4822,7 +4822,7 @@ RSpec.describe Ci::Build do ...@@ -4822,7 +4822,7 @@ RSpec.describe Ci::Build do
context 'with project services' do context 'with project services' do
before do before do
create(:service, active: true, job_events: true, project: project) create(:integration, active: true, job_events: true, project: project)
end end
it 'executes services' do it 'executes services' do
...@@ -4836,7 +4836,7 @@ RSpec.describe Ci::Build do ...@@ -4836,7 +4836,7 @@ RSpec.describe Ci::Build do
context 'without relevant project services' do context 'without relevant project services' do
before do before do
create(:service, active: true, job_events: false, project: project) create(:integration, active: true, job_events: false, project: project)
end end
it 'does not execute services' do it 'does not execute services' do
......
...@@ -160,7 +160,7 @@ RSpec.describe Integration do ...@@ -160,7 +160,7 @@ RSpec.describe Integration do
context 'when instance-level service' do context 'when instance-level service' do
Integration.available_services_types.each do |service_type| Integration.available_services_types.each do |service_type|
let(:service) do let(:service) do
described_class.send(:service_type_to_model, service_type).new(instance: true) described_class.send(:integration_type_to_model, service_type).new(instance: true)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
...@@ -170,7 +170,7 @@ RSpec.describe Integration do ...@@ -170,7 +170,7 @@ RSpec.describe Integration do
context 'when group-level service' do context 'when group-level service' do
Integration.available_services_types.each do |service_type| Integration.available_services_types.each do |service_type|
let(:service) do let(:service) do
described_class.send(:service_type_to_model, service_type).new(group_id: group.id) described_class.send(:integration_type_to_model, service_type).new(group_id: group.id)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
...@@ -668,16 +668,16 @@ RSpec.describe Integration do ...@@ -668,16 +668,16 @@ RSpec.describe Integration do
end end
end end
describe '.service_name_to_model' do describe '.integration_name_to_model' do
it 'returns the model for the given service name', :aggregate_failures do it 'returns the model for the given service name', :aggregate_failures do
expect(described_class.service_name_to_model('asana')).to eq(Integrations::Asana) expect(described_class.integration_name_to_model('asana')).to eq(Integrations::Asana)
# TODO We can remove this test when all models have been namespaced: # TODO We can remove this test when all models have been namespaced:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60968#note_570994955 # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60968#note_570994955
expect(described_class.service_name_to_model('prometheus')).to eq(PrometheusService) expect(described_class.integration_name_to_model('prometheus')).to eq(PrometheusService)
end end
it 'raises an error if service name is invalid' do it 'raises an error if service name is invalid' do
expect { described_class.service_name_to_model('foo') }.to raise_exception(NameError, /uninitialized constant FooService/) expect { described_class.integration_name_to_model('foo') }.to raise_exception(NameError, /uninitialized constant FooService/)
end end
end end
......
...@@ -251,22 +251,22 @@ RSpec.describe MergeRequests::BuildService do ...@@ -251,22 +251,22 @@ RSpec.describe MergeRequests::BuildService do
end end
context 'when the source branch matches an issue' do context 'when the source branch matches an issue' do
where(:issue_tracker, :source_branch, :closing_message) do where(:factory, :source_branch, :closing_message) do
:jira | 'FOO-123-fix-issue' | 'Closes FOO-123' :jira_service | 'FOO-123-fix-issue' | 'Closes FOO-123'
:jira | 'fix-issue' | nil :jira_service | 'fix-issue' | nil
:custom_issue_tracker | '123-fix-issue' | 'Closes #123' :custom_issue_tracker_integration | '123-fix-issue' | 'Closes #123'
:custom_issue_tracker | 'fix-issue' | nil :custom_issue_tracker_integration | 'fix-issue' | nil
:internal | '123-fix-issue' | 'Closes #123' nil | '123-fix-issue' | 'Closes #123'
:internal | 'fix-issue' | nil nil | 'fix-issue' | nil
end end
with_them do with_them do
before do before do
if issue_tracker == :internal if factory
issue.update!(iid: 123) create(factory, project: project)
else
create(:"#{issue_tracker}_service", project: project)
project.reload project.reload
else
issue.update!(iid: 123)
end end
end end
...@@ -350,23 +350,23 @@ RSpec.describe MergeRequests::BuildService do ...@@ -350,23 +350,23 @@ RSpec.describe MergeRequests::BuildService do
end end
context 'when the source branch matches an issue' do context 'when the source branch matches an issue' do
where(:issue_tracker, :source_branch, :title, :closing_message) do where(:factory, :source_branch, :title, :closing_message) do
:jira | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123' :jira_service | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123'
:jira | 'fix-issue' | 'Fix issue' | nil :jira_service | 'fix-issue' | 'Fix issue' | nil
:custom_issue_tracker | '123-fix-issue' | 'Resolve #123 "Fix issue"' | 'Closes #123' :custom_issue_tracker_integration | '123-fix-issue' | 'Resolve #123 "Fix issue"' | 'Closes #123'
:custom_issue_tracker | 'fix-issue' | 'Fix issue' | nil :custom_issue_tracker_integration | 'fix-issue' | 'Fix issue' | nil
:internal | '123-fix-issue' | 'Resolve "A bug"' | 'Closes #123' nil | '123-fix-issue' | 'Resolve "A bug"' | 'Closes #123'
:internal | 'fix-issue' | 'Fix issue' | nil nil | 'fix-issue' | 'Fix issue' | nil
:internal | '124-fix-issue' | '124 fix issue' | nil nil | '124-fix-issue' | '124 fix issue' | nil
end end
with_them do with_them do
before do before do
if issue_tracker == :internal if factory
issue.update!(iid: 123) create(factory, project: project)
else
create(:"#{issue_tracker}_service", project: project)
project.reload project.reload
else
issue.update!(iid: 123)
end end
end end
...@@ -399,23 +399,23 @@ RSpec.describe MergeRequests::BuildService do ...@@ -399,23 +399,23 @@ RSpec.describe MergeRequests::BuildService do
end end
context 'when the source branch matches an issue' do context 'when the source branch matches an issue' do
where(:issue_tracker, :source_branch, :title, :closing_message) do where(:factory, :source_branch, :title, :closing_message) do
:jira | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123' :jira_service | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123'
:jira | 'fix-issue' | 'Fix issue' | nil :jira_service | 'fix-issue' | 'Fix issue' | nil
:custom_issue_tracker | '123-fix-issue' | 'Resolve #123 "Fix issue"' | 'Closes #123' :custom_issue_tracker_integration | '123-fix-issue' | 'Resolve #123 "Fix issue"' | 'Closes #123'
:custom_issue_tracker | 'fix-issue' | 'Fix issue' | nil :custom_issue_tracker_integration | 'fix-issue' | 'Fix issue' | nil
:internal | '123-fix-issue' | 'Resolve "A bug"' | 'Closes #123' nil | '123-fix-issue' | 'Resolve "A bug"' | 'Closes #123'
:internal | 'fix-issue' | 'Fix issue' | nil nil | 'fix-issue' | 'Fix issue' | nil
:internal | '124-fix-issue' | '124 fix issue' | nil nil | '124-fix-issue' | '124 fix issue' | nil
end end
with_them do with_them do
before do before do
if issue_tracker == :internal if factory
issue.update!(iid: 123) create(factory, project: project)
else
create(:"#{issue_tracker}_service", project: project)
project.reload project.reload
else
issue.update!(iid: 123)
end end
end end
......
# frozen_string_literal: true
RSpec::Matchers.define :be_one_of do |collection|
match do |item|
expect(collection).to include(item)
end
failure_message do |item|
"expected #{item} to be one of #{collection}"
end
end
...@@ -5,8 +5,8 @@ Integration.available_services_names.each do |service| ...@@ -5,8 +5,8 @@ Integration.available_services_names.each do |service|
include JiraServiceHelper if service == 'jira' include JiraServiceHelper if service == 'jira'
let(:dashed_service) { service.dasherize } let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym } let(:service_method) { integration_association(service) }
let(:service_klass) { Integration.service_name_to_model(service) } let(:service_klass) { Integration.integration_name_to_model(service) }
let(:service_instance) { service_klass.new } let(:service_instance) { service_klass.new }
let(:service_fields) { service_instance.fields } let(:service_fields) { service_instance.fields }
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } } let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
...@@ -57,6 +57,16 @@ Integration.available_services_names.each do |service| ...@@ -57,6 +57,16 @@ Integration.available_services_names.each do |service|
service_item service_item
end end
# Returns the association name for the given integration.
# Example: 'asana' => 'asana_integration'
def integration_association(name)
if Integration::RENAMED_TO_INTEGRATION.include?(name)
"#{name}_integration".to_sym
else
"#{name}_service".to_sym
end
end
private private
def enable_license_for_service(service) def enable_license_for_service(service)
......
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