Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
bcc77054ee
commit
1ca9950d5f
|
|
@ -7,7 +7,7 @@ class Admin::ServicesController < Admin::ApplicationController
|
|||
before_action :service, only: [:edit, :update]
|
||||
|
||||
def index
|
||||
@services = instance_level_services
|
||||
@services = services_templates
|
||||
end
|
||||
|
||||
def edit
|
||||
|
|
@ -19,7 +19,7 @@ class Admin::ServicesController < Admin::ApplicationController
|
|||
|
||||
def update
|
||||
if service.update(service_params[:service])
|
||||
PropagateInstanceLevelServiceWorker.perform_async(service.id) if service.active?
|
||||
PropagateServiceTemplateWorker.perform_async(service.id) if service.active?
|
||||
|
||||
redirect_to admin_application_settings_services_path,
|
||||
notice: 'Application settings saved successfully'
|
||||
|
|
@ -31,17 +31,17 @@ class Admin::ServicesController < Admin::ApplicationController
|
|||
private
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def instance_level_services
|
||||
def services_templates
|
||||
Service.available_services_names.map do |service_name|
|
||||
service = "#{service_name}_service".camelize.constantize
|
||||
service.where(instance: true).first_or_create
|
||||
service_template = "#{service_name}_service".camelize.constantize
|
||||
service_template.where(template: true).first_or_create
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def service
|
||||
@service ||= Service.where(id: params[:id], instance: true).first
|
||||
@service ||= Service.where(id: params[:id], template: true).first
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@ module Projects
|
|||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
functions = finder.execute
|
||||
functions = finder.execute.select do |function|
|
||||
can?(@current_user, :read_cluster, function.cluster)
|
||||
end
|
||||
|
||||
serialized_functions = serialize_function(functions)
|
||||
|
||||
render json: {
|
||||
knative_installed: finder.knative_installed,
|
||||
functions: serialize_function(functions)
|
||||
functions: serialized_functions
|
||||
}.to_json
|
||||
end
|
||||
|
||||
|
|
@ -23,11 +27,14 @@ module Projects
|
|||
end
|
||||
|
||||
def show
|
||||
@service = serialize_function(finder.service(params[:environment_id], params[:id]))
|
||||
@prometheus = finder.has_prometheus?(params[:environment_id])
|
||||
function = finder.service(params[:environment_id], params[:id])
|
||||
return not_found unless function && can?(@current_user, :read_cluster, function.cluster)
|
||||
|
||||
@service = serialize_function(function)
|
||||
return not_found if @service.nil?
|
||||
|
||||
@prometheus = finder.has_prometheus?(params[:environment_id])
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: @service
|
||||
|
|
|
|||
|
|
@ -93,24 +93,32 @@ module Projects
|
|||
.services
|
||||
.select { |svc| svc["metadata"]["name"] == name }
|
||||
|
||||
add_metadata(finder, services).first unless services.nil?
|
||||
attributes = add_metadata(finder, services).first
|
||||
next unless attributes
|
||||
|
||||
Gitlab::Serverless::Service.new(attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def knative_services
|
||||
services_finders.map do |finder|
|
||||
services = finder.services
|
||||
attributes = add_metadata(finder, finder.services)
|
||||
|
||||
add_metadata(finder, services) unless services.nil?
|
||||
attributes&.map do |attributes|
|
||||
Gitlab::Serverless::Service.new(attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_metadata(finder, services)
|
||||
return if services.nil?
|
||||
|
||||
add_pod_count = services.one?
|
||||
|
||||
services.each do |s|
|
||||
s["environment_scope"] = finder.cluster.environment_scope
|
||||
s["cluster_id"] = finder.cluster.id
|
||||
s["environment"] = finder.environment
|
||||
s["cluster"] = finder.cluster
|
||||
|
||||
if add_pod_count
|
||||
s["podcount"] = finder
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ module Types
|
|||
description: 'Blob highlighted data',
|
||||
null: true
|
||||
|
||||
field :plain_highlighted_data, GraphQL::STRING_TYPE,
|
||||
description: 'Blob plain highlighted data',
|
||||
null: true
|
||||
|
||||
field :raw_path, GraphQL::STRING_TYPE,
|
||||
description: 'Blob raw content endpoint path',
|
||||
null: false
|
||||
|
|
|
|||
|
|
@ -290,6 +290,12 @@ module Clusters
|
|||
end
|
||||
end
|
||||
|
||||
def serverless_domain
|
||||
strong_memoize(:serverless_domain) do
|
||||
self.application_knative&.serverless_domain_cluster
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unique_management_project_environment_scope
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ class PagesDomain < ApplicationRecord
|
|||
|
||||
scope :for_removal, -> { where("remove_at < ?", Time.now) }
|
||||
|
||||
scope :with_logging_info, -> { includes(project: [:namespace, :route]) }
|
||||
|
||||
def verified?
|
||||
!!verified_at
|
||||
end
|
||||
|
|
@ -285,3 +287,5 @@ class PagesDomain < ApplicationRecord
|
|||
!auto_ssl_enabled? && project&.pages_https_only?
|
||||
end
|
||||
end
|
||||
|
||||
PagesDomain.prepend_if_ee('::EE::PagesDomain')
|
||||
|
|
|
|||
|
|
@ -1224,13 +1224,13 @@ class Project < ApplicationRecord
|
|||
service = find_service(services, name)
|
||||
return service if service
|
||||
|
||||
# We should check if an instance-level service exists
|
||||
instance_level_service = find_service(instance_level_services, name)
|
||||
# We should check if template for the service exists
|
||||
template = find_service(services_templates, name)
|
||||
|
||||
if instance_level_service
|
||||
Service.build_from_instance(id, instance_level_service)
|
||||
if template
|
||||
Service.build_from_template(id, template)
|
||||
else
|
||||
# If no instance-level service exists, we should create a new service. Ex `build_gitlab_ci_service`
|
||||
# If no template, we should create an instance. Ex `build_gitlab_ci_service`
|
||||
public_send("build_#{name}_service") # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
|
|
@ -2460,8 +2460,8 @@ class Project < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def instance_level_services
|
||||
@instance_level_services ||= Service.where(instance: true)
|
||||
def services_templates
|
||||
@services_templates ||= Service.where(template: true)
|
||||
end
|
||||
|
||||
def ensure_pages_metadatum
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ class IssueTrackerService < Service
|
|||
end
|
||||
|
||||
def one_issue_tracker
|
||||
return if instance?
|
||||
return if template?
|
||||
return if project.blank?
|
||||
|
||||
if project.services.external_issue_trackers.where.not(id: id).any?
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class PrometheusService < MonitoringService
|
|||
end
|
||||
|
||||
def prometheus_available?
|
||||
return false if instance?
|
||||
return false if template?
|
||||
return false unless project
|
||||
|
||||
project.all_clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class Service < ApplicationRecord
|
|||
belongs_to :project, inverse_of: :services
|
||||
has_one :service_hook
|
||||
|
||||
validates :project_id, presence: true, unless: proc { |service| service.instance? }
|
||||
validates :project_id, presence: true, unless: proc { |service| service.template? }
|
||||
validates :type, presence: true
|
||||
|
||||
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
|
||||
|
|
@ -70,8 +70,8 @@ class Service < ApplicationRecord
|
|||
true
|
||||
end
|
||||
|
||||
def instance?
|
||||
instance
|
||||
def template?
|
||||
template
|
||||
end
|
||||
|
||||
def category
|
||||
|
|
@ -299,15 +299,15 @@ class Service < ApplicationRecord
|
|||
service_names.sort_by(&:downcase)
|
||||
end
|
||||
|
||||
def self.build_from_instance(project_id, instance_level_service)
|
||||
service = instance_level_service.dup
|
||||
def self.build_from_template(project_id, template)
|
||||
service = template.dup
|
||||
|
||||
if instance_level_service.supports_data_fields?
|
||||
data_fields = instance_level_service.data_fields.dup
|
||||
if template.supports_data_fields?
|
||||
data_fields = template.data_fields.dup
|
||||
data_fields.service = service
|
||||
end
|
||||
|
||||
service.instance = false
|
||||
service.template = false
|
||||
service.project_id = project_id
|
||||
service.active = false if service.active? && !service.valid?
|
||||
service
|
||||
|
|
@ -321,6 +321,10 @@ class Service < ApplicationRecord
|
|||
nil
|
||||
end
|
||||
|
||||
def self.find_by_template
|
||||
find_by(template: true)
|
||||
end
|
||||
|
||||
# override if needed
|
||||
def supports_data_fields?
|
||||
false
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ class SnippetBlobPresenter < BlobPresenter
|
|||
def highlighted_data
|
||||
return if blob.binary?
|
||||
|
||||
if blob.rich_viewer&.partial_name == 'markup'
|
||||
blob.rendered_markup
|
||||
else
|
||||
highlight
|
||||
end
|
||||
highlight(plain: false)
|
||||
end
|
||||
|
||||
def plain_highlighted_data
|
||||
return if blob.binary?
|
||||
|
||||
highlight(plain: true)
|
||||
end
|
||||
|
||||
def raw_path
|
||||
|
|
|
|||
|
|
@ -5,91 +5,31 @@ module Projects
|
|||
class ServiceEntity < Grape::Entity
|
||||
include RequestAwareEntity
|
||||
|
||||
expose :name do |service|
|
||||
service.dig('metadata', 'name')
|
||||
end
|
||||
|
||||
expose :namespace do |service|
|
||||
service.dig('metadata', 'namespace')
|
||||
end
|
||||
|
||||
expose :environment_scope do |service|
|
||||
service.dig('environment_scope')
|
||||
end
|
||||
|
||||
expose :cluster_id do |service|
|
||||
service.dig('cluster_id')
|
||||
end
|
||||
expose :name
|
||||
expose :namespace
|
||||
expose :environment_scope
|
||||
expose :podcount
|
||||
expose :created_at
|
||||
expose :image
|
||||
expose :description
|
||||
expose :url
|
||||
|
||||
expose :detail_url do |service|
|
||||
project_serverless_path(
|
||||
request.project,
|
||||
service.dig('environment_scope'),
|
||||
service.dig('metadata', 'name'))
|
||||
end
|
||||
|
||||
expose :podcount do |service|
|
||||
service.dig('podcount')
|
||||
service.environment_scope,
|
||||
service.name)
|
||||
end
|
||||
|
||||
expose :metrics_url do |service|
|
||||
project_serverless_metrics_path(
|
||||
request.project,
|
||||
service.dig('environment_scope'),
|
||||
service.dig('metadata', 'name')) + ".json"
|
||||
service.environment_scope,
|
||||
service.name, format: :json)
|
||||
end
|
||||
|
||||
expose :created_at do |service|
|
||||
service.dig('metadata', 'creationTimestamp')
|
||||
end
|
||||
|
||||
expose :url do |service|
|
||||
knative_06_07_url(service) || knative_05_url(service)
|
||||
end
|
||||
|
||||
expose :description do |service|
|
||||
knative_07_description(service) || knative_05_06_description(service)
|
||||
end
|
||||
|
||||
expose :image do |service|
|
||||
service.dig(
|
||||
'spec',
|
||||
'runLatest',
|
||||
'configuration',
|
||||
'build',
|
||||
'template',
|
||||
'name')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def knative_07_description(service)
|
||||
service.dig(
|
||||
'spec',
|
||||
'template',
|
||||
'metadata',
|
||||
'annotations',
|
||||
'Description'
|
||||
)
|
||||
end
|
||||
|
||||
def knative_05_url(service)
|
||||
"http://#{service.dig('status', 'domain')}"
|
||||
end
|
||||
|
||||
def knative_06_07_url(service)
|
||||
service.dig('status', 'url')
|
||||
end
|
||||
|
||||
def knative_05_06_description(service)
|
||||
service.dig(
|
||||
'spec',
|
||||
'runLatest',
|
||||
'configuration',
|
||||
'revisionTemplate',
|
||||
'metadata',
|
||||
'annotations',
|
||||
'Description')
|
||||
expose :cluster_id do |service|
|
||||
service.cluster&.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ module Projects
|
|||
|
||||
if @project.save
|
||||
unless @project.gitlab_project_import?
|
||||
create_services_from_active_instance_level_services(@project)
|
||||
create_services_from_active_templates(@project)
|
||||
@project.create_labels
|
||||
end
|
||||
|
||||
|
|
@ -161,9 +161,9 @@ module Projects
|
|||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def create_services_from_active_instance_level_services(project)
|
||||
Service.where(instance: true, active: true).each do |template|
|
||||
service = Service.build_from_instance(project.id, template)
|
||||
def create_services_from_active_templates(project)
|
||||
Service.where(template: true, active: true).each do |template|
|
||||
service = Service.build_from_template(project.id, template)
|
||||
service.save!
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
class PropagateInstanceLevelService
|
||||
class PropagateServiceTemplate
|
||||
BATCH_SIZE = 100
|
||||
|
||||
def self.propagate(*args)
|
||||
new(*args).propagate
|
||||
end
|
||||
|
||||
def initialize(instance_level_service)
|
||||
@instance_level_service = instance_level_service
|
||||
def initialize(template)
|
||||
@template = template
|
||||
end
|
||||
|
||||
def propagate
|
||||
return unless @instance_level_service.active?
|
||||
return unless @template.active?
|
||||
|
||||
Rails.logger.info("Propagating services for instance_level_service #{@instance_level_service.id}") # rubocop:disable Gitlab/RailsLogger
|
||||
Rails.logger.info("Propagating services for template #{@template.id}") # rubocop:disable Gitlab/RailsLogger
|
||||
|
||||
propagate_projects_with_instance_level_service
|
||||
propagate_projects_with_template
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def propagate_projects_with_instance_level_service
|
||||
def propagate_projects_with_template
|
||||
loop do
|
||||
batch = Project.uncached { project_ids_batch }
|
||||
|
||||
bulk_create_from_instance_level_service(batch) unless batch.empty?
|
||||
bulk_create_from_template(batch) unless batch.empty?
|
||||
|
||||
break if batch.size < BATCH_SIZE
|
||||
end
|
||||
end
|
||||
|
||||
def bulk_create_from_instance_level_service(batch)
|
||||
def bulk_create_from_template(batch)
|
||||
service_list = batch.map do |project_id|
|
||||
service_hash.values << project_id
|
||||
end
|
||||
|
|
@ -52,7 +52,7 @@ module Projects
|
|||
SELECT true
|
||||
FROM services
|
||||
WHERE services.project_id = projects.id
|
||||
AND services.type = '#{@instance_level_service.type}'
|
||||
AND services.type = '#{@template.type}'
|
||||
)
|
||||
AND projects.pending_delete = false
|
||||
AND projects.archived = false
|
||||
|
|
@ -73,9 +73,9 @@ module Projects
|
|||
def service_hash
|
||||
@service_hash ||=
|
||||
begin
|
||||
instance_hash = @instance_level_service.as_json(methods: :type).except('id', 'instance', 'project_id')
|
||||
template_hash = @template.as_json(methods: :type).except('id', 'template', 'project_id')
|
||||
|
||||
instance_hash.each_with_object({}) do |(key, value), service_hash|
|
||||
template_hash.each_with_object({}) do |(key, value), service_hash|
|
||||
value = value.is_a?(Hash) ? value.to_json : value
|
||||
|
||||
service_hash[ActiveRecord::Base.connection.quote_column_name(key)] =
|
||||
|
|
@ -97,11 +97,11 @@ module Projects
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def active_external_issue_tracker?
|
||||
@instance_level_service.issue_tracker? && !@instance_level_service.default
|
||||
@template.issue_tracker? && !@template.default
|
||||
end
|
||||
|
||||
def active_external_wiki?
|
||||
@instance_level_service.type == 'ExternalWikiService'
|
||||
@template.type == 'ExternalWikiService'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
%p.inline
|
||||
= s_("MattermostService|See list of available commands in Mattermost after setting up this service, by entering")
|
||||
%kbd.inline /<trigger> help
|
||||
- unless enabled || @service.instance?
|
||||
- unless enabled || @service.template?
|
||||
= render 'projects/services/mattermost_slash_commands/detailed_help', subject: @service
|
||||
|
||||
- if enabled && !@service.instance?
|
||||
- if enabled && !@service.template?
|
||||
= render 'projects/services/mattermost_slash_commands/installation_info', subject: @service
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
%p.inline
|
||||
= s_("SlackService|See list of available commands in Slack after setting up this service, by entering")
|
||||
%kbd.inline /<command> help
|
||||
- unless @service.instance?
|
||||
- unless @service.template?
|
||||
%p= _("To set up this service:")
|
||||
%ul.list-unstyled.indent-list
|
||||
%li
|
||||
|
|
|
|||
|
|
@ -969,7 +969,7 @@
|
|||
:latency_sensitive:
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
- :name: propagate_instance_level_service
|
||||
- :name: propagate_service_template
|
||||
:feature_category: :source_code_management
|
||||
:has_external_dependencies:
|
||||
:latency_sensitive:
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
class PagesDomainRemovalCronWorker
|
||||
include ApplicationWorker
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
include CronjobQueue
|
||||
|
||||
feature_category :pages
|
||||
worker_resource_boundary :cpu
|
||||
|
||||
def perform
|
||||
PagesDomain.for_removal.find_each do |domain|
|
||||
domain.destroy!
|
||||
PagesDomain.for_removal.with_logging_info.find_each do |domain|
|
||||
with_context(project: domain.project) { domain.destroy! }
|
||||
rescue => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
class PagesDomainSslRenewalCronWorker
|
||||
include ApplicationWorker
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
include CronjobQueue
|
||||
|
||||
feature_category :pages
|
||||
|
||||
def perform
|
||||
return unless ::Gitlab::LetsEncrypt.enabled?
|
||||
|
||||
PagesDomain.need_auto_ssl_renewal.find_each do |domain|
|
||||
PagesDomainSslRenewalWorker.perform_async(domain.id)
|
||||
PagesDomain.need_auto_ssl_renewal.with_logging_info.find_each do |domain|
|
||||
with_context(project: domain.project) do
|
||||
PagesDomainSslRenewalWorker.perform_async(domain.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
class PagesDomainVerificationCronWorker
|
||||
include ApplicationWorker
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
include CronjobQueue
|
||||
|
||||
feature_category :pages
|
||||
|
||||
def perform
|
||||
return if Gitlab::Database.read_only?
|
||||
|
||||
PagesDomain.needs_verification.find_each do |domain|
|
||||
PagesDomainVerificationWorker.perform_async(domain.id)
|
||||
PagesDomain.needs_verification.with_logging_info.find_each do |domain|
|
||||
with_context(project: domain.project) do
|
||||
PagesDomainVerificationWorker.perform_async(domain.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Worker for updating any project specific caches.
|
||||
class PropagateInstanceLevelServiceWorker
|
||||
include ApplicationWorker
|
||||
|
||||
feature_category :source_code_management
|
||||
|
||||
LEASE_TIMEOUT = 4.hours.to_i
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def perform(instance_level_service_id)
|
||||
return unless try_obtain_lease_for(instance_level_service_id)
|
||||
|
||||
Projects::PropagateInstanceLevelService.propagate(Service.find_by(id: instance_level_service_id))
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
private
|
||||
|
||||
def try_obtain_lease_for(instance_level_service_id)
|
||||
Gitlab::ExclusiveLease
|
||||
.new("propagate_instance_level_service_worker:#{instance_level_service_id}", timeout: LEASE_TIMEOUT)
|
||||
.try_obtain
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Worker for updating any project specific caches.
|
||||
class PropagateServiceTemplateWorker
|
||||
include ApplicationWorker
|
||||
|
||||
feature_category :source_code_management
|
||||
|
||||
LEASE_TIMEOUT = 4.hours.to_i
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def perform(template_id)
|
||||
return unless try_obtain_lease_for(template_id)
|
||||
|
||||
Projects::PropagateServiceTemplate.propagate(Service.find_by(id: template_id))
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
private
|
||||
|
||||
def try_obtain_lease_for(template_id)
|
||||
Gitlab::ExclusiveLease
|
||||
.new("propagate_service_template_worker:#{template_id}", timeout: LEASE_TIMEOUT)
|
||||
.try_obtain
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add plain_highlighted_data field to SnippetBlobType
|
||||
merge_request: 24856
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: 'Service model: Rename template attribute to instance'
|
||||
merge_request: 23595
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
- 1
|
||||
- - project_update_repository_storage
|
||||
- 1
|
||||
- - propagate_instance_level_service
|
||||
- - propagate_service_template
|
||||
- 1
|
||||
- - reactive_caching
|
||||
- 1
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RenameServicesTemplateToInstance < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
rename_column_concurrently :services, :template, :instance
|
||||
end
|
||||
|
||||
def down
|
||||
undo_rename_column_concurrently :services, :template, :instance
|
||||
end
|
||||
end
|
||||
|
|
@ -23,10 +23,6 @@ class RemoveEmptyGithubServiceTemplates < ActiveRecord::Migration[5.2]
|
|||
private
|
||||
|
||||
def relationship
|
||||
# The column `template` was renamed to `instance`. Column information needs
|
||||
# to be resetted to avoid cache problems after migrating down.
|
||||
RemoveEmptyGithubServiceTemplates::Service.reset_column_information
|
||||
|
||||
RemoveEmptyGithubServiceTemplates::Service.where(template: true, type: 'GithubService')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanupRenameServicesTemplateToInstance < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
cleanup_concurrent_column_rename :services, :template, :instance
|
||||
end
|
||||
|
||||
def down
|
||||
undo_cleanup_concurrent_column_rename :services, :template, :instance
|
||||
end
|
||||
end
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigratePropagateServiceTemplateSidekiqQueue < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
sidekiq_queue_migrate 'propagate_service_template', to: 'propagate_instance_level_service'
|
||||
end
|
||||
|
||||
def down
|
||||
sidekiq_queue_migrate 'propagate_instance_level_service', to: 'propagate_service_template'
|
||||
end
|
||||
end
|
||||
|
|
@ -3858,9 +3858,9 @@ ActiveRecord::Schema.define(version: 2020_02_07_151640) do
|
|||
t.boolean "deployment_events", default: false, null: false
|
||||
t.string "description", limit: 500
|
||||
t.boolean "comment_on_event_enabled", default: true, null: false
|
||||
t.boolean "instance", default: false
|
||||
t.index ["instance"], name: "index_services_on_instance"
|
||||
t.boolean "template", default: false
|
||||
t.index ["project_id"], name: "index_services_on_project_id"
|
||||
t.index ["template"], name: "index_services_on_template"
|
||||
t.index ["type"], name: "index_services_on_type"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6777,6 +6777,11 @@ type SnippetBlob {
|
|||
"""
|
||||
path: String
|
||||
|
||||
"""
|
||||
Blob plain highlighted data
|
||||
"""
|
||||
plainHighlightedData: String
|
||||
|
||||
"""
|
||||
Blob raw content endpoint path
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -7612,6 +7612,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "plainHighlightedData",
|
||||
"description": "Blob plain highlighted data",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "rawPath",
|
||||
"description": "Blob raw content endpoint path",
|
||||
|
|
|
|||
|
|
@ -1071,6 +1071,7 @@ Represents the snippet blob
|
|||
| `mode` | String | Blob mode |
|
||||
| `name` | String | Blob name |
|
||||
| `path` | String | Blob path |
|
||||
| `plainHighlightedData` | String | Blob plain highlighted data |
|
||||
| `rawPath` | String! | Blob raw content endpoint path |
|
||||
| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
|
||||
| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ module API
|
|||
helpers do
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def slash_command_service(project, service_slug, params)
|
||||
project.services.active.where(instance: false).find do |service|
|
||||
project.services.active.where(template: false).find do |service|
|
||||
service.try(:token) == params[:token] && service.to_param == service_slug.underscore
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ excluded_attributes:
|
|||
- :token
|
||||
- :token_encrypted
|
||||
services:
|
||||
- :instance
|
||||
- :template
|
||||
error_tracking_setting:
|
||||
- :encrypted_token
|
||||
- :encrypted_token_iv
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Gitlab::Serverless::Service
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(attributes)
|
||||
@attributes = attributes
|
||||
end
|
||||
|
||||
def name
|
||||
@attributes.dig('metadata', 'name')
|
||||
end
|
||||
|
||||
def namespace
|
||||
@attributes.dig('metadata', 'namespace')
|
||||
end
|
||||
|
||||
def environment_scope
|
||||
@attributes.dig('environment_scope')
|
||||
end
|
||||
|
||||
def environment
|
||||
@attributes.dig('environment')
|
||||
end
|
||||
|
||||
def podcount
|
||||
@attributes.dig('podcount')
|
||||
end
|
||||
|
||||
def created_at
|
||||
strong_memoize(:created_at) do
|
||||
timestamp = @attributes.dig('metadata', 'creationTimestamp')
|
||||
DateTime.parse(timestamp) if timestamp
|
||||
end
|
||||
end
|
||||
|
||||
def image
|
||||
@attributes.dig(
|
||||
'spec',
|
||||
'runLatest',
|
||||
'configuration',
|
||||
'build',
|
||||
'template',
|
||||
'name')
|
||||
end
|
||||
|
||||
def description
|
||||
knative_07_description || knative_05_06_description
|
||||
end
|
||||
|
||||
def cluster
|
||||
@attributes.dig('cluster')
|
||||
end
|
||||
|
||||
def url
|
||||
proxy_url || knative_06_07_url || knative_05_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def proxy_url
|
||||
if cluster&.serverless_domain
|
||||
Gitlab::Serverless::FunctionURI.new(function: name, cluster: cluster.serverless_domain, environment: environment)
|
||||
end
|
||||
end
|
||||
|
||||
def knative_07_description
|
||||
@attributes.dig(
|
||||
'spec',
|
||||
'template',
|
||||
'metadata',
|
||||
'annotations',
|
||||
'Description'
|
||||
)
|
||||
end
|
||||
|
||||
def knative_05_06_description
|
||||
@attributes.dig(
|
||||
'spec',
|
||||
'runLatest',
|
||||
'configuration',
|
||||
'revisionTemplate',
|
||||
'metadata',
|
||||
'annotations',
|
||||
'Description')
|
||||
end
|
||||
|
||||
def knative_05_url
|
||||
domain = @attributes.dig('status', 'domain')
|
||||
return unless domain
|
||||
|
||||
"http://#{domain}"
|
||||
end
|
||||
|
||||
def knative_06_07_url
|
||||
@attributes.dig('status', 'url')
|
||||
end
|
||||
end
|
||||
|
|
@ -179,7 +179,7 @@ module Gitlab
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def services_usage
|
||||
service_counts = count(Service.active.where(instance: false).where.not(type: 'JiraService').group(:type), fallback: Hash.new(-1))
|
||||
service_counts = count(Service.active.where(template: false).where.not(type: 'JiraService').group(:type), fallback: Hash.new(-1))
|
||||
|
||||
results = Service.available_services_names.each_with_object({}) do |service_name, response|
|
||||
response["projects_#{service_name}_active".to_sym] = service_counts["#{service_name}_service".camelize] || 0
|
||||
|
|
|
|||
1
qa/qa.rb
1
qa/qa.rb
|
|
@ -469,6 +469,7 @@ module QA
|
|||
autoload :Configure, 'qa/vendor/jenkins/page/configure'
|
||||
autoload :NewCredentials, 'qa/vendor/jenkins/page/new_credentials'
|
||||
autoload :NewJob, 'qa/vendor/jenkins/page/new_job'
|
||||
autoload :Job, 'qa/vendor/jenkins/page/job'
|
||||
autoload :ConfigureJob, 'qa/vendor/jenkins/page/configure_job'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'capybara/dsl'
|
||||
|
||||
module QA
|
||||
module Vendor
|
||||
module Jenkins
|
||||
module Page
|
||||
class Job < Page::Base
|
||||
attr_accessor :job_name
|
||||
|
||||
def path
|
||||
"/job/#{@job_name}"
|
||||
end
|
||||
|
||||
def has_successful_build?
|
||||
page.has_text?("Last successful build")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,11 +15,11 @@ describe Admin::ServicesController do
|
|||
Service.available_services_names.each do |service_name|
|
||||
context "#{service_name}" do
|
||||
let!(:service) do
|
||||
service_instance = "#{service_name}_service".camelize.constantize
|
||||
service_instance.where(instance: true).first_or_create
|
||||
service_template = "#{service_name}_service".camelize.constantize
|
||||
service_template.where(template: true).first_or_create
|
||||
end
|
||||
|
||||
it 'successfully displays the service' do
|
||||
it 'successfully displays the template' do
|
||||
get :edit, params: { id: service.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
|
@ -34,7 +34,7 @@ describe Admin::ServicesController do
|
|||
RedmineService.create(
|
||||
project: project,
|
||||
active: false,
|
||||
instance: true,
|
||||
template: true,
|
||||
properties: {
|
||||
project_url: 'http://abc',
|
||||
issues_url: 'http://abc',
|
||||
|
|
@ -44,7 +44,7 @@ describe Admin::ServicesController do
|
|||
end
|
||||
|
||||
it 'calls the propagation worker when service is active' do
|
||||
expect(PropagateInstanceLevelServiceWorker).to receive(:perform_async).with(service.id)
|
||||
expect(PropagateServiceTemplateWorker).to receive(:perform_async).with(service.id)
|
||||
|
||||
put :update, params: { id: service.id, service: { active: true } }
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ describe Admin::ServicesController do
|
|||
end
|
||||
|
||||
it 'does not call the propagation worker when service is not active' do
|
||||
expect(PropagateInstanceLevelServiceWorker).not_to receive(:perform_async)
|
||||
expect(PropagateServiceTemplateWorker).not_to receive(:perform_async)
|
||||
|
||||
put :update, params: { id: service.id, service: { properties: {} } }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ describe Projects::Serverless::FunctionsController do
|
|||
let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
|
||||
let(:knative_services_finder) { environment.knative_services_finder }
|
||||
let(:function_description) { 'A serverless function' }
|
||||
let(:function_name) { 'some-function-name' }
|
||||
let(:knative_stub_options) do
|
||||
{ namespace: namespace.namespace, name: cluster.project.name, description: function_description }
|
||||
{ namespace: namespace.namespace, name: function_name, description: function_description }
|
||||
end
|
||||
let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
|
||||
|
||||
let(:namespace) do
|
||||
create(:cluster_kubernetes_namespace,
|
||||
|
|
@ -87,25 +89,65 @@ describe Projects::Serverless::FunctionsController do
|
|||
end
|
||||
|
||||
context 'when functions were found' do
|
||||
let(:functions) { ["asdf"] }
|
||||
let(:functions) { [{}, {}] }
|
||||
|
||||
before do
|
||||
stub_kubeclient_knative_services(namespace: namespace.namespace)
|
||||
get :index, params: params({ format: :json })
|
||||
stub_kubeclient_knative_services(namespace: namespace.namespace, cluster_id: cluster.id, name: function_name)
|
||||
end
|
||||
|
||||
it 'returns functions' do
|
||||
get :index, params: params({ format: :json })
|
||||
expect(json_response["functions"]).not_to be_empty
|
||||
end
|
||||
|
||||
it { expect(response).to have_gitlab_http_status(:ok) }
|
||||
it 'filters out the functions whose cluster the user does not have permission to read' do
|
||||
allow(controller).to receive(:can?).and_return(true)
|
||||
expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false)
|
||||
|
||||
get :index, params: params({ format: :json })
|
||||
|
||||
expect(json_response["functions"]).to be_empty
|
||||
end
|
||||
|
||||
it 'returns a successful response status' do
|
||||
get :index, params: params({ format: :json })
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'when there is serverless domain for a cluster' do
|
||||
let!(:serverless_domain_cluster) do
|
||||
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
|
||||
end
|
||||
|
||||
it 'returns JSON with function details with serverless domain URL' do
|
||||
get :index, params: params({ format: :json })
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
expect(json_response["functions"]).not_to be_empty
|
||||
|
||||
expect(json_response["functions"]).to all(
|
||||
include(
|
||||
'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no serverless domain for a cluster' do
|
||||
it 'keeps function URL as it was' do
|
||||
expect(Gitlab::Serverless::Domain).not_to receive(:new)
|
||||
|
||||
get :index, params: params({ format: :json })
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
context 'invalid data' do
|
||||
it 'has a bad function name' do
|
||||
context 'with function that does not exist' do
|
||||
it 'returns 404' do
|
||||
get :show, params: params({ format: :json, environment_id: "*", id: "foo" })
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
|
@ -113,15 +155,50 @@ describe Projects::Serverless::FunctionsController do
|
|||
|
||||
context 'with valid data', :use_clean_rails_memory_store_caching do
|
||||
shared_examples 'GET #show with valid data' do
|
||||
it 'has a valid function name' do
|
||||
get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name })
|
||||
context 'when there is serverless domain for a cluster' do
|
||||
let!(:serverless_domain_cluster) do
|
||||
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
|
||||
end
|
||||
|
||||
it 'returns JSON with function details with serverless domain URL' do
|
||||
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
expect(json_response).to include(
|
||||
'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}"
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns 404 when user does not have permission to read the cluster' do
|
||||
allow(controller).to receive(:can?).and_return(true)
|
||||
expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false)
|
||||
|
||||
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no serverless domain for a cluster' do
|
||||
it 'keeps function URL as it was' do
|
||||
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
expect(json_response).to include(
|
||||
'url' => "http://#{function_name}.#{namespace.namespace}.example.com"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'return json with function details' do
|
||||
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
expect(json_response).to include(
|
||||
'name' => project.name,
|
||||
'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
|
||||
'name' => function_name,
|
||||
'url' => "http://#{function_name}.#{namespace.namespace}.example.com",
|
||||
'description' => function_description,
|
||||
'podcount' => 1
|
||||
'podcount' => 0
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -180,8 +257,8 @@ describe Projects::Serverless::FunctionsController do
|
|||
'knative_installed' => 'checking',
|
||||
'functions' => [
|
||||
a_hash_including(
|
||||
'name' => project.name,
|
||||
'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
|
||||
'name' => function_name,
|
||||
'url' => "http://#{function_name}.#{namespace.namespace}.example.com",
|
||||
'description' => function_description
|
||||
)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -154,12 +154,12 @@ describe Projects::ServicesController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when activating Jira service from instance level service' do
|
||||
context 'when activating Jira service from a template' do
|
||||
let(:service) do
|
||||
create(:jira_service, project: project, instance: true)
|
||||
create(:jira_service, project: project, template: true)
|
||||
end
|
||||
|
||||
it 'activate Jira service from instance level service' do
|
||||
it 'activate Jira service from template' do
|
||||
expect(flash[:notice]).to eq 'Jira activated.'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -380,5 +380,9 @@ x6zG6WoibsbsJMj70nwseUnPTBQNDP+j61RJjC/r
|
|||
scope { :instance }
|
||||
usage { :serverless }
|
||||
end
|
||||
|
||||
trait :with_project do
|
||||
association :project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@ describe Projects::Serverless::FunctionsFinder do
|
|||
*knative_services_finder.cache_args)
|
||||
|
||||
result = finder.service(cluster.environment_scope, cluster.project.name)
|
||||
expect(result).not_to be_empty
|
||||
expect(result["metadata"]["name"]).to be_eql(cluster.project.name)
|
||||
expect(result).to be_present
|
||||
expect(result.name).to be_eql(cluster.project.name)
|
||||
end
|
||||
|
||||
it 'has metrics', :use_clean_rails_memory_store_caching do
|
||||
|
|
|
|||
|
|
@ -2736,7 +2736,7 @@ Service
|
|||
when repository is empty
|
||||
test runs execute
|
||||
Template
|
||||
.build_from_instance
|
||||
.build_from_template
|
||||
when template is invalid
|
||||
sets service template to inactive when template is invalid
|
||||
for pushover service
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Container from '~/environments/components/container.vue';
|
||||
import EmptyState from '~/environments/components/empty_state.vue';
|
||||
import EnvironmentsApp from '~/environments/components/environments_app.vue';
|
||||
import { environment, folder } from './mock_data';
|
||||
|
||||
describe('Environment', () => {
|
||||
let mock;
|
||||
let wrapper;
|
||||
|
||||
const mockData = {
|
||||
endpoint: 'environments.json',
|
||||
canCreateEnvironment: true,
|
||||
canReadEnvironment: true,
|
||||
newEnvironmentPath: 'environments/new',
|
||||
helpPagePath: 'help',
|
||||
canaryDeploymentFeatureId: 'canary_deployment',
|
||||
showCanaryDeploymentCallout: true,
|
||||
userCalloutsPath: '/callouts',
|
||||
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
|
||||
helpCanaryDeploymentsPath: 'help/canary-deployments',
|
||||
};
|
||||
|
||||
const mockRequest = (response, body) => {
|
||||
mock.onGet(mockData.endpoint).reply(response, body, {
|
||||
'X-nExt-pAge': '2',
|
||||
'x-page': '1',
|
||||
'X-Per-Page': '1',
|
||||
'X-Prev-Page': '',
|
||||
'X-TOTAL': '37',
|
||||
'X-Total-Pages': '2',
|
||||
});
|
||||
};
|
||||
|
||||
const createWrapper = (shallow = false) => {
|
||||
const fn = shallow ? shallowMount : mount;
|
||||
wrapper = fn(EnvironmentsApp, { propsData: mockData });
|
||||
return axios.waitForAll();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('successful request', () => {
|
||||
describe('without environments', () => {
|
||||
beforeEach(() => {
|
||||
mockRequest(200, { environments: [] });
|
||||
return createWrapper(true);
|
||||
});
|
||||
|
||||
it('should render the empty state', () => {
|
||||
expect(wrapper.find(EmptyState).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when it is possible to enable a review app', () => {
|
||||
beforeEach(() => {
|
||||
mockRequest(200, { environments: [], review_app: { can_setup_review_app: true } });
|
||||
return createWrapper();
|
||||
});
|
||||
|
||||
it('should render the enable review app button', () => {
|
||||
expect(wrapper.find('.js-enable-review-app-button').text()).toContain(
|
||||
'Enable review app',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with paginated environments', () => {
|
||||
const environmentList = [environment];
|
||||
|
||||
beforeEach(() => {
|
||||
mockRequest(200, {
|
||||
environments: environmentList,
|
||||
stopped_count: 1,
|
||||
available_count: 0,
|
||||
});
|
||||
return createWrapper();
|
||||
});
|
||||
|
||||
it('should render a conatiner table with environments', () => {
|
||||
const containerTable = wrapper.find(Container);
|
||||
|
||||
expect(containerTable.exists()).toBe(true);
|
||||
expect(containerTable.props('environments').length).toEqual(environmentList.length);
|
||||
expect(containerTable.find('.environment-name').text()).toEqual(environmentList[0].name);
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
it('should render pagination', () => {
|
||||
expect(wrapper.findAll('.gl-pagination li').length).toEqual(9);
|
||||
});
|
||||
|
||||
it('should make an API request when page is clicked', () => {
|
||||
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
|
||||
|
||||
wrapper.find('.gl-pagination li:nth-child(3) .page-link').trigger('click');
|
||||
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
|
||||
});
|
||||
|
||||
it('should make an API request when using tabs', () => {
|
||||
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
|
||||
wrapper.find('.js-environments-tab-stopped').trigger('click');
|
||||
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsuccessful request', () => {
|
||||
beforeEach(() => {
|
||||
mockRequest(500, {});
|
||||
return createWrapper(true);
|
||||
});
|
||||
|
||||
it('should render empty state', () => {
|
||||
expect(wrapper.find(EmptyState).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandable folders', () => {
|
||||
beforeEach(() => {
|
||||
mockRequest(200, {
|
||||
environments: [folder],
|
||||
stopped_count: 1,
|
||||
available_count: 0,
|
||||
});
|
||||
|
||||
mock.onGet(environment.folder_path).reply(200, { environments: [environment] });
|
||||
|
||||
return createWrapper().then(() => {
|
||||
// open folder
|
||||
wrapper.find('.folder-name').trigger('click');
|
||||
return axios.waitForAll();
|
||||
});
|
||||
});
|
||||
|
||||
it('should open a closed folder', () => {
|
||||
expect(wrapper.find('.folder-icon.ic-chevron-right').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should close an opened folder', () => {
|
||||
expect(wrapper.find('.folder-icon.ic-chevron-down').exists()).toBe(true);
|
||||
|
||||
// close folder
|
||||
wrapper.find('.folder-name').trigger('click');
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.find('.folder-icon.ic-chevron-down').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show children environments', () => {
|
||||
expect(wrapper.findAll('.js-child-row').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should show a button to show all environments', () => {
|
||||
expect(wrapper.find('.text-center > a.btn').text()).toContain('Show all');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -91,10 +91,10 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
describe('no data found', () => {
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper();
|
||||
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('shows the environment selector dropdown', () => {
|
||||
|
|
@ -118,20 +118,15 @@ describe('Dashboard', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows up a loading state', done => {
|
||||
it('shows up a loading state', () => {
|
||||
createShallowWrapper({ hasMetrics: true }, { methods: {} });
|
||||
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(wrapper.vm.emptyState).toEqual('loading');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.vm.emptyState).toEqual('loading');
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the group panels when showPanels is false', done => {
|
||||
it('hides the group panels when showPanels is false', () => {
|
||||
createMountedWrapper(
|
||||
{ hasMetrics: true, showPanels: false },
|
||||
{ stubs: ['graph-group', 'panel-type'] },
|
||||
|
|
@ -139,15 +134,10 @@ describe('Dashboard', () => {
|
|||
|
||||
setupComponentStore(wrapper);
|
||||
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(wrapper.vm.showEmptyState).toEqual(false);
|
||||
expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.vm.showEmptyState).toEqual(false);
|
||||
expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches the metrics data with proper time window', () => {
|
||||
|
|
@ -171,43 +161,32 @@ describe('Dashboard', () => {
|
|||
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
|
||||
|
||||
setupComponentStore(wrapper);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('renders the environments dropdown with a number of environments', done => {
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length);
|
||||
it('renders the environments dropdown with a number of environments', () => {
|
||||
expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length);
|
||||
|
||||
findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => {
|
||||
const anchorEl = itemWrapper.find('a');
|
||||
if (anchorEl.exists() && environmentData[index].metrics_path) {
|
||||
const href = anchorEl.attributes('href');
|
||||
expect(href).toBe(environmentData[index].metrics_path);
|
||||
}
|
||||
});
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => {
|
||||
const anchorEl = itemWrapper.find('a');
|
||||
if (anchorEl.exists() && environmentData[index].metrics_path) {
|
||||
const href = anchorEl.attributes('href');
|
||||
expect(href).toBe(environmentData[index].metrics_path);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the environments dropdown with a single active element', done => {
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
const activeItem = findAllEnvironmentsDropdownItems().wrappers.filter(itemWrapper =>
|
||||
itemWrapper.find('.active').exists(),
|
||||
);
|
||||
it('renders the environments dropdown with a single active element', () => {
|
||||
const activeItem = findAllEnvironmentsDropdownItems().wrappers.filter(itemWrapper =>
|
||||
itemWrapper.find('.active').exists(),
|
||||
);
|
||||
|
||||
expect(activeItem.length).toBe(1);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
expect(activeItem.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the environments dropdown list when there is no environments', done => {
|
||||
it('hides the environments dropdown list when there is no environments', () => {
|
||||
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
|
||||
|
||||
wrapper.vm.$store.commit(
|
||||
|
|
@ -219,35 +198,27 @@ describe('Dashboard', () => {
|
|||
mockedQueryResultPayload,
|
||||
);
|
||||
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(findAllEnvironmentsDropdownItems()).toHaveLength(0);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findAllEnvironmentsDropdownItems()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the datetimepicker dropdown', done => {
|
||||
it('renders the datetimepicker dropdown', () => {
|
||||
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
|
||||
|
||||
setupComponentStore(wrapper);
|
||||
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(wrapper.find(DateTimePicker).exists()).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.find(DateTimePicker).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when one of the metrics is missing', () => {
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
setupComponentStore(wrapper);
|
||||
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('shows a group empty area', () => {
|
||||
|
|
@ -300,7 +271,7 @@ describe('Dashboard', () => {
|
|||
const resultEnvs = environmentData.filter(({ name }) => name.indexOf(searchTerm) !== -1);
|
||||
setSearchTerm(searchTerm);
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findAllEnvironmentsDropdownItems().length).toEqual(resultEnvs.length);
|
||||
});
|
||||
});
|
||||
|
|
@ -349,12 +320,12 @@ describe('Dashboard', () => {
|
|||
const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel');
|
||||
const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
|
||||
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
|
||||
setupComponentStore(wrapper);
|
||||
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('wraps vuedraggable', () => {
|
||||
|
|
@ -368,9 +339,9 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
describe('when rearrange is enabled', () => {
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
wrapper.setProps({ rearrangePanelsAvailable: true });
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('displays rearrange button', () => {
|
||||
|
|
@ -383,9 +354,9 @@ describe('Dashboard', () => {
|
|||
.at(0)
|
||||
.find('.js-draggable-remove');
|
||||
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
findRearrangeButton().vm.$emit('click');
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('it enables draggables', () => {
|
||||
|
|
@ -393,7 +364,7 @@ describe('Dashboard', () => {
|
|||
expect(findEnabledDraggables()).toEqual(findDraggables());
|
||||
});
|
||||
|
||||
it('metrics can be swapped', done => {
|
||||
it('metrics can be swapped', () => {
|
||||
const firstDraggable = findDraggables().at(0);
|
||||
const mockMetrics = [...metricsDashboardPayload.panel_groups[1].panels];
|
||||
|
||||
|
|
@ -404,33 +375,30 @@ describe('Dashboard', () => {
|
|||
[mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]];
|
||||
firstDraggable.vm.$emit('input', mockMetrics);
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
const { panels } = wrapper.vm.dashboard.panel_groups[1];
|
||||
|
||||
expect(panels[1].title).toEqual(firstTitle);
|
||||
expect(panels[0].title).toEqual(secondTitle);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a remove button, which removes a panel', done => {
|
||||
it('shows a remove button, which removes a panel', () => {
|
||||
expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
|
||||
|
||||
expect(findDraggablePanels().length).toEqual(expectedPanelCount);
|
||||
findFirstDraggableRemoveButton().trigger('click');
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('it disables draggables when clicked again', done => {
|
||||
it('it disables draggables when clicked again', () => {
|
||||
findRearrangeButton().vm.$emit('click');
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
|
||||
expect(findEnabledDraggables().length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -438,13 +406,13 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
describe('cluster health', () => {
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
mock.onGet(propsData.metricsEndpoint).reply(statusCodes.OK, JSON.stringify({}));
|
||||
createShallowWrapper({ hasMetrics: true, showHeader: false });
|
||||
|
||||
// all_dashboards is not defined in health dashboards
|
||||
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('hides dashboard header by default', () => {
|
||||
|
|
@ -460,33 +428,29 @@ describe('Dashboard', () => {
|
|||
describe('dashboard edit link', () => {
|
||||
const findEditLink = () => wrapper.find('.js-edit-link');
|
||||
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
|
||||
wrapper.vm.$store.commit(
|
||||
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
|
||||
dashboardGitResponse,
|
||||
);
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('is not present for the default dashboard', () => {
|
||||
expect(findEditLink().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('is present for a custom dashboard, and links to its edit_path', done => {
|
||||
it('is present for a custom dashboard, and links to its edit_path', () => {
|
||||
const dashboard = dashboardGitResponse[1]; // non-default dashboard
|
||||
const currentDashboard = dashboard.path;
|
||||
|
||||
wrapper.setProps({ currentDashboard });
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(findEditLink().exists()).toBe(true);
|
||||
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findEditLink().exists()).toBe(true);
|
||||
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -498,18 +462,14 @@ describe('Dashboard', () => {
|
|||
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
|
||||
dashboardGitResponse,
|
||||
);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('shows the dashboard dropdown', done => {
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
const dashboardDropdown = wrapper.find(DashboardsDropdown);
|
||||
it('shows the dashboard dropdown', () => {
|
||||
const dashboardDropdown = wrapper.find(DashboardsDropdown);
|
||||
|
||||
expect(dashboardDropdown.exists()).toBe(true);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
expect(dashboardDropdown.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -524,20 +484,16 @@ describe('Dashboard', () => {
|
|||
},
|
||||
{ stubs: ['graph-group', 'panel-type'] },
|
||||
);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('shows the link', done => {
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
const externalDashboardButton = wrapper.find('.js-external-dashboard-link');
|
||||
it('shows the link', () => {
|
||||
const externalDashboardButton = wrapper.find('.js-external-dashboard-link');
|
||||
|
||||
expect(externalDashboardButton.exists()).toBe(true);
|
||||
expect(externalDashboardButton.is(GlButton)).toBe(true);
|
||||
expect(externalDashboardButton.text()).toContain('View full dashboard');
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
expect(externalDashboardButton.exists()).toBe(true);
|
||||
expect(externalDashboardButton.is(GlButton)).toBe(true);
|
||||
expect(externalDashboardButton.text()).toContain('View full dashboard');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -550,12 +506,12 @@ describe('Dashboard', () => {
|
|||
.at(i)
|
||||
.props('clipboardText');
|
||||
|
||||
beforeEach(done => {
|
||||
beforeEach(() => {
|
||||
createShallowWrapper({ hasMetrics: true, currentDashboard });
|
||||
|
||||
setupComponentStore(wrapper);
|
||||
|
||||
wrapper.vm.$nextTick(done);
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('contains a link to the dashboard', () => {
|
||||
|
|
@ -565,23 +521,21 @@ describe('Dashboard', () => {
|
|||
expect(getClipboardTextAt(0)).toContain(`y_label=`);
|
||||
});
|
||||
|
||||
it('strips the undefined parameter', done => {
|
||||
it('strips the undefined parameter', () => {
|
||||
wrapper.setProps({ currentDashboard: undefined });
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(getClipboardTextAt(0)).not.toContain(`dashboard=`);
|
||||
expect(getClipboardTextAt(0)).toContain(`y_label=`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('null parameter is stripped', done => {
|
||||
it('null parameter is stripped', () => {
|
||||
wrapper.setProps({ currentDashboard: null });
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(getClipboardTextAt(0)).not.toContain(`dashboard=`);
|
||||
expect(getClipboardTextAt(0)).toContain(`y_label=`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -131,20 +131,17 @@ describe('DashboardsDropdown', () => {
|
|||
expect(findModal().contains(DuplicateDashboardForm)).toBe(true);
|
||||
});
|
||||
|
||||
it('saves a new dashboard', done => {
|
||||
it('saves a new dashboard', () => {
|
||||
findModal().vm.$emit('ok', okEvent);
|
||||
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
expect(okEvent.preventDefault).toHaveBeenCalled();
|
||||
return waitForPromises().then(() => {
|
||||
expect(okEvent.preventDefault).toHaveBeenCalled();
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
|
||||
expect(wrapper.emitted().selectDashboard).toBeTruthy();
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
|
||||
expect(wrapper.emitted().selectDashboard).toBeTruthy();
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a new dashboard is saved succesfully', () => {
|
||||
|
|
@ -167,52 +164,42 @@ describe('DashboardsDropdown', () => {
|
|||
findModal().vm.$emit('ok', okEvent);
|
||||
};
|
||||
|
||||
it('to the default branch, redirects to the new dashboard', done => {
|
||||
it('to the default branch, redirects to the new dashboard', () => {
|
||||
submitForm({
|
||||
branch: defaultBranch,
|
||||
});
|
||||
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard);
|
||||
});
|
||||
});
|
||||
|
||||
it('to a new branch refreshes in the current dashboard', done => {
|
||||
it('to a new branch refreshes in the current dashboard', () => {
|
||||
submitForm({
|
||||
branch: 'another-branch',
|
||||
});
|
||||
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles error when a new dashboard is not saved', done => {
|
||||
it('handles error when a new dashboard is not saved', () => {
|
||||
const errMsg = 'An error occurred';
|
||||
|
||||
duplicateDashboardAction.mockRejectedValueOnce(errMsg);
|
||||
findModal().vm.$emit('ok', okEvent);
|
||||
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
expect(okEvent.preventDefault).toHaveBeenCalled();
|
||||
return waitForPromises().then(() => {
|
||||
expect(okEvent.preventDefault).toHaveBeenCalled();
|
||||
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
expect(findAlert().text()).toBe(errMsg);
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
expect(findAlert().text()).toBe(errMsg);
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('id is correct, as the value of modal directive binding matches modal id', () => {
|
||||
|
|
|
|||
|
|
@ -44,30 +44,27 @@ describe('DuplicateDashboardForm', () => {
|
|||
describe('validates the file name', () => {
|
||||
const findInvalidFeedback = () => findByRef('fileNameFormGroup').find('.invalid-feedback');
|
||||
|
||||
it('when is empty', done => {
|
||||
it('when is empty', () => {
|
||||
setValue('fileName', '');
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findByRef('fileNameFormGroup').is('.is-valid')).toBe(true);
|
||||
expect(findInvalidFeedback().exists()).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('when is valid', done => {
|
||||
it('when is valid', () => {
|
||||
setValue('fileName', 'my_dashboard.yml');
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findByRef('fileNameFormGroup').is('.is-valid')).toBe(true);
|
||||
expect(findInvalidFeedback().exists()).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('when is not valid', done => {
|
||||
it('when is not valid', () => {
|
||||
setValue('fileName', 'my_dashboard.exe');
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findByRef('fileNameFormGroup').is('.is-invalid')).toBe(true);
|
||||
expect(findInvalidFeedback().text()).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -124,30 +121,26 @@ describe('DuplicateDashboardForm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('when a `default` branch option is set, branch input is invisible and ignored', done => {
|
||||
it('when a `default` branch option is set, branch input is invisible and ignored', () => {
|
||||
setChecked(wrapper.vm.$options.radioVals.DEFAULT);
|
||||
setValue('branchName', 'a-new-branch');
|
||||
|
||||
expect(lastChange()).resolves.toMatchObject({
|
||||
branch: defaultBranch,
|
||||
});
|
||||
wrapper.vm.$nextTick(() => {
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findByRef('branchName').isVisible()).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('when `new` branch option is chosen, focuses on the branch name input', done => {
|
||||
it('when `new` branch option is chosen, focuses on the branch name input', () => {
|
||||
setChecked(wrapper.vm.$options.radioVals.NEW);
|
||||
|
||||
wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
wrapper.find('form').trigger('change');
|
||||
expect(findByRef('branchName').is(':focus')).toBe(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
wrapper.find('form').trigger('change');
|
||||
expect(findByRef('branchName').is(':focus')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,25 +32,23 @@ describe('Graph group component', () => {
|
|||
expect(findCaretIcon().props('name')).toBe('angle-down');
|
||||
});
|
||||
|
||||
it('should show the angle-right caret icon when the user collapses the group', done => {
|
||||
it('should show the angle-right caret icon when the user collapses the group', () => {
|
||||
wrapper.vm.collapse();
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findContent().isVisible()).toBe(false);
|
||||
expect(findCaretIcon().props('name')).toBe('angle-right');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the open the group when collapseGroup is set to true', done => {
|
||||
it('should show the open the group when collapseGroup is set to true', () => {
|
||||
wrapper.setProps({
|
||||
collapseGroup: true,
|
||||
});
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findContent().isVisible()).toBe(true);
|
||||
expect(findCaretIcon().props('name')).toBe('angle-down');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -102,13 +100,12 @@ describe('Graph group component', () => {
|
|||
expect(findCaretIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should show the panel content when clicked', done => {
|
||||
it('should show the panel content when clicked', () => {
|
||||
wrapper.vm.collapse();
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(findContent().isVisible()).toBe(true);
|
||||
expect(findCaretIcon().exists()).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ describe('Panel Type component', () => {
|
|||
|
||||
const exampleText = 'example_text';
|
||||
|
||||
const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' });
|
||||
|
||||
const createWrapper = props => {
|
||||
wrapper = shallowMount(PanelType, {
|
||||
propsData: {
|
||||
|
|
@ -96,8 +98,7 @@ describe('Panel Type component', () => {
|
|||
});
|
||||
|
||||
it('sets no clipboard copy link on dropdown by default', () => {
|
||||
const link = () => wrapper.find({ ref: 'copyChartLink' });
|
||||
expect(link().exists()).toBe(false);
|
||||
expect(findCopyLink().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('Time Series Chart panel type', () => {
|
||||
|
|
@ -204,7 +205,6 @@ describe('Panel Type component', () => {
|
|||
});
|
||||
|
||||
describe('when cliboard data is available', () => {
|
||||
const link = () => wrapper.find({ ref: 'copyChartLink' });
|
||||
const clipboardText = 'A value to copy.';
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -219,16 +219,16 @@ describe('Panel Type component', () => {
|
|||
});
|
||||
|
||||
it('sets clipboard text on the dropdown', () => {
|
||||
expect(link().exists()).toBe(true);
|
||||
expect(link().element.dataset.clipboardText).toBe(clipboardText);
|
||||
expect(findCopyLink().exists()).toBe(true);
|
||||
expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText);
|
||||
});
|
||||
|
||||
it('adds a copy button to the dropdown', () => {
|
||||
expect(link().text()).toContain('Generate link to chart');
|
||||
expect(findCopyLink().text()).toContain('Generate link to chart');
|
||||
});
|
||||
|
||||
it('opens a toast on click', () => {
|
||||
link().vm.$emit('click');
|
||||
findCopyLink().vm.$emit('click');
|
||||
|
||||
expect(wrapper.vm.$toast.show).toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ require 'spec_helper'
|
|||
|
||||
describe GitlabSchema.types['SnippetBlob'] do
|
||||
it 'has the correct fields' do
|
||||
expected_fields = [:highlighted_data, :raw_path,
|
||||
:size, :binary, :name, :path,
|
||||
:simple_viewer, :rich_viewer,
|
||||
:mode]
|
||||
expected_fields = [:highlighted_data, :plain_highlighted_data,
|
||||
:raw_path, :size, :binary, :name, :path,
|
||||
:simple_viewer, :rich_viewer, :mode]
|
||||
|
||||
is_expected.to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,279 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import environmentsComponent from '~/environments/components/environments_app.vue';
|
||||
import { environment, folder } from './mock_data';
|
||||
|
||||
describe('Environment', () => {
|
||||
const mockData = {
|
||||
endpoint: 'environments.json',
|
||||
canCreateEnvironment: true,
|
||||
canReadEnvironment: true,
|
||||
newEnvironmentPath: 'environments/new',
|
||||
helpPagePath: 'help',
|
||||
canaryDeploymentFeatureId: 'canary_deployment',
|
||||
showCanaryDeploymentCallout: true,
|
||||
userCalloutsPath: '/callouts',
|
||||
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
|
||||
helpCanaryDeploymentsPath: 'help/canary-deployments',
|
||||
};
|
||||
|
||||
let EnvironmentsComponent;
|
||||
let component;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
EnvironmentsComponent = Vue.extend(environmentsComponent);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.$destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('successful request', () => {
|
||||
describe('without environments', () => {
|
||||
beforeEach(done => {
|
||||
mock.onGet(mockData.endpoint).reply(200, { environments: [] });
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render the empty state', () => {
|
||||
expect(component.$el.querySelector('.js-new-environment-button').textContent).toContain(
|
||||
'New environment',
|
||||
);
|
||||
|
||||
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain(
|
||||
"You don't have any environments right now",
|
||||
);
|
||||
});
|
||||
|
||||
describe('when it is possible to enable a review app', () => {
|
||||
beforeEach(done => {
|
||||
mock
|
||||
.onGet(mockData.endpoint)
|
||||
.reply(200, { environments: [], review_app: { can_setup_review_app: true } });
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render the enable review app button', () => {
|
||||
expect(component.$el.querySelector('.js-enable-review-app-button').textContent).toContain(
|
||||
'Enable review app',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with paginated environments', () => {
|
||||
beforeEach(done => {
|
||||
mock.onGet(mockData.endpoint).reply(
|
||||
200,
|
||||
{
|
||||
environments: [environment],
|
||||
stopped_count: 1,
|
||||
available_count: 0,
|
||||
},
|
||||
{
|
||||
'X-nExt-pAge': '2',
|
||||
'x-page': '1',
|
||||
'X-Per-Page': '1',
|
||||
'X-Prev-Page': '',
|
||||
'X-TOTAL': '37',
|
||||
'X-Total-Pages': '2',
|
||||
},
|
||||
);
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render a table with environments', () => {
|
||||
expect(component.$el.querySelectorAll('table')).not.toBeNull();
|
||||
expect(component.$el.querySelector('.environment-name').textContent.trim()).toEqual(
|
||||
environment.name,
|
||||
);
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
it('should render pagination', () => {
|
||||
expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(9);
|
||||
});
|
||||
|
||||
it('should make an API request when page is clicked', done => {
|
||||
spyOn(component, 'updateContent');
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(3) .page-link').click();
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should make an API request when using tabs', done => {
|
||||
setTimeout(() => {
|
||||
spyOn(component, 'updateContent');
|
||||
component.$el.querySelector('.js-environments-tab-stopped').click();
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsuccessfull request', () => {
|
||||
beforeEach(done => {
|
||||
mock.onGet(mockData.endpoint).reply(500, {});
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render empty state', () => {
|
||||
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain(
|
||||
"You don't have any environments right now",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandable folders', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet(mockData.endpoint).reply(
|
||||
200,
|
||||
{
|
||||
environments: [folder],
|
||||
stopped_count: 0,
|
||||
available_count: 1,
|
||||
},
|
||||
{
|
||||
'X-nExt-pAge': '2',
|
||||
'x-page': '1',
|
||||
'X-Per-Page': '1',
|
||||
'X-Prev-Page': '',
|
||||
'X-TOTAL': '37',
|
||||
'X-Total-Pages': '2',
|
||||
},
|
||||
);
|
||||
|
||||
mock.onGet(environment.folder_path).reply(200, { environments: [environment] });
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
});
|
||||
|
||||
it('should open a closed folder', done => {
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.folder-name').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(component.$el.querySelector('.folder-icon.ic-chevron-right')).toBe(null);
|
||||
done();
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should close an opened folder', done => {
|
||||
setTimeout(() => {
|
||||
// open folder
|
||||
component.$el.querySelector('.folder-name').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
// close folder
|
||||
component.$el.querySelector('.folder-name').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(component.$el.querySelector('.folder-icon.ic-chevron-down')).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should show children environments and a button to show all environments', done => {
|
||||
setTimeout(() => {
|
||||
// open folder
|
||||
component.$el.querySelector('.folder-name').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
// wait for next async request
|
||||
setTimeout(() => {
|
||||
expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1);
|
||||
expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain(
|
||||
'Show all',
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet(mockData.endpoint).reply(
|
||||
200,
|
||||
{
|
||||
environments: [],
|
||||
stopped_count: 0,
|
||||
available_count: 1,
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
spyOn(window.history, 'pushState').and.stub();
|
||||
});
|
||||
|
||||
describe('updateContent', () => {
|
||||
it('should set given parameters', done => {
|
||||
component
|
||||
.updateContent({ scope: 'stopped', page: '3' })
|
||||
.then(() => {
|
||||
expect(component.page).toEqual('3');
|
||||
expect(component.scope).toEqual('stopped');
|
||||
expect(component.requestData.scope).toEqual('stopped');
|
||||
expect(component.requestData.page).toEqual('3');
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangeTab', () => {
|
||||
it('should set page to 1', () => {
|
||||
spyOn(component, 'updateContent');
|
||||
component.onChangeTab('stopped');
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangePage', () => {
|
||||
it('should update page and keep scope', () => {
|
||||
spyOn(component, 'updateContent');
|
||||
component.onChangePage(4);
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -652,10 +652,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
|
|||
setup_import_export_config('light')
|
||||
end
|
||||
|
||||
it 'does not import any instance-level services' do
|
||||
it 'does not import any templated services' do
|
||||
expect(restored_project_json).to eq(true)
|
||||
|
||||
expect(project.services.where(instance: true).count).to eq(0)
|
||||
expect(project.services.where(template: true).count).to eq(0)
|
||||
end
|
||||
|
||||
it 'imports labels' do
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ Service:
|
|||
- updated_at
|
||||
- active
|
||||
- properties
|
||||
- instance
|
||||
- template
|
||||
- push_events
|
||||
- issues_events
|
||||
- commit_events
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Serverless::Service do
|
||||
let(:cluster) { create(:cluster) }
|
||||
let(:environment) { create(:environment) }
|
||||
let(:attributes) do
|
||||
{
|
||||
'apiVersion' => 'serving.knative.dev/v1alpha1',
|
||||
'kind' => 'Service',
|
||||
'metadata' => {
|
||||
'creationTimestamp' => '2019-10-22T21:19:13Z',
|
||||
'name' => 'kubetest',
|
||||
'namespace' => 'project1-1-environment1'
|
||||
},
|
||||
'spec' => {
|
||||
'runLatest' => {
|
||||
'configuration' => {
|
||||
'build' => {
|
||||
'template' => {
|
||||
'name' => 'some-image'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'environment_scope' => '*',
|
||||
'cluster' => cluster,
|
||||
'environment' => environment,
|
||||
'podcount' => 0
|
||||
}
|
||||
end
|
||||
|
||||
it 'exposes methods extracting data from the attributes hash' do
|
||||
service = Gitlab::Serverless::Service.new(attributes)
|
||||
|
||||
expect(service.name).to eq('kubetest')
|
||||
expect(service.namespace).to eq('project1-1-environment1')
|
||||
expect(service.environment_scope).to eq('*')
|
||||
expect(service.podcount).to eq(0)
|
||||
expect(service.created_at).to eq(DateTime.parse('2019-10-22T21:19:13Z'))
|
||||
expect(service.image).to eq('some-image')
|
||||
expect(service.cluster).to eq(cluster)
|
||||
expect(service.environment).to eq(environment)
|
||||
end
|
||||
|
||||
it 'returns nil for missing attributes' do
|
||||
service = Gitlab::Serverless::Service.new({})
|
||||
|
||||
[:name, :namespace, :environment_scope, :cluster, :podcount, :created_at, :image, :description, :url, :environment].each do |method|
|
||||
expect(service.send(method)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'extracts the description in knative 7 format if available' do
|
||||
attributes = {
|
||||
'spec' => {
|
||||
'template' => {
|
||||
'metadata' => {
|
||||
'annotations' => {
|
||||
'Description' => 'some description'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
service = Gitlab::Serverless::Service.new(attributes)
|
||||
|
||||
expect(service.description).to eq('some description')
|
||||
end
|
||||
|
||||
it 'extracts the description in knative 5/6 format if 7 is not available' do
|
||||
attributes = {
|
||||
'spec' => {
|
||||
'runLatest' => {
|
||||
'configuration' => {
|
||||
'revisionTemplate' => {
|
||||
'metadata' => {
|
||||
'annotations' => {
|
||||
'Description' => 'some description'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
service = Gitlab::Serverless::Service.new(attributes)
|
||||
|
||||
expect(service.description).to eq('some description')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#url' do
|
||||
it 'returns proxy URL if cluster has serverless domain' do
|
||||
# cluster = create(:cluster)
|
||||
knative = create(:clusters_applications_knative, :installed, cluster: cluster)
|
||||
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
|
||||
service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster))
|
||||
|
||||
expect(Gitlab::Serverless::FunctionURI).to receive(:new).with(
|
||||
function: service.name,
|
||||
cluster: service.cluster.serverless_domain,
|
||||
environment: service.environment
|
||||
).and_return('https://proxy.example.com')
|
||||
|
||||
expect(service.url).to eq('https://proxy.example.com')
|
||||
end
|
||||
|
||||
it 'returns the URL from the knative 6/7 format' do
|
||||
attributes = {
|
||||
'status' => {
|
||||
'url' => 'https://example.com'
|
||||
}
|
||||
}
|
||||
service = Gitlab::Serverless::Service.new(attributes)
|
||||
|
||||
expect(service.url).to eq('https://example.com')
|
||||
end
|
||||
|
||||
it 'returns the URL from the knative 5 format' do
|
||||
attributes = {
|
||||
'status' => {
|
||||
'domain' => 'example.com'
|
||||
}
|
||||
}
|
||||
service = Gitlab::Serverless::Service.new(attributes)
|
||||
|
||||
expect(service.url).to eq('http://example.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -18,7 +18,7 @@ describe Gitlab::UsageData do
|
|||
create(:service, project: projects[1], type: 'SlackService', active: true)
|
||||
create(:service, project: projects[2], type: 'SlackService', active: true)
|
||||
create(:service, project: projects[2], type: 'MattermostService', active: false)
|
||||
create(:service, project: projects[2], type: 'MattermostService', active: true, instance: true)
|
||||
create(:service, project: projects[2], type: 'MattermostService', active: true, template: true)
|
||||
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
|
||||
create(:project_error_tracking_setting, project: projects[0])
|
||||
create(:project_error_tracking_setting, project: projects[1], enabled: false)
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'post_migrate', '20200206111847_migrate_propagate_service_template_sidekiq_queue.rb')
|
||||
|
||||
describe MigratePropagateServiceTemplateSidekiqQueue, :sidekiq, :redis do
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
include StubWorker
|
||||
|
||||
context 'when there are jobs in the queue' do
|
||||
it 'correctly migrates queue when migrating up' do
|
||||
Sidekiq::Testing.disable! do
|
||||
stub_worker(queue: 'propagate_service_template').perform_async('Something', [1])
|
||||
stub_worker(queue: 'propagate_instance_level_service').perform_async('Something', [1])
|
||||
|
||||
described_class.new.up
|
||||
|
||||
expect(sidekiq_queue_length('propagate_service_template')).to eq 0
|
||||
expect(sidekiq_queue_length('propagate_instance_level_service')).to eq 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no jobs in the queues' do
|
||||
it 'does not raise error when migrating up' do
|
||||
expect { described_class.new.up }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -97,23 +97,23 @@ describe Service do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Instance" do
|
||||
describe "Template" do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
describe '.build_from_instance' do
|
||||
context 'when instance level integration is invalid' do
|
||||
it 'sets instance level integration to inactive when instance is invalid' do
|
||||
instance = build(:prometheus_service, instance: true, active: true, properties: {})
|
||||
instance.save(validate: false)
|
||||
describe '.build_from_template' do
|
||||
context 'when template is invalid' do
|
||||
it 'sets service template to inactive when template is invalid' do
|
||||
template = build(:prometheus_service, template: true, active: true, properties: {})
|
||||
template.save(validate: false)
|
||||
|
||||
service = described_class.build_from_instance(project.id, instance)
|
||||
service = described_class.build_from_template(project.id, template)
|
||||
|
||||
expect(service).to be_valid
|
||||
expect(service.active).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'build issue tracker from a instance level integration' do
|
||||
describe 'build issue tracker from a template' do
|
||||
let(:title) { 'custom title' }
|
||||
let(:description) { 'custom description' }
|
||||
let(:url) { 'http://jira.example.com' }
|
||||
|
|
@ -127,9 +127,9 @@ describe Service do
|
|||
}
|
||||
end
|
||||
|
||||
shared_examples 'integration creation from instance level' do
|
||||
shared_examples 'service creation from a template' do
|
||||
it 'creates a correct service' do
|
||||
service = described_class.build_from_instance(project.id, instance_level_integration)
|
||||
service = described_class.build_from_template(project.id, template)
|
||||
|
||||
expect(service).to be_active
|
||||
expect(service.title).to eq(title)
|
||||
|
|
@ -144,38 +144,38 @@ describe Service do
|
|||
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
|
||||
context 'when data are stored in properties' do
|
||||
let(:properties) { data_params.merge(title: title, description: description) }
|
||||
let!(:instance_level_integration) do
|
||||
create(:jira_service, :without_properties_callback, instance: true, properties: properties.merge(additional: 'something'))
|
||||
let!(:template) do
|
||||
create(:jira_service, :without_properties_callback, template: true, properties: properties.merge(additional: 'something'))
|
||||
end
|
||||
|
||||
it_behaves_like 'integration creation from instance level'
|
||||
it_behaves_like 'service creation from a template'
|
||||
end
|
||||
|
||||
context 'when data are stored in separated fields' do
|
||||
let(:instance_level_integration) do
|
||||
create(:jira_service, data_params.merge(properties: {}, title: title, description: description, instance: true))
|
||||
let(:template) do
|
||||
create(:jira_service, data_params.merge(properties: {}, title: title, description: description, template: true))
|
||||
end
|
||||
|
||||
it_behaves_like 'integration creation from instance level'
|
||||
it_behaves_like 'service creation from a template'
|
||||
end
|
||||
|
||||
context 'when data are stored in both properties and separated fields' do
|
||||
let(:properties) { data_params.merge(title: title, description: description) }
|
||||
let(:instance_level_integration) do
|
||||
create(:jira_service, :without_properties_callback, active: true, instance: true, properties: properties).tap do |service|
|
||||
let(:template) do
|
||||
create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |service|
|
||||
create(:jira_tracker_data, data_params.merge(service: service))
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'integration creation from instance level'
|
||||
it_behaves_like 'service creation from a template'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "for pushover service" do
|
||||
let!(:instance_level_integration) do
|
||||
let!(:service_template) do
|
||||
PushoverService.create(
|
||||
instance: true,
|
||||
template: true,
|
||||
properties: {
|
||||
device: 'MyDevice',
|
||||
sound: 'mic',
|
||||
|
|
@ -188,7 +188,7 @@ describe Service do
|
|||
it "has all fields prefilled" do
|
||||
service = project.find_or_initialize_service('pushover')
|
||||
|
||||
expect(service.instance).to eq(false)
|
||||
expect(service.template).to eq(false)
|
||||
expect(service.device).to eq('MyDevice')
|
||||
expect(service.sound).to eq('mic')
|
||||
expect(service.priority).to eq(4)
|
||||
|
|
@ -391,6 +391,14 @@ describe Service do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.find_by_template' do
|
||||
let!(:service) { create(:service, template: true) }
|
||||
|
||||
it 'returns service template' do
|
||||
expect(described_class.find_by_template).to eq(service)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#api_field_names' do
|
||||
let(:fake_service) do
|
||||
Class.new(Service) do
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe SnippetBlobPresenter do
|
|||
snippet.file_name = 'test.md'
|
||||
snippet.content = '*foo*'
|
||||
|
||||
expect(subject).to eq '<p data-sourcepos="1:1-1:5" dir="auto"><em>foo</em></p>'
|
||||
expect(subject).to eq '<span id="LC1" class="line" lang="markdown"><span class="ge">*foo*</span></span>'
|
||||
end
|
||||
|
||||
it 'returns syntax highlighted content' do
|
||||
|
|
@ -33,7 +33,41 @@ describe SnippetBlobPresenter do
|
|||
snippet.file_name = 'test'
|
||||
snippet.content = 'foo'
|
||||
|
||||
expect(described_class.new(snippet.blob).highlighted_data).to eq '<span id="LC1" class="line" lang="plaintext">foo</span>'
|
||||
expect(subject).to eq '<span id="LC1" class="line" lang="plaintext">foo</span>'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#plain_highlighted_data' do
|
||||
let(:snippet) { build(:personal_snippet) }
|
||||
|
||||
subject { described_class.new(snippet.blob).plain_highlighted_data }
|
||||
|
||||
it 'returns nil when the snippet blob is binary' do
|
||||
allow(snippet.blob).to receive(:binary?).and_return(true)
|
||||
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
|
||||
it 'returns plain content when snippet file is markup' do
|
||||
snippet.file_name = 'test.md'
|
||||
snippet.content = '*foo*'
|
||||
|
||||
expect(subject).to eq '<span id="LC1" class="line" lang="">*foo*</span>'
|
||||
end
|
||||
|
||||
it 'returns plain syntax content' do
|
||||
snippet.file_name = 'test.rb'
|
||||
snippet.content = 'class Foo;end'
|
||||
|
||||
expect(subject)
|
||||
.to eq '<span id="LC1" class="line" lang="">class Foo;end</span>'
|
||||
end
|
||||
|
||||
it 'returns plain text highlighted content' do
|
||||
snippet.file_name = 'test'
|
||||
snippet.content = 'foo'
|
||||
|
||||
expect(subject).to eq '<span id="LC1" class="line" lang="">foo</span>'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ describe Projects::CreateService, '#execute' do
|
|||
}
|
||||
end
|
||||
|
||||
it 'creates labels on Project creation if there are instance level services' do
|
||||
it 'creates labels on Project creation if there are templates' do
|
||||
Label.create(title: "bug", template: true)
|
||||
project = create_project(user, opts)
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ describe Projects::CreateService, '#execute' do
|
|||
end
|
||||
|
||||
it 'sets invalid service as inactive' do
|
||||
create(:service, type: 'JiraService', project: nil, instance: true, active: true)
|
||||
create(:service, type: 'JiraService', project: nil, template: true, active: true)
|
||||
|
||||
project = create_project(user, opts)
|
||||
service = project.services.first
|
||||
|
|
@ -342,22 +342,22 @@ describe Projects::CreateService, '#execute' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there is an active instance level service' do
|
||||
context 'when there is an active service template' do
|
||||
before do
|
||||
create(:service, project: nil, instance: true, active: true)
|
||||
create(:service, project: nil, template: true, active: true)
|
||||
end
|
||||
|
||||
it 'creates a service from instance level service' do
|
||||
it 'creates a service from this template' do
|
||||
project = create_project(user, opts)
|
||||
|
||||
expect(project.services.count).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a bad instance level service is created' do
|
||||
context 'when a bad service template is created' do
|
||||
it 'sets service to be inactive' do
|
||||
opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-foss'
|
||||
create(:service, type: 'DroneCiService', project: nil, instance: true, active: true)
|
||||
create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
|
||||
|
||||
project = create_project(user, opts)
|
||||
service = project.services.first
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::PropagateInstanceLevelService do
|
||||
describe Projects::PropagateServiceTemplate do
|
||||
describe '.propagate' do
|
||||
let!(:instance_level_integration) do
|
||||
let!(:service_template) do
|
||||
PushoverService.create(
|
||||
instance: true,
|
||||
template: true,
|
||||
active: true,
|
||||
properties: {
|
||||
device: 'MyDevice',
|
||||
|
|
@ -22,14 +22,14 @@ describe Projects::PropagateInstanceLevelService do
|
|||
it 'creates services for projects' do
|
||||
expect(project.pushover_service).to be_nil
|
||||
|
||||
described_class.propagate(instance_level_integration)
|
||||
described_class.propagate(service_template)
|
||||
|
||||
expect(project.reload.pushover_service).to be_present
|
||||
end
|
||||
|
||||
it 'creates services for a project that has another service' do
|
||||
BambooService.create(
|
||||
instance: true,
|
||||
template: true,
|
||||
active: true,
|
||||
project: project,
|
||||
properties: {
|
||||
|
|
@ -42,14 +42,14 @@ describe Projects::PropagateInstanceLevelService do
|
|||
|
||||
expect(project.pushover_service).to be_nil
|
||||
|
||||
described_class.propagate(instance_level_integration)
|
||||
described_class.propagate(service_template)
|
||||
|
||||
expect(project.reload.pushover_service).to be_present
|
||||
end
|
||||
|
||||
it 'does not create the service if it exists already' do
|
||||
other_service = BambooService.create(
|
||||
instance: true,
|
||||
template: true,
|
||||
active: true,
|
||||
properties: {
|
||||
bamboo_url: 'http://gitlab.com',
|
||||
|
|
@ -59,17 +59,17 @@ describe Projects::PropagateInstanceLevelService do
|
|||
}
|
||||
)
|
||||
|
||||
Service.build_from_instance(project.id, instance_level_integration).save!
|
||||
Service.build_from_instance(project.id, other_service).save!
|
||||
Service.build_from_template(project.id, service_template).save!
|
||||
Service.build_from_template(project.id, other_service).save!
|
||||
|
||||
expect { described_class.propagate(instance_level_integration) }
|
||||
expect { described_class.propagate(service_template) }
|
||||
.not_to change { Service.count }
|
||||
end
|
||||
|
||||
it 'creates the service containing the instance attributes' do
|
||||
described_class.propagate(instance_level_integration)
|
||||
it 'creates the service containing the template attributes' do
|
||||
described_class.propagate(service_template)
|
||||
|
||||
expect(project.pushover_service.properties).to eq(instance_level_integration.properties)
|
||||
expect(project.pushover_service.properties).to eq(service_template.properties)
|
||||
end
|
||||
|
||||
describe 'bulk update', :use_sql_query_cache do
|
||||
|
|
@ -80,7 +80,7 @@ describe Projects::PropagateInstanceLevelService do
|
|||
|
||||
project_total.times { create(:project) }
|
||||
|
||||
described_class.propagate(instance_level_integration)
|
||||
described_class.propagate(service_template)
|
||||
end
|
||||
|
||||
it 'creates services for all projects' do
|
||||
|
|
@ -90,18 +90,18 @@ describe Projects::PropagateInstanceLevelService do
|
|||
|
||||
describe 'external tracker' do
|
||||
it 'updates the project external tracker' do
|
||||
instance_level_integration.update!(category: 'issue_tracker', default: false)
|
||||
service_template.update!(category: 'issue_tracker', default: false)
|
||||
|
||||
expect { described_class.propagate(instance_level_integration) }
|
||||
expect { described_class.propagate(service_template) }
|
||||
.to change { project.reload.has_external_issue_tracker }.to(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'external wiki' do
|
||||
it 'updates the project external tracker' do
|
||||
instance_level_integration.update!(type: 'ExternalWikiService')
|
||||
service_template.update!(type: 'ExternalWikiService')
|
||||
|
||||
expect { described_class.propagate(instance_level_integration) }
|
||||
expect { described_class.propagate(service_template) }
|
||||
.to change { project.reload.has_external_wiki }.to(true)
|
||||
end
|
||||
end
|
||||
|
|
@ -557,7 +557,7 @@ module KubernetesHelpers
|
|||
end
|
||||
|
||||
# noinspection RubyStringKeysInHashInspection
|
||||
def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
|
||||
def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 9)
|
||||
{ "apiVersion" => "serving.knative.dev/v1alpha1",
|
||||
"kind" => "Service",
|
||||
"metadata" =>
|
||||
|
|
@ -612,12 +612,12 @@ module KubernetesHelpers
|
|||
"url" => "http://#{name}.#{namespace}.#{domain}"
|
||||
},
|
||||
"environment_scope" => environment,
|
||||
"cluster_id" => 9,
|
||||
"cluster_id" => cluster_id,
|
||||
"podcount" => 0 }
|
||||
end
|
||||
|
||||
# noinspection RubyStringKeysInHashInspection
|
||||
def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
|
||||
def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5)
|
||||
{ "apiVersion" => "serving.knative.dev/v1alpha1",
|
||||
"kind" => "Service",
|
||||
"metadata" =>
|
||||
|
|
@ -664,12 +664,12 @@ module KubernetesHelpers
|
|||
"traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
|
||||
"url" => "http://#{name}.#{namespace}.#{domain}" },
|
||||
"environment_scope" => environment,
|
||||
"cluster_id" => 5,
|
||||
"cluster_id" => cluster_id,
|
||||
"podcount" => 0 }
|
||||
end
|
||||
|
||||
# noinspection RubyStringKeysInHashInspection
|
||||
def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
|
||||
def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5)
|
||||
{ "apiVersion" => "serving.knative.dev/v1alpha1",
|
||||
"kind" => "Service",
|
||||
"metadata" =>
|
||||
|
|
@ -716,12 +716,12 @@ module KubernetesHelpers
|
|||
"traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
|
||||
"url" => "http://#{name}.#{namespace}.#{domain}" },
|
||||
"environment_scope" => environment,
|
||||
"cluster_id" => 5,
|
||||
"cluster_id" => cluster_id,
|
||||
"podcount" => 0 }
|
||||
end
|
||||
|
||||
# noinspection RubyStringKeysInHashInspection
|
||||
def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
|
||||
def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 8)
|
||||
{ "apiVersion" => "serving.knative.dev/v1alpha1",
|
||||
"kind" => "Service",
|
||||
"metadata" =>
|
||||
|
|
@ -771,7 +771,7 @@ module KubernetesHelpers
|
|||
"observedGeneration" => 1,
|
||||
"traffic" => [{ "percent" => 100, "revisionName" => "#{name}-58qgr" }] },
|
||||
"environment_scope" => environment,
|
||||
"cluster_id" => 8,
|
||||
"cluster_id" => cluster_id,
|
||||
"podcount" => 0 }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'a pages cronjob scheduling jobs with context' do |scheduled_worker_class|
|
||||
let(:worker) { described_class.new }
|
||||
|
||||
it 'does not cause extra queries for multiple domains' do
|
||||
control = ActiveRecord::QueryRecorder.new { worker.perform }
|
||||
|
||||
extra_domain
|
||||
|
||||
expect { worker.perform }.not_to exceed_query_limit(control)
|
||||
end
|
||||
|
||||
it 'schedules the renewal with a context' do
|
||||
extra_domain
|
||||
|
||||
worker.perform
|
||||
|
||||
expect(scheduled_worker_class.jobs.last).to include("meta.project" => extra_domain.project.full_path)
|
||||
end
|
||||
end
|
||||
|
|
@ -12,7 +12,7 @@ describe PagesDomainSslRenewalCronWorker do
|
|||
end
|
||||
|
||||
describe '#perform' do
|
||||
let(:project) { create :project }
|
||||
let_it_be(:project) { create :project }
|
||||
let!(:domain) { create(:pages_domain, project: project, auto_ssl_enabled: false) }
|
||||
let!(:domain_with_enabled_auto_ssl) { create(:pages_domain, project: project, auto_ssl_enabled: true) }
|
||||
let!(:domain_with_obtained_letsencrypt) do
|
||||
|
|
@ -35,12 +35,16 @@ describe PagesDomainSslRenewalCronWorker do
|
|||
|
||||
[domain,
|
||||
domain_with_obtained_letsencrypt].each do |domain|
|
||||
expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(domain.id)
|
||||
expect(PagesDomainSslRenewalWorker).not_to receive(:perform_async).with(domain.id)
|
||||
end
|
||||
|
||||
worker.perform
|
||||
end
|
||||
|
||||
it_behaves_like 'a pages cronjob scheduling jobs with context', PagesDomainSslRenewalWorker do
|
||||
let(:extra_domain) { create(:pages_domain, :with_project, auto_ssl_enabled: true) }
|
||||
end
|
||||
|
||||
shared_examples 'does nothing' do
|
||||
it 'does nothing' do
|
||||
expect(PagesDomainSslRenewalWorker).not_to receive(:perform_async)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ require 'spec_helper'
|
|||
describe PagesDomainVerificationCronWorker do
|
||||
subject(:worker) { described_class.new }
|
||||
|
||||
describe '#perform' do
|
||||
describe '#perform', :sidekiq do
|
||||
let!(:verified) { create(:pages_domain) }
|
||||
let!(:reverify) { create(:pages_domain, :reverify) }
|
||||
let!(:reverify) { create(:pages_domain, :reverify, :with_project) }
|
||||
let!(:disabled) { create(:pages_domain, :disabled) }
|
||||
|
||||
it 'does nothing if the database is read-only' do
|
||||
|
|
@ -26,5 +26,9 @@ describe PagesDomainVerificationCronWorker do
|
|||
|
||||
worker.perform
|
||||
end
|
||||
|
||||
it_behaves_like 'a pages cronjob scheduling jobs with context', PagesDomainVerificationWorker do
|
||||
let(:extra_domain) { create(:pages_domain, :reverify, :with_project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe PropagateInstanceLevelServiceWorker do
|
||||
include ExclusiveLeaseHelpers
|
||||
|
||||
describe '#perform' do
|
||||
it 'calls the propagate service with the instance level service' do
|
||||
instance_level_service = PushoverService.create(
|
||||
instance: true,
|
||||
active: true,
|
||||
properties: {
|
||||
device: 'MyDevice',
|
||||
sound: 'mic',
|
||||
priority: 4,
|
||||
user_key: 'asdf',
|
||||
api_key: '123456789'
|
||||
})
|
||||
|
||||
stub_exclusive_lease("propagate_instance_level_service_worker:#{instance_level_service.id}",
|
||||
timeout: PropagateInstanceLevelServiceWorker::LEASE_TIMEOUT)
|
||||
|
||||
expect(Projects::PropagateInstanceLevelService)
|
||||
.to receive(:propagate)
|
||||
.with(instance_level_service)
|
||||
|
||||
subject.perform(instance_level_service.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe PropagateServiceTemplateWorker do
|
||||
include ExclusiveLeaseHelpers
|
||||
|
||||
describe '#perform' do
|
||||
it 'calls the propagate service with the template' do
|
||||
template = PushoverService.create(
|
||||
template: true,
|
||||
active: true,
|
||||
properties: {
|
||||
device: 'MyDevice',
|
||||
sound: 'mic',
|
||||
priority: 4,
|
||||
user_key: 'asdf',
|
||||
api_key: '123456789'
|
||||
})
|
||||
|
||||
stub_exclusive_lease("propagate_service_template_worker:#{template.id}",
|
||||
timeout: PropagateServiceTemplateWorker::LEASE_TIMEOUT)
|
||||
|
||||
expect(Projects::PropagateServiceTemplate)
|
||||
.to receive(:propagate)
|
||||
.with(template)
|
||||
|
||||
subject.perform(template.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue