Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/admin/services_controller.rb12
-rw-r--r--app/controllers/projects/serverless/functions_controller.rb15
-rw-r--r--app/finders/projects/serverless/functions_finder.rb16
-rw-r--r--app/graphql/types/snippets/blob_type.rb4
-rw-r--r--app/models/clusters/cluster.rb6
-rw-r--r--app/models/pages_domain.rb4
-rw-r--r--app/models/project.rb14
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb2
-rw-r--r--app/models/service.rb20
-rw-r--r--app/presenters/snippet_blob_presenter.rb12
-rw-r--r--app/serializers/projects/serverless/service_entity.rb88
-rw-r--r--app/services/projects/create_service.rb8
-rw-r--r--app/services/projects/propagate_service_template.rb (renamed from app/services/projects/propagate_instance_level_service.rb)28
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_help.html.haml4
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml2
-rw-r--r--app/workers/all_queues.yml2
-rw-r--r--app/workers/pages_domain_removal_cron_worker.rb6
-rw-r--r--app/workers/pages_domain_ssl_renewal_cron_worker.rb8
-rw-r--r--app/workers/pages_domain_verification_cron_worker.rb8
-rw-r--r--app/workers/propagate_instance_level_service_worker.rb26
-rw-r--r--app/workers/propagate_service_template_worker.rb26
-rw-r--r--changelogs/unreleased/fj-add-plain-data-field-to-snippet-blob-type-endpoint.yml5
-rw-r--r--changelogs/unreleased/rename_services_template_to_instance.yml5
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/migrate/20200123092602_rename_services_template_to_instance.rb17
-rw-r--r--db/post_migrate/20191021101942_remove_empty_github_service_templates.rb4
-rw-r--r--db/post_migrate/20200123101859_cleanup_rename_services_template_to_instance.rb17
-rw-r--r--db/post_migrate/20200206111847_migrate_propagate_service_template_sidekiq_queue.rb15
-rw-r--r--db/schema.rb4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json14
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml2
-rw-r--r--lib/gitlab/serverless/service.rb98
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/vendor/jenkins/page/job.rb23
-rw-r--r--spec/controllers/admin/services_controller_spec.rb12
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb105
-rw-r--r--spec/controllers/projects/services_controller_spec.rb6
-rw-r--r--spec/factories/pages_domains.rb4
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb4
-rw-r--r--spec/fixtures/trace/sample_trace2
-rw-r--r--spec/frontend/environments/environments_app_spec.js168
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js200
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js63
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js35
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js15
-rw-r--r--spec/frontend/monitoring/components/panel_type_spec.js14
-rw-r--r--spec/graphql/types/snippets/blob_type_spec.rb7
-rw-r--r--spec/javascripts/environments/environments_app_spec.js279
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/lib/gitlab/serverless/service_spec.rb134
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb29
-rw-r--r--spec/models/service_spec.rb52
-rw-r--r--spec/presenters/snippet_blob_presenter_spec.rb38
-rw-r--r--spec/services/projects/create_service_spec.rb14
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb (renamed from spec/services/projects/propagate_instance_level_service_spec.rb)36
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb16
-rw-r--r--spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb21
-rw-r--r--spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb8
-rw-r--r--spec/workers/pages_domain_verification_cron_worker_spec.rb8
-rw-r--r--spec/workers/propagate_instance_level_service_worker_spec.rb31
-rw-r--r--spec/workers/propagate_service_template_worker_spec.rb31
68 files changed, 1007 insertions, 863 deletions
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 9cea25cc7fe..e31e0e09978 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -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
diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb
index 4b0d001fca6..0b55414d390 100644
--- a/app/controllers/projects/serverless/functions_controller.rb
+++ b/app/controllers/projects/serverless/functions_controller.rb
@@ -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
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
index 4e0b69f47e5..3b4ecbb5387 100644
--- a/app/finders/projects/serverless/functions_finder.rb
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -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
diff --git a/app/graphql/types/snippets/blob_type.rb b/app/graphql/types/snippets/blob_type.rb
index cacb2177192..d63e08a5b08 100644
--- a/app/graphql/types/snippets/blob_type.rb
+++ b/app/graphql/types/snippets/blob_type.rb
@@ -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
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 7855db960c9..7e76d324bdc 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -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
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 330764ebc7f..91767c53f81 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -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')
diff --git a/app/models/project.rb b/app/models/project.rb
index b5639039bb6..44701ef792a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 2ccf8e094e6..9e1393196ff 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -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?
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 72dabe2578a..00b06ae2595 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -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? }
diff --git a/app/models/service.rb b/app/models/service.rb
index 6d0e375e757..95b7c6927cf 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -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
diff --git a/app/presenters/snippet_blob_presenter.rb b/app/presenters/snippet_blob_presenter.rb
index 9baaacdbb24..71361b18f5a 100644
--- a/app/presenters/snippet_blob_presenter.rb
+++ b/app/presenters/snippet_blob_presenter.rb
@@ -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
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
index 10360e575bb..05beb562e40 100644
--- a/app/serializers/projects/serverless/service_entity.rb
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -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"
- 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)
+ service.environment_scope,
+ service.name, format: :json)
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
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 6b1fb92681a..7bf68e7d315 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -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
diff --git a/app/services/projects/propagate_instance_level_service.rb b/app/services/projects/propagate_service_template.rb
index dc75977ba0f..6013b00b8c6 100644
--- a/app/services/projects/propagate_instance_level_service.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -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
diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
index 7f14128a0cb..cc005dd69b7 100644
--- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
@@ -10,8 +10,8 @@
%p.inline
= s_("MattermostService|See list of available commands in Mattermost after setting up this service, by entering")
%kbd.inline /&lt;trigger&gt; 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
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index 447f7f074a8..7f6717e298c 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -11,7 +11,7 @@
%p.inline
= s_("SlackService|See list of available commands in Slack after setting up this service, by entering")
%kbd.inline /&lt;command&gt; help
- - unless @service.instance?
+ - unless @service.template?
%p= _("To set up this service:")
%ul.list-unstyled.indent-list
%li
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 22dd7f5843f..42ee0994617 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -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:
diff --git a/app/workers/pages_domain_removal_cron_worker.rb b/app/workers/pages_domain_removal_cron_worker.rb
index b14d0d4597c..1c96dd6ad8c 100644
--- a/app/workers/pages_domain_removal_cron_worker.rb
+++ b/app/workers/pages_domain_removal_cron_worker.rb
@@ -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
diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
index b20ed23dc89..c1201b935d1 100644
--- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
@@ -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
diff --git a/app/workers/pages_domain_verification_cron_worker.rb b/app/workers/pages_domain_verification_cron_worker.rb
index 8bd7fe33334..b06aa65a8e5 100644
--- a/app/workers/pages_domain_verification_cron_worker.rb
+++ b/app/workers/pages_domain_verification_cron_worker.rb
@@ -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
diff --git a/app/workers/propagate_instance_level_service_worker.rb b/app/workers/propagate_instance_level_service_worker.rb
deleted file mode 100644
index 64ea61cabfa..00000000000
--- a/app/workers/propagate_instance_level_service_worker.rb
+++ /dev/null
@@ -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
diff --git a/app/workers/propagate_service_template_worker.rb b/app/workers/propagate_service_template_worker.rb
new file mode 100644
index 00000000000..73a2b453207
--- /dev/null
+++ b/app/workers/propagate_service_template_worker.rb
@@ -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
diff --git a/changelogs/unreleased/fj-add-plain-data-field-to-snippet-blob-type-endpoint.yml b/changelogs/unreleased/fj-add-plain-data-field-to-snippet-blob-type-endpoint.yml
new file mode 100644
index 00000000000..72c672cf48f
--- /dev/null
+++ b/changelogs/unreleased/fj-add-plain-data-field-to-snippet-blob-type-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Add plain_highlighted_data field to SnippetBlobType
+merge_request: 24856
+author:
+type: changed
diff --git a/changelogs/unreleased/rename_services_template_to_instance.yml b/changelogs/unreleased/rename_services_template_to_instance.yml
deleted file mode 100644
index 999981fed40..00000000000
--- a/changelogs/unreleased/rename_services_template_to_instance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Service model: Rename template attribute to instance'
-merge_request: 23595
-author:
-type: other
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index ede061f0359..1cb19d18a0d 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -194,7 +194,7 @@
- 1
- - project_update_repository_storage
- 1
-- - propagate_instance_level_service
+- - propagate_service_template
- 1
- - reactive_caching
- 1
diff --git a/db/migrate/20200123092602_rename_services_template_to_instance.rb b/db/migrate/20200123092602_rename_services_template_to_instance.rb
deleted file mode 100644
index 42964dfe348..00000000000
--- a/db/migrate/20200123092602_rename_services_template_to_instance.rb
+++ /dev/null
@@ -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
diff --git a/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb b/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb
index 2f9db9e2cf6..64abe93b3e8 100644
--- a/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb
+++ b/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb
@@ -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
diff --git a/db/post_migrate/20200123101859_cleanup_rename_services_template_to_instance.rb b/db/post_migrate/20200123101859_cleanup_rename_services_template_to_instance.rb
deleted file mode 100644
index 904cad616d7..00000000000
--- a/db/post_migrate/20200123101859_cleanup_rename_services_template_to_instance.rb
+++ /dev/null
@@ -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
diff --git a/db/post_migrate/20200206111847_migrate_propagate_service_template_sidekiq_queue.rb b/db/post_migrate/20200206111847_migrate_propagate_service_template_sidekiq_queue.rb
deleted file mode 100644
index 30f6c2038d9..00000000000
--- a/db/post_migrate/20200206111847_migrate_propagate_service_template_sidekiq_queue.rb
+++ /dev/null
@@ -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
diff --git a/db/schema.rb b/db/schema.rb
index 6ddf61799e3..ecc4870b33d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -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
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index c0600d45d13..399ae43c9d0 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -6778,6 +6778,11 @@ type SnippetBlob {
path: String
"""
+ Blob plain highlighted data
+ """
+ plainHighlightedData: String
+
+ """
Blob raw content endpoint path
"""
rawPath: String!
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 5b80c8d74a6..99db9539688 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -7613,6 +7613,20 @@
"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",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ee3c05eb6a0..55b22d22337 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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 |
diff --git a/lib/api/services.rb b/lib/api/services.rb
index e6b48274989..a3b5d2cc4b7 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -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
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 9acc2098823..afa575241a1 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -257,7 +257,7 @@ excluded_attributes:
- :token
- :token_encrypted
services:
- - :instance
+ - :template
error_tracking_setting:
- :encrypted_token
- :encrypted_token_iv
diff --git a/lib/gitlab/serverless/service.rb b/lib/gitlab/serverless/service.rb
new file mode 100644
index 00000000000..643e076c587
--- /dev/null
+++ b/lib/gitlab/serverless/service.rb
@@ -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
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 75fa3f4c718..f10eb82e03e 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -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
diff --git a/qa/qa.rb b/qa/qa.rb
index 29205c63251..53a43e8da64 100644
--- a/qa/qa.rb
+++ b/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
diff --git a/qa/qa/vendor/jenkins/page/job.rb b/qa/qa/vendor/jenkins/page/job.rb
new file mode 100644
index 00000000000..498ce6041b8
--- /dev/null
+++ b/qa/qa/vendor/jenkins/page/job.rb
@@ -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
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb
index 6f59a5ac016..44233776865 100644
--- a/spec/controllers/admin/services_controller_spec.rb
+++ b/spec/controllers/admin/services_controller_spec.rb
@@ -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: {} } }
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index f0153ac37bf..db7533eb609 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -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
)
]
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index b6c64a964a6..fb7cca3997b 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -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
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index 7606e806e04..f914128ed3b 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -380,5 +380,9 @@ x6zG6WoibsbsJMj70nwseUnPTBQNDP+j61RJjC/r
scope { :instance }
usage { :serverless }
end
+
+ trait :with_project do
+ association :project
+ end
end
end
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 67eda297b91..4e9f3d371ce 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -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
diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace
index 1aba1e76d0b..d774d154496 100644
--- a/spec/fixtures/trace/sample_trace
+++ b/spec/fixtures/trace/sample_trace
@@ -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
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
new file mode 100644
index 00000000000..f3d2bd2462e
--- /dev/null
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -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');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 7a039d46d39..15c82242262 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -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);
-
- 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 number of environments', () => {
+ expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length);
- 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();
});
});
});
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index 51c7b22f242..0bcfabe6415 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -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();
-
- 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);
+ 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);
+ });
});
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();
-
- expect(findAlert().exists()).toBe(true);
- expect(findAlert().text()).toBe(errMsg);
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(errMsg);
- 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', () => {
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
index 75a488b5c7b..10fd58f749d 100644
--- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
@@ -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);
+ });
});
});
});
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index 983785d0ecc..28a6af64394 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -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();
});
});
});
diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/panel_type_spec.js
index 6d8bd1d3a30..0d79babf386 100644
--- a/spec/frontend/monitoring/components/panel_type_spec.js
+++ b/spec/frontend/monitoring/components/panel_type_spec.js
@@ -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();
});
diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb
index e7d4e5dfa2d..a263fada644 100644
--- a/spec/graphql/types/snippets/blob_type_spec.rb
+++ b/spec/graphql/types/snippets/blob_type_spec.rb
@@ -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
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
deleted file mode 100644
index 6c05b609923..00000000000
--- a/spec/javascripts/environments/environments_app_spec.js
+++ /dev/null
@@ -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' });
- });
- });
- });
-});
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index f4d3c9e613e..c899217d164 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 7695617cb57..55e7d6bd1e3 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -453,7 +453,7 @@ Service:
- updated_at
- active
- properties
-- instance
+- template
- push_events
- issues_events
- commit_events
diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb
new file mode 100644
index 00000000000..f618dd02cdb
--- /dev/null
+++ b/spec/lib/gitlab/serverless/service_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 8e9a816ba6a..9a49d334f52 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -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)
diff --git a/spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb b/spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb
deleted file mode 100644
index 2fffe638117..00000000000
--- a/spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index df2ed4911ec..f58bcbebd67 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -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
diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb
index 2a113e353c8..92893ec597a 100644
--- a/spec/presenters/snippet_blob_presenter_spec.rb
+++ b/spec/presenters/snippet_blob_presenter_spec.rb
@@ -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
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 9d23556efda..a8e7919dc81 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -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
diff --git a/spec/services/projects/propagate_instance_level_service_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index a842842a010..2c3effec617 100644
--- a/spec/services/projects/propagate_instance_level_service_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -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
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 0d312575e91..e2d96db02be 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -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
diff --git a/spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb b/spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb
new file mode 100644
index 00000000000..9e8102aea53
--- /dev/null
+++ b/spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb
@@ -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
diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
index 10c23cbb6d4..736acc40371 100644
--- a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
@@ -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)
diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb
index 3fb86adee11..6dd6c33f5fe 100644
--- a/spec/workers/pages_domain_verification_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb
@@ -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
diff --git a/spec/workers/propagate_instance_level_service_worker_spec.rb b/spec/workers/propagate_instance_level_service_worker_spec.rb
deleted file mode 100644
index 6552b198181..00000000000
--- a/spec/workers/propagate_instance_level_service_worker_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb
new file mode 100644
index 00000000000..fb4ced77832
--- /dev/null
+++ b/spec/workers/propagate_service_template_worker_spec.rb
@@ -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