diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-11 21:08:58 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-11 21:08:58 +0300 |
commit | 1ca9950d5f890cd8f185e1eda158b969a7244fe2 (patch) | |
tree | 6f62715938a4b2b001705c51c697609a8e0850ae | |
parent | bcc77054ee9aefd1e332e04a4189390fd5a3112e (diff) |
Add latest changes from gitlab-org/gitlab@master
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 /<trigger> help - - unless enabled || @service.instance? + - unless enabled || @service.template? = render 'projects/services/mattermost_slash_commands/detailed_help', subject: @service -- if enabled && !@service.instance? +- if enabled && !@service.template? = render 'projects/services/mattermost_slash_commands/installation_info', subject: @service 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 /<command> 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 @@ -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 |