summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-05 15:07:52 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-05 15:07:52 +0000
commitafe2b984524ae4b0c8a0636db7ec5b2c452f0734 (patch)
tree3de39f954c7239e09a9afe84263a64e7042b2b60
parent5a6b36b60502c50ab59c0bc3c345793b70a3d548 (diff)
downloadgitlab-ce-afe2b984524ae4b0c8a0636db7ec5b2c452f0734.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS8
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml4
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js16
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/import/gitea_controller.rb23
-rw-r--r--app/models/deployment.rb4
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/models/service.rb1
-rw-r--r--app/services/ci/find_exposed_artifacts_service.rb6
-rw-r--r--app/views/projects/wikis/_form.html.haml12
-rw-r--r--changelogs/unreleased/15103-markup-tips-for-markdown-shown-while-editing-wiki-pages-in-other-fo.yml5
-rw-r--r--changelogs/unreleased/196832-drop-feature-toggle.yml5
-rw-r--r--changelogs/unreleased/fooishbar-gitlab-fix-project-job-path-exposed-artifacts.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-fix-500-on-gitea-importer.yml5
-rw-r--r--changelogs/unreleased/remove-merged-branch-names-ff.yml5
-rw-r--r--changelogs/unreleased/unique-service-template-per-type.yml5
-rw-r--r--config/initializers/active_record_force_reconnects.rb7
-rw-r--r--danger/telemetry/Dangerfile19
-rw-r--r--db/migrate/20200304160801_delete_template_services_duplicated_by_type.rb25
-rw-r--r--db/migrate/20200304160823_add_index_to_service_unique_template_per_type.rb17
-rw-r--r--db/schema.rb3
-rw-r--r--lib/gitlab/database/connection_timer.rb50
-rw-r--r--lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb30
-rw-r--r--lib/gitlab/import_export/project/tree_restorer.rb5
-rw-r--r--lib/gitlab_danger.rb1
-rw-r--r--locale/gitlab.pot5
-rw-r--r--spec/controllers/application_controller_spec.rb35
-rw-r--r--spec/controllers/import/gitea_controller_spec.rb16
-rw-r--r--spec/frontend/fixtures/metrics_dashboard.rb41
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js26
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js11
-rw-r--r--spec/frontend/monitoring/mock_data.js136
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js45
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js29
-rw-r--r--spec/frontend/wikis_spec.js27
-rw-r--r--spec/lib/gitlab/database/connection_timer_spec.rb100
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb55
-rw-r--r--spec/lib/gitlab_danger_spec.rb2
-rw-r--r--spec/migrations/delete_template_services_duplicated_by_type_spec.rb24
-rw-r--r--spec/models/service_spec.rb10
-rw-r--r--spec/services/ci/find_exposed_artifacts_service_spec.rb42
-rw-r--r--spec/services/deployments/after_create_service_spec.rb2
42 files changed, 690 insertions, 187 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index a0ee7c456e1..fa87b981f5d 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -48,3 +48,11 @@ Dangerfile @gl-quality/eng-prod
# Delivery owner files
/.gitlab/ci/releases.gitlab-ci.yml @gitlab-org/delivery
+
+# Telemetry owner files
+/ee/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry
+/ee/lib/ee/gitlab/usage_data.rb @gitlab-org/growth/telemetry
+/lib/gitlab/grafana_embed_usage_data.rb @gitlab-org/growth/telemetry
+/lib/gitlab/usage_data.rb @gitlab-org/growth/telemetry
+/lib/gitlab/cycle_analytics/usage_data.rb @gitlab-org/growth/telemetry
+/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 08c0efe40db..5b1f375d30e 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -51,7 +51,9 @@ docs lint:
script:
- scripts/lint-doc.sh
# Prepare docs for build
- - mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
+ # The path must be 'ee/' because we have hardcoded links relying on it
+ # https://gitlab.com/gitlab-org/gitlab-docs/-/blob/887850752fc0e72856da6632db132f005ba77f16/content/index.erb#L44-63
+ - mv doc/ /tmp/gitlab-docs/content/ee
- cd /tmp/gitlab-docs
# Build HTML from Markdown
- bundle exec nanoc
diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js
index 6b02a074abf..93afdc54ce1 100644
--- a/app/assets/javascripts/pages/projects/wikis/wikis.js
+++ b/app/assets/javascripts/pages/projects/wikis/wikis.js
@@ -1,6 +1,13 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { s__, sprintf } from '~/locale';
+const MARKDOWN_LINK_TEXT = {
+ markdown: '[Link Title](page-slug)',
+ rdoc: '{Link title}[link:page-slug]',
+ asciidoc: 'link:page-slug[Link title]',
+ org: '[[page-slug]]',
+};
+
export default class Wikis {
constructor() {
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
@@ -28,6 +35,15 @@ export default class Wikis {
window.addEventListener('resize', () => this.renderSidebar());
this.renderSidebar();
+
+ const changeFormatSelect = document.querySelector('#wiki_format');
+ const linkExample = document.querySelector('.js-markup-link-example');
+
+ if (changeFormatSelect) {
+ changeFormatSelect.addEventListener('change', e => {
+ linkExample.innerHTML = MARKDOWN_LINK_TEXT[e.target.value];
+ });
+ }
}
handleWikiTitleChange(e) {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 484e86964f6..8cb8fd6dd4c 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -149,10 +149,6 @@ class ApplicationController < ActionController::Base
payload[:username] = logged_user.try(:username)
end
- if response.status == 422 && response.body.present? && response.content_type == 'application/json'
- payload[:response] = response.body
- end
-
payload[:queue_duration] = request.env[::Gitlab::Middleware::RailsQueueDuration::GITLAB_RAILS_QUEUE_DURATION_KEY]
end
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index a23b2f8139e..f0888e08622 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -16,7 +16,13 @@ class Import::GiteaController < Import::GithubController
# Must be defined or it will 404
def status
- super
+ if blocked_url?
+ session[access_token_key] = nil
+
+ redirect_to new_import_url, alert: _('Specified URL cannot be used.')
+ else
+ super
+ end
end
private
@@ -54,4 +60,19 @@ class Import::GiteaController < Import::GithubController
def client_options
{ host: provider_url, api_version: 'v1' }
end
+
+ def blocked_url?
+ Gitlab::UrlBlocker.blocked_url?(
+ provider_url,
+ {
+ allow_localhost: allow_local_requests?,
+ allow_local_network: allow_local_requests?,
+ schemes: %w(http https)
+ }
+ )
+ end
+
+ def allow_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index b118404b916..707c4e8157d 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -280,12 +280,12 @@ class Deployment < ApplicationRecord
errors.add(:ref, _('The branch or tag does not exist'))
end
+ private
+
def ref_path
File.join(environment.ref_path, 'deployments', iid.to_s)
end
- private
-
def legacy_finished_at
self.created_at if success? && !read_attribute(:finished_at)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index beb65ff26fd..534c62ec467 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -911,10 +911,8 @@ class Repository
def merged_branch_names(branch_names = [])
# Currently we should skip caching if requesting all branch names
# This is only used in a few places, notably app/services/branches/delete_merged_service.rb,
- # and it could potentially result in a very large cache/performance issues with the current
- # implementation.
- skip_cache = branch_names.empty? || Feature.disabled?(:merged_branch_names_redis_caching, default_enabled: true)
- return raw_repository.merged_branch_names(branch_names) if skip_cache
+ # and it could potentially result in a very large cache.
+ return raw_repository.merged_branch_names(branch_names) if branch_names.empty?
cache = redis_hash_cache
diff --git a/app/models/service.rb b/app/models/service.rb
index 41ae197f432..e6b32db7eef 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -34,6 +34,7 @@ class Service < ApplicationRecord
validates :project_id, presence: true, unless: -> { template? }
validates :type, presence: true
+ validates :template, uniqueness: { scope: :type }, if: -> { template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
diff --git a/app/services/ci/find_exposed_artifacts_service.rb b/app/services/ci/find_exposed_artifacts_service.rb
index d268252577f..abbeb101be2 100644
--- a/app/services/ci/find_exposed_artifacts_service.rb
+++ b/app/services/ci/find_exposed_artifacts_service.rb
@@ -35,7 +35,7 @@ module Ci
{
text: job.artifacts_expose_as,
url: path_for_entries(metadata_entries, job),
- job_path: project_job_path(project, job),
+ job_path: project_job_path(job.project, job),
job_name: job.name
}
end
@@ -59,9 +59,9 @@ module Ci
return if entries.empty?
if single_artifact?(entries)
- file_project_job_artifacts_path(project, job, entries.first.path)
+ file_project_job_artifacts_path(job.project, job, entries.first.path)
else
- browse_project_job_artifacts_path(project, job)
+ browse_project_job_artifacts_path(job.project, job)
end
end
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index bc20635f0c5..d29abfa937d 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -43,8 +43,16 @@
.form-text.text-muted
= succeed '.' do
- = (s_("WikiMarkdownTip|To link to a (new) page, simply type %{link_example}") % { link_example: '<code>[Link Title](page-slug)</code>' }).html_safe
-
+ - case @page.format.to_s
+ - when 'rdoc'
+ - link_example = '{Link title}[link:page-slug]'
+ - when 'asciidoc'
+ - link_example = 'link:page-slug[Link title]'
+ - when 'org'
+ - link_example = '[[page-slug]]'
+ - else
+ - link_example = '[Link Title](page-slug)'
+ = (s_('WikiMarkdownTip|To link to a (new) page, simply type <code class="js-markup-link-example">%{link_example}</code>') % { link_example: link_example }).html_safe
= succeed '.' do
- markdown_link = link_to s_("WikiMarkdownDocs|documentation"), help_page_path('user/markdown', anchor: 'wiki-specific-markdown')
= (s_("WikiMarkdownDocs|More examples are in the %{docs_link}") % { docs_link: markdown_link }).html_safe
diff --git a/changelogs/unreleased/15103-markup-tips-for-markdown-shown-while-editing-wiki-pages-in-other-fo.yml b/changelogs/unreleased/15103-markup-tips-for-markdown-shown-while-editing-wiki-pages-in-other-fo.yml
new file mode 100644
index 00000000000..723a52d65a1
--- /dev/null
+++ b/changelogs/unreleased/15103-markup-tips-for-markdown-shown-while-editing-wiki-pages-in-other-fo.yml
@@ -0,0 +1,5 @@
+---
+title: Markup tips for Markdown shown while editing wiki pages in other formats
+merge_request: 25974
+author:
+type: fixed
diff --git a/changelogs/unreleased/196832-drop-feature-toggle.yml b/changelogs/unreleased/196832-drop-feature-toggle.yml
new file mode 100644
index 00000000000..bd9de0e33ca
--- /dev/null
+++ b/changelogs/unreleased/196832-drop-feature-toggle.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize project representation in large imports
+merge_request: !22598
+author:
+type: performance
diff --git a/changelogs/unreleased/fooishbar-gitlab-fix-project-job-path-exposed-artifacts.yml b/changelogs/unreleased/fooishbar-gitlab-fix-project-job-path-exposed-artifacts.yml
new file mode 100644
index 00000000000..2a361fcc264
--- /dev/null
+++ b/changelogs/unreleased/fooishbar-gitlab-fix-project-job-path-exposed-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Fix links to exposed artifacts in MRs from forks
+merge_request: 25868
+author: Daniel Stone
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-fix-500-on-gitea-importer.yml b/changelogs/unreleased/georgekoltsov-fix-500-on-gitea-importer.yml
new file mode 100644
index 00000000000..d1d37115c9c
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-fix-500-on-gitea-importer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 Error when using Gitea Importer
+merge_request: 26166
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove-merged-branch-names-ff.yml b/changelogs/unreleased/remove-merged-branch-names-ff.yml
new file mode 100644
index 00000000000..103ac3d3792
--- /dev/null
+++ b/changelogs/unreleased/remove-merged-branch-names-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of Repository#merged_branch_names
+merge_request: 26005
+author:
+type: performance
diff --git a/changelogs/unreleased/unique-service-template-per-type.yml b/changelogs/unreleased/unique-service-template-per-type.yml
new file mode 100644
index 00000000000..e394b959e7d
--- /dev/null
+++ b/changelogs/unreleased/unique-service-template-per-type.yml
@@ -0,0 +1,5 @@
+---
+title: Validates only one service template per type
+merge_request: 26380
+author:
+type: other
diff --git a/config/initializers/active_record_force_reconnects.rb b/config/initializers/active_record_force_reconnects.rb
new file mode 100644
index 00000000000..73dfaf5e121
--- /dev/null
+++ b/config/initializers/active_record_force_reconnects.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+Gitlab::Database::ConnectionTimer.configure do |config|
+ config.interval = Rails.application.config_for(:database)[:force_reconnect_interval]
+end
+
+ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin)
diff --git a/danger/telemetry/Dangerfile b/danger/telemetry/Dangerfile
new file mode 100644
index 00000000000..a9e66da6ab7
--- /dev/null
+++ b/danger/telemetry/Dangerfile
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+TELEMETRY_CHANGED_FILES_MESSAGE = <<~MSG
+This merge request adds or changes files that require a
+review from the Data team and Telemetry team @gitlab-org/growth/telemetry.
+The specific group is mentioned in order to send a notification to team members.
+MSG
+
+usage_data_changed_files = git.modified_files.grep(%r{usage_data})
+
+if usage_data_changed_files.any?
+ warn format(TELEMETRY_CHANGED_FILES_MESSAGE)
+
+ USAGE_DATA_FILES_MESSAGE = <<~MSG
+ The following files require a review from the [Data team and Telemetry team](https://gitlab.com/groups/gitlab-org/growth/telemetry/-/group_members?with_inherited_permissions=exclude):
+ MSG
+
+ markdown(USAGE_DATA_FILES_MESSAGE + helper.markdown_list(usage_data_changed_files))
+end
diff --git a/db/migrate/20200304160801_delete_template_services_duplicated_by_type.rb b/db/migrate/20200304160801_delete_template_services_duplicated_by_type.rb
new file mode 100644
index 00000000000..a1c5161aea0
--- /dev/null
+++ b/db/migrate/20200304160801_delete_template_services_duplicated_by_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class DeleteTemplateServicesDuplicatedByType < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ # Delete service templates with duplicated types. Keep the service
+ # template with the lowest `id` because that is the service template used:
+ # https://gitlab.com/gitlab-org/gitlab/-/blob/v12.8.1-ee/app/controllers/admin/services_controller.rb#L37
+ execute <<~SQL
+ DELETE
+ FROM services
+ WHERE TEMPLATE = TRUE
+ AND id NOT IN
+ (SELECT MIN(id)
+ FROM services
+ WHERE TEMPLATE = TRUE
+ GROUP BY TYPE);
+ SQL
+ end
+
+ def down
+ # This migration cannot be reversed.
+ end
+end
diff --git a/db/migrate/20200304160823_add_index_to_service_unique_template_per_type.rb b/db/migrate/20200304160823_add_index_to_service_unique_template_per_type.rb
new file mode 100644
index 00000000000..b81e5acf67f
--- /dev/null
+++ b/db/migrate/20200304160823_add_index_to_service_unique_template_per_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToServiceUniqueTemplatePerType < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:services, [:type, :template], unique: true, where: 'template IS TRUE')
+ end
+
+ def down
+ remove_concurrent_index(:services, [:type, :template])
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a42d31160d2..2360c0f4025 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_03_04_090155) do
+ActiveRecord::Schema.define(version: 2020_03_04_160823) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -3898,6 +3898,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_090155) do
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", "template"], name: "index_services_on_type_and_template", unique: true, where: "(template IS TRUE)"
t.index ["type"], name: "index_services_on_type"
end
diff --git a/lib/gitlab/database/connection_timer.rb b/lib/gitlab/database/connection_timer.rb
new file mode 100644
index 00000000000..ef8d52ba71c
--- /dev/null
+++ b/lib/gitlab/database/connection_timer.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ class ConnectionTimer
+ DEFAULT_INTERVAL = 3600
+ RANDOMIZATION_INTERVAL = 600
+
+ class << self
+ def configure
+ yield self
+ end
+
+ def starting_now
+ # add a small amount of randomization to the interval, so reconnects don't all occur at once
+ new(interval_with_randomization, current_clock_value)
+ end
+
+ attr_writer :interval
+
+ def interval
+ @interval ||= DEFAULT_INTERVAL
+ end
+
+ def interval_with_randomization
+ interval + rand(RANDOMIZATION_INTERVAL) if interval.positive?
+ end
+
+ def current_clock_value
+ Concurrent.monotonic_time
+ end
+ end
+
+ attr_reader :interval, :starting_clock_value
+
+ def initialize(interval, starting_clock_value)
+ @interval = interval
+ @starting_clock_value = starting_clock_value
+ end
+
+ def expired?
+ interval&.positive? && self.class.current_clock_value > (starting_clock_value + interval)
+ end
+
+ def reset!
+ @starting_clock_value = self.class.current_clock_value
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb b/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb
new file mode 100644
index 00000000000..9f664fa2137
--- /dev/null
+++ b/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module PostgresqlAdapter
+ module ForceDisconnectableMixin
+ extend ActiveSupport::Concern
+
+ prepended do
+ set_callback :checkin, :after, :force_disconnect_if_old!
+ end
+
+ def force_disconnect_if_old!
+ if force_disconnect_timer.expired?
+ disconnect!
+ reset_force_disconnect_timer!
+ end
+ end
+
+ def reset_force_disconnect_timer!
+ force_disconnect_timer.reset!
+ end
+
+ def force_disconnect_timer
+ @force_disconnect_timer ||= ConnectionTimer.starting_now
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/project/tree_restorer.rb b/lib/gitlab/import_export/project/tree_restorer.rb
index 904cb461ac3..295e0d5f348 100644
--- a/lib/gitlab/import_export/project/tree_restorer.rb
+++ b/lib/gitlab/import_export/project/tree_restorer.rb
@@ -43,10 +43,7 @@ module Gitlab
def read_tree_hash
path = File.join(@shared.export_path, 'project.json')
- dedup_entries = large_project?(path) &&
- Feature.enabled?(:dedup_project_import_metadata, project.group)
-
- @tree_loader.load(path, dedup_entries: dedup_entries)
+ @tree_loader.load(path, dedup_entries: large_project?(path))
rescue => e
Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
diff --git a/lib/gitlab_danger.rb b/lib/gitlab_danger.rb
index 29300d02985..ee0951f18ca 100644
--- a/lib/gitlab_danger.rb
+++ b/lib/gitlab_danger.rb
@@ -11,6 +11,7 @@ class GitlabDanger
karma
database
commit_messages
+ telemetry
].freeze
CI_ONLY_RULES ||= %w[
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5353da415ed..7c63ec93374 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18480,6 +18480,9 @@ msgstr ""
msgid "Specific Runners"
msgstr ""
+msgid "Specified URL cannot be used."
+msgstr ""
+
msgid "Specify an e-mail address regex pattern to identify default internal users."
msgstr ""
@@ -22296,7 +22299,7 @@ msgstr ""
msgid "WikiMarkdownDocs|documentation"
msgstr ""
-msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgid "WikiMarkdownTip|To link to a (new) page, simply type <code class=\"js-markup-link-example\">%{link_example}</code>"
msgstr ""
msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 90f6697a8c0..4a3d591e94d 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -530,41 +530,6 @@ describe ApplicationController do
expect(controller.last_payload).to include('correlation_id' => 'new-id')
end
-
- context '422 errors' do
- it 'logs a response with a string' do
- response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {})
- allow(controller).to receive(:response).and_return(response)
- get :index
-
- expect(controller.last_payload[:response]).to eq('Hello world')
- end
-
- it 'logs a response with an array' do
- body = ['I want', 'my hat back']
- response = spy(ActionDispatch::Response, status: 422, body: body, content_type: 'application/json', cookies: {})
- allow(controller).to receive(:response).and_return(response)
- get :index
-
- expect(controller.last_payload[:response]).to eq(body)
- end
-
- it 'does not log a string with an empty body' do
- response = spy(ActionDispatch::Response, status: 422, body: nil, content_type: 'application/json', cookies: {})
- allow(controller).to receive(:response).and_return(response)
- get :index
-
- expect(controller.last_payload.has_key?(:response)).to be_falsey
- end
-
- it 'does not log an HTML body' do
- response = spy(ActionDispatch::Response, status: 422, body: 'This is a test', content_type: 'application/html', cookies: {})
- allow(controller).to receive(:response).and_return(response)
- get :index
-
- expect(controller.last_payload.has_key?(:response)).to be_falsey
- end
- end
end
describe '#access_denied' do
diff --git a/spec/controllers/import/gitea_controller_spec.rb b/spec/controllers/import/gitea_controller_spec.rb
index 730e3f98c98..b4834dffdb3 100644
--- a/spec/controllers/import/gitea_controller_spec.rb
+++ b/spec/controllers/import/gitea_controller_spec.rb
@@ -28,10 +28,24 @@ describe Import::GiteaController do
describe "GET status" do
it_behaves_like 'a GitHub-ish import controller: GET status' do
+ let(:extra_assign_expectations) { { gitea_host_url: host_url } }
+
before do
assign_host_url
end
- let(:extra_assign_expectations) { { gitea_host_url: host_url } }
+
+ context 'when host url is local or not http' do
+ %w[https://localhost:3000 http://192.168.0.1 ftp://testing].each do |url|
+ let(:host_url) { url }
+
+ it 'denies network request' do
+ get :status, format: :json
+
+ expect(controller).to redirect_to(new_import_url)
+ expect(flash[:alert]).to eq('Specified URL cannot be used.')
+ end
+ end
+ end
end
end
diff --git a/spec/frontend/fixtures/metrics_dashboard.rb b/spec/frontend/fixtures/metrics_dashboard.rb
new file mode 100644
index 00000000000..f0c741af37d
--- /dev/null
+++ b/spec/frontend/fixtures/metrics_dashboard.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+ include MetricsDashboardHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { project_with_dashboard('.gitlab/dashboards/test.yml') }
+ let(:environment) { create(:environment, project: project) }
+ let(:params) { { environment: environment } }
+
+ before(:all) do
+ clean_frontend_fixtures('metrics_dashboard/')
+ end
+
+ controller(::ApplicationController) do
+ include MetricsDashboard
+ end
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+
+ allow(controller).to receive(:project).and_return(project)
+ allow(controller)
+ .to receive(:metrics_dashboard_params)
+ .and_return(params)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'metrics_dashboard/environment_metrics_dashboard.json' do
+ routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" }
+ response = get :metrics_dashboard, format: :json
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index 8dcb54e3fd9..797c52cd31b 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -10,16 +10,21 @@ import TimeSeries from '~/monitoring/components/charts/time_series.vue';
import * as types from '~/monitoring/stores/mutation_types';
import {
deploymentData,
- metricsDashboardPayload,
- mockedQueryResultPayload,
+ mockedQueryResultFixture,
metricsDashboardViewModel,
mockProjectDir,
mockHost,
} from '../../mock_data';
import * as iconUtils from '~/lib/utils/icon_utils';
+import { getJSONFixture } from '../../../helpers/fixtures';
const mockSvgPathContent = 'mockSvgPathContent';
+const metricsDashboardFixture = getJSONFixture(
+ 'metrics_dashboard/environment_metrics_dashboard.json',
+);
+const metricsDashboardPayload = metricsDashboardFixture.dashboard;
+
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
jest.fn(func => {
@@ -59,13 +64,11 @@ describe('Time series component', () => {
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
- // Mock data contains 2 panel groups, with 1 and 2 panels respectively
store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
- mockedQueryResultPayload,
+ mockedQueryResultFixture,
);
-
- // Pick the second panel group and the first panel in it
+ // dashboard is a dynamically generated fixture and stored at environment_metrics_dashboard.json
[mockGraphData] = store.state.monitoringDashboard.dashboard.panelGroups[0].panels;
});
@@ -189,9 +192,8 @@ describe('Time series component', () => {
});
it('formats tooltip content', () => {
- const name = 'Total';
- const value = '5.556MB';
-
+ const name = 'Status Code';
+ const value = '5.556';
const dataIndex = 0;
const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
@@ -399,7 +401,7 @@ describe('Time series component', () => {
});
it('formats and rounds to 2 decimal places', () => {
- expect(dataFormatter(0.88888)).toBe('0.89MB');
+ expect(dataFormatter(0.88888)).toBe('0.89');
});
it('deployment formatter is set as is required to display a tooltip', () => {
@@ -441,7 +443,7 @@ describe('Time series component', () => {
it('constructs a label for the chart y-axis', () => {
const { yAxis } = getChartOptions();
- expect(yAxis[0].name).toBe('Total Memory Used');
+ expect(yAxis[0].name).toBe('Requests / Sec');
});
});
});
@@ -544,7 +546,7 @@ describe('Time series component', () => {
store = createStore();
const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]);
graphData.metrics.forEach(metric =>
- Object.assign(metric, { result: mockedQueryResultPayload.result }),
+ Object.assign(metric, { result: mockedQueryResultFixture.result }),
);
timeSeriesChart = makeTimeSeriesChart(graphData, 'area-chart');
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index bec22b28a5c..b9d838085a1 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import { metricStates } from '~/monitoring/constants';
import Dashboard from '~/monitoring/components/dashboard.vue';
+import { getJSONFixture } from '../../../../spec/frontend/helpers/fixtures';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
@@ -15,16 +16,20 @@ import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { setupComponentStore, propsData } from '../init_utils';
import {
- metricsDashboardPayload,
- mockedQueryResultPayload,
metricsDashboardViewModel,
environmentData,
dashboardGitResponse,
+ mockedQueryResultFixture,
} from '../mock_data';
const localVue = createLocalVue();
const expectedPanelCount = 4;
+const metricsDashboardFixture = getJSONFixture(
+ 'metrics_dashboard/environment_metrics_dashboard.json',
+);
+const metricsDashboardPayload = metricsDashboardFixture.dashboard;
+
describe('Dashboard', () => {
let store;
let wrapper;
@@ -196,7 +201,7 @@ describe('Dashboard', () => {
);
wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
- mockedQueryResultPayload,
+ mockedQueryResultFixture,
);
return wrapper.vm.$nextTick().then(() => {
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index 47651eca3c8..c98b6a9592f 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -242,95 +242,75 @@ export const metricsNewGroupsAPIResponse = [
},
];
+const metricsResult = [
+ {
+ metric: {},
+ values: [
+ [1563272065.589, '10.396484375'],
+ [1563272125.589, '10.333984375'],
+ [1563272185.589, '10.333984375'],
+ [1563272245.589, '10.333984375'],
+ [1563272305.589, '10.333984375'],
+ [1563272365.589, '10.333984375'],
+ [1563272425.589, '10.38671875'],
+ [1563272485.589, '10.333984375'],
+ [1563272545.589, '10.333984375'],
+ [1563272605.589, '10.333984375'],
+ [1563272665.589, '10.333984375'],
+ [1563272725.589, '10.333984375'],
+ [1563272785.589, '10.396484375'],
+ [1563272845.589, '10.333984375'],
+ [1563272905.589, '10.333984375'],
+ [1563272965.589, '10.3984375'],
+ [1563273025.589, '10.337890625'],
+ [1563273085.589, '10.34765625'],
+ [1563273145.589, '10.337890625'],
+ [1563273205.589, '10.337890625'],
+ [1563273265.589, '10.337890625'],
+ [1563273325.589, '10.337890625'],
+ [1563273385.589, '10.337890625'],
+ [1563273445.589, '10.337890625'],
+ [1563273505.589, '10.337890625'],
+ [1563273565.589, '10.337890625'],
+ [1563273625.589, '10.337890625'],
+ [1563273685.589, '10.337890625'],
+ [1563273745.589, '10.337890625'],
+ [1563273805.589, '10.337890625'],
+ [1563273865.589, '10.390625'],
+ [1563273925.589, '10.390625'],
+ ],
+ },
+];
+
export const mockedEmptyResult = {
metricId: '1_response_metrics_nginx_ingress_throughput_status_code',
result: [],
};
+export const mockedEmptyThroughputResult = {
+ metricId: 'undefined_response_metrics_nginx_ingress_16_throughput_status_code',
+ result: [],
+};
+
export const mockedQueryResultPayload = {
metricId: '12_system_metrics_kubernetes_container_memory_total',
- result: [
- {
- metric: {},
- values: [
- [1563272065.589, '10.396484375'],
- [1563272125.589, '10.333984375'],
- [1563272185.589, '10.333984375'],
- [1563272245.589, '10.333984375'],
- [1563272305.589, '10.333984375'],
- [1563272365.589, '10.333984375'],
- [1563272425.589, '10.38671875'],
- [1563272485.589, '10.333984375'],
- [1563272545.589, '10.333984375'],
- [1563272605.589, '10.333984375'],
- [1563272665.589, '10.333984375'],
- [1563272725.589, '10.333984375'],
- [1563272785.589, '10.396484375'],
- [1563272845.589, '10.333984375'],
- [1563272905.589, '10.333984375'],
- [1563272965.589, '10.3984375'],
- [1563273025.589, '10.337890625'],
- [1563273085.589, '10.34765625'],
- [1563273145.589, '10.337890625'],
- [1563273205.589, '10.337890625'],
- [1563273265.589, '10.337890625'],
- [1563273325.589, '10.337890625'],
- [1563273385.589, '10.337890625'],
- [1563273445.589, '10.337890625'],
- [1563273505.589, '10.337890625'],
- [1563273565.589, '10.337890625'],
- [1563273625.589, '10.337890625'],
- [1563273685.589, '10.337890625'],
- [1563273745.589, '10.337890625'],
- [1563273805.589, '10.337890625'],
- [1563273865.589, '10.390625'],
- [1563273925.589, '10.390625'],
- ],
- },
- ],
+ result: metricsResult,
};
export const mockedQueryResultPayloadCoresTotal = {
metricId: '13_system_metrics_kubernetes_container_cores_total',
- result: [
- {
- metric: {},
- values: [
- [1563272065.589, '9.396484375'],
- [1563272125.589, '9.333984375'],
- [1563272185.589, '9.333984375'],
- [1563272245.589, '9.333984375'],
- [1563272305.589, '9.333984375'],
- [1563272365.589, '9.333984375'],
- [1563272425.589, '9.38671875'],
- [1563272485.589, '9.333984375'],
- [1563272545.589, '9.333984375'],
- [1563272605.589, '9.333984375'],
- [1563272665.589, '9.333984375'],
- [1563272725.589, '9.333984375'],
- [1563272785.589, '9.396484375'],
- [1563272845.589, '9.333984375'],
- [1563272905.589, '9.333984375'],
- [1563272965.589, '9.3984375'],
- [1563273025.589, '9.337890625'],
- [1563273085.589, '9.34765625'],
- [1563273145.589, '9.337890625'],
- [1563273205.589, '9.337890625'],
- [1563273265.589, '9.337890625'],
- [1563273325.589, '9.337890625'],
- [1563273385.589, '9.337890625'],
- [1563273445.589, '9.337890625'],
- [1563273505.589, '9.337890625'],
- [1563273565.589, '9.337890625'],
- [1563273625.589, '9.337890625'],
- [1563273685.589, '9.337890625'],
- [1563273745.589, '9.337890625'],
- [1563273805.589, '9.337890625'],
- [1563273865.589, '9.390625'],
- [1563273925.589, '9.390625'],
- ],
- },
- ],
+ result: metricsResult,
+};
+
+export const mockedQueryResultFixture = {
+ // First metric in fixture `metrics_dashboard/environment_metrics_dashboard.json`
+ metricId: 'undefined_response_metrics_nginx_ingress_throughput_status_code',
+ result: metricsResult,
+};
+
+export const mockedQueryResultFixtureStatusCode = {
+ metricId: 'undefined_response_metrics_nginx_ingress_latency_pod_average',
+ result: metricsResult,
};
const extraEnvironmentData = new Array(15).fill(null).map((_, idx) => ({
diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js
index 64601e892ad..777181df10e 100644
--- a/spec/frontend/monitoring/store/getters_spec.js
+++ b/spec/frontend/monitoring/store/getters_spec.js
@@ -4,11 +4,16 @@ import * as types from '~/monitoring/stores/mutation_types';
import { metricStates } from '~/monitoring/constants';
import {
environmentData,
- metricsDashboardPayload,
- mockedEmptyResult,
- mockedQueryResultPayload,
- mockedQueryResultPayloadCoresTotal,
+ mockedEmptyThroughputResult,
+ mockedQueryResultFixture,
+ mockedQueryResultFixtureStatusCode,
} from '../mock_data';
+import { getJSONFixture } from '../../helpers/fixtures';
+
+const metricsDashboardFixture = getJSONFixture(
+ 'metrics_dashboard/environment_metrics_dashboard.json',
+);
+const metricsDashboardPayload = metricsDashboardFixture.dashboard;
describe('Monitoring store Getters', () => {
describe('getMetricStates', () => {
@@ -55,14 +60,14 @@ describe('Monitoring store Getters', () => {
it('on an empty metric with no result, returns NO_DATA', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyThroughputResult);
expect(getMetricStates()).toEqual([metricStates.NO_DATA]);
});
it('on a metric with a result, returns OK', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixture);
expect(getMetricStates()).toEqual([metricStates.OK]);
});
@@ -78,8 +83,8 @@ describe('Monitoring store Getters', () => {
it('on multiple metrics with results, returns OK', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixture);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixtureStatusCode);
expect(getMetricStates()).toEqual([metricStates.OK]);
@@ -110,7 +115,7 @@ describe('Monitoring store Getters', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
// An success in 1 group
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixture);
// An error in 2 groups
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[0].panels[1].metrics[0].metricId,
@@ -176,38 +181,38 @@ describe('Monitoring store Getters', () => {
it('an empty metric, returns empty', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyThroughputResult);
expect(metricsWithData()).toEqual([]);
});
it('a metric with results, it returns a metric', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixture);
- expect(metricsWithData()).toEqual([mockedQueryResultPayload.metricId]);
+ expect(metricsWithData()).toEqual([mockedQueryResultFixture.metricId]);
});
it('multiple metrics with results, it return multiple metrics', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixture);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixtureStatusCode);
expect(metricsWithData()).toEqual([
- mockedQueryResultPayload.metricId,
- mockedQueryResultPayloadCoresTotal.metricId,
+ mockedQueryResultFixture.metricId,
+ mockedQueryResultFixtureStatusCode.metricId,
]);
});
it('multiple metrics with results, it returns metrics filtered by group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixture);
+ mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultFixtureStatusCode);
// First group has metrics
expect(metricsWithData(state.dashboard.panelGroups[0].key)).toEqual([
- mockedQueryResultPayload.metricId,
- mockedQueryResultPayloadCoresTotal.metricId,
+ mockedQueryResultFixture.metricId,
+ mockedQueryResultFixtureStatusCode.metricId,
]);
// Second group has no metrics
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 76efc68788d..a94de5494e5 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -5,7 +5,13 @@ import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state';
import { metricStates } from '~/monitoring/constants';
-import { metricsDashboardPayload, deploymentData, dashboardGitResponse } from '../mock_data';
+import { deploymentData, dashboardGitResponse } from '../mock_data';
+import { getJSONFixture } from '../../helpers/fixtures';
+
+const metricsDashboardFixture = getJSONFixture(
+ 'metrics_dashboard/environment_metrics_dashboard.json',
+);
+const metricsDashboardPayload = metricsDashboardFixture.dashboard;
describe('Monitoring mutations', () => {
let stateCopy;
@@ -26,32 +32,31 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
const groups = getGroups();
- expect(groups[0].key).toBe('system-metrics-kubernetes-0');
- expect(groups[1].key).toBe('response-metrics-nginx-ingress-vts-1');
+ expect(groups[0].key).toBe('response-metrics-nginx-ingress-vts-0');
+ expect(groups[1].key).toBe('response-metrics-nginx-ingress-1');
});
it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
- const expectedLabel = 'Pod average';
+ const expectedLabel = '5xx Errors (%)';
const { label, queryRange } = getGroups()[0].panels[2].metrics[0];
expect(label).toEqual(expectedLabel);
expect(queryRange.length).toBeGreaterThan(0);
});
- it('contains two groups, with panels with a metric each', () => {
+ it('contains six groups, with panels with a metric each', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
const groups = getGroups();
expect(groups).toBeDefined();
- expect(groups).toHaveLength(2);
+ expect(groups).toHaveLength(6);
- expect(groups[0].panels).toHaveLength(4);
+ expect(groups[0].panels).toHaveLength(3);
expect(groups[0].panels[0].metrics).toHaveLength(1);
expect(groups[0].panels[1].metrics).toHaveLength(1);
expect(groups[0].panels[2].metrics).toHaveLength(1);
- expect(groups[0].panels[3].metrics).toHaveLength(5);
- expect(groups[1].panels).toHaveLength(1);
+ expect(groups[1].panels).toHaveLength(3);
expect(groups[1].panels[0].metrics).toHaveLength(1);
});
it('assigns metrics a metric id', () => {
@@ -60,10 +65,10 @@ describe('Monitoring mutations', () => {
const groups = getGroups();
expect(groups[0].panels[0].metrics[0].metricId).toEqual(
- '12_system_metrics_kubernetes_container_memory_total',
+ 'undefined_response_metrics_nginx_ingress_throughput_status_code',
);
expect(groups[1].panels[0].metrics[0].metricId).toEqual(
- '1_response_metrics_nginx_ingress_throughput_status_code',
+ 'undefined_response_metrics_nginx_ingress_16_throughput_status_code',
);
});
});
@@ -123,7 +128,7 @@ describe('Monitoring mutations', () => {
});
describe('Individual panel/metric results', () => {
- const metricId = '12_system_metrics_kubernetes_container_memory_total';
+ const metricId = 'undefined_response_metrics_nginx_ingress_throughput_status_code';
const result = [
{
values: [[0, 1], [1, 1], [1, 3]],
diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js
index b2475488d97..1d17c8b0777 100644
--- a/spec/frontend/wikis_spec.js
+++ b/spec/frontend/wikis_spec.js
@@ -8,11 +8,21 @@ describe('Wikis', () => {
}">
<input type="text" id="wiki_title" value="My title" />
<input type="text" id="wiki_message" />
- </form>`;
+ <select class="form-control select-control" name="wiki[format]" id="wiki_format">
+ <option value="markdown">Markdown</option>
+ <option selected="selected" value="rdoc">RDoc</option>
+ <option value="asciidoc">AsciiDoc</option>
+ <option value="org">Org</option>
+ </select>
+ <code class="js-markup-link-example">{Link title}[link:page-slug]</code>
+ </form>
+ `;
let wikis;
let titleInput;
let messageInput;
+ let changeFormatSelect;
+ let linkExample;
describe('when the wiki page is being created', () => {
const formHtmlFixture = editFormHtmlFixture({ newPage: true });
@@ -22,6 +32,8 @@ describe('Wikis', () => {
titleInput = document.getElementById('wiki_title');
messageInput = document.getElementById('wiki_message');
+ changeFormatSelect = document.querySelector('#wiki_format');
+ linkExample = document.querySelector('.js-markup-link-example');
wikis = new Wikis();
});
@@ -69,6 +81,19 @@ describe('Wikis', () => {
expect(messageInput.value).toEqual('Update My title');
});
+
+ it.each`
+ value | text
+ ${'markdown'} | ${'[Link Title](page-slug)'}
+ ${'rdoc'} | ${'{Link title}[link:page-slug]'}
+ ${'asciidoc'} | ${'link:page-slug[Link title]'}
+ ${'org'} | ${'[[page-slug]]'}
+ `('updates a message when value=$value is selected', ({ value, text }) => {
+ changeFormatSelect.value = value;
+ changeFormatSelect.dispatchEvent(new Event('change'));
+
+ expect(linkExample.innerHTML).toBe(text);
+ });
});
});
});
diff --git a/spec/lib/gitlab/database/connection_timer_spec.rb b/spec/lib/gitlab/database/connection_timer_spec.rb
new file mode 100644
index 00000000000..c9e9d770343
--- /dev/null
+++ b/spec/lib/gitlab/database/connection_timer_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::ConnectionTimer do
+ let(:current_clock_value) { 1234.56 }
+
+ before do
+ allow(described_class).to receive(:current_clock_value).and_return(current_clock_value)
+ end
+
+ describe '.starting_now' do
+ let(:default_interval) { described_class::DEFAULT_INTERVAL }
+ let(:random_value) { 120 }
+
+ before do
+ allow(described_class).to receive(:rand).and_return(random_value)
+ end
+
+ context 'when the configured interval is positive' do
+ before do
+ allow(described_class).to receive(:interval).and_return(default_interval)
+ end
+
+ it 'randomizes the interval of the created timer' do
+ timer = described_class.starting_now
+
+ expect(timer.interval).to eq(default_interval + random_value)
+ end
+ end
+
+ context 'when the configured interval is not positive' do
+ before do
+ allow(described_class).to receive(:interval).and_return(0)
+ end
+
+ it 'sets the interval of the created timer to nil' do
+ timer = described_class.starting_now
+
+ expect(timer.interval).to be_nil
+ end
+ end
+ end
+
+ describe '.expired?' do
+ context 'when the interval is positive' do
+ context 'when the interval has elapsed' do
+ it 'returns true' do
+ timer = described_class.new(20, current_clock_value - 30)
+
+ expect(timer).to be_expired
+ end
+ end
+
+ context 'when the interval has not elapsed' do
+ it 'returns false' do
+ timer = described_class.new(20, current_clock_value - 10)
+
+ expect(timer).not_to be_expired
+ end
+ end
+ end
+
+ context 'when the interval is not positive' do
+ context 'when the interval has elapsed' do
+ it 'returns false' do
+ timer = described_class.new(0, current_clock_value - 30)
+
+ expect(timer).not_to be_expired
+ end
+ end
+
+ context 'when the interval has not elapsed' do
+ it 'returns false' do
+ timer = described_class.new(0, current_clock_value + 10)
+
+ expect(timer).not_to be_expired
+ end
+ end
+ end
+
+ context 'when the interval is nil' do
+ it 'returns false' do
+ timer = described_class.new(nil, current_clock_value - 30)
+
+ expect(timer).not_to be_expired
+ end
+ end
+ end
+
+ describe '.reset!' do
+ it 'updates the timer clock value' do
+ timer = described_class.new(20, current_clock_value - 20)
+ expect(timer.starting_clock_value).not_to eql(current_clock_value)
+
+ timer.reset!
+ expect(timer.starting_clock_value).to eql(current_clock_value)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
new file mode 100644
index 00000000000..0523066b593
--- /dev/null
+++ b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin do
+ describe 'checking in a connection to the pool' do
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ self.abstract_class = true
+
+ def self.name
+ 'ForceDisconnectTestModel'
+ end
+ end
+ end
+ let(:config) { Rails.application.config_for(:database).merge(pool: 1) }
+ let(:pool) { model.establish_connection(config) }
+
+ it 'calls the force disconnect callback on checkin' do
+ connection = pool.connection
+
+ expect(pool.active_connection?).to be_truthy
+ expect(connection).to receive(:force_disconnect_if_old!).and_call_original
+
+ model.clear_active_connections!
+ end
+ end
+
+ describe 'disconnecting from the database' do
+ let(:connection) { ActiveRecord::Base.connection_pool.connection }
+ let(:timer) { connection.force_disconnect_timer }
+
+ context 'when the timer is expired' do
+ it 'disconnects from the database' do
+ allow(timer).to receive(:expired?).and_return(true)
+
+ expect(connection).to receive(:disconnect!).and_call_original
+ expect(timer).to receive(:reset!).and_call_original
+
+ connection.force_disconnect_if_old!
+ end
+ end
+
+ context 'when the timer is not expired' do
+ it 'does not disconnect from the database' do
+ allow(timer).to receive(:expired?).and_return(false)
+
+ expect(connection).not_to receive(:disconnect!)
+ expect(timer).not_to receive(:reset!)
+
+ connection.force_disconnect_if_old!
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab_danger_spec.rb b/spec/lib/gitlab_danger_spec.rb
index d3d7357a012..f4620e54979 100644
--- a/spec/lib/gitlab_danger_spec.rb
+++ b/spec/lib/gitlab_danger_spec.rb
@@ -9,7 +9,7 @@ describe GitlabDanger do
describe '.local_warning_message' do
it 'returns an informational message with rules that can run' do
- expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, documentation, frozen_string, duplicate_yarn_dependencies, prettier, eslint, karma, database, commit_messages')
+ expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, documentation, frozen_string, duplicate_yarn_dependencies, prettier, eslint, karma, database, commit_messages, telemetry')
end
end
diff --git a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
new file mode 100644
index 00000000000..80645b1f162
--- /dev/null
+++ b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200304160801_delete_template_services_duplicated_by_type.rb')
+
+describe DeleteTemplateServicesDuplicatedByType, :migration do
+ let(:services) { table(:services) }
+
+ before do
+ services.create!(template: true, type: 'JenkinsService')
+ services.create!(template: true, type: 'JenkinsService')
+ services.create!(template: true, type: 'JiraService')
+ services.create!(template: true, type: 'JenkinsService')
+ end
+
+ it 'deletes service templates duplicated by type except the one with the lowest ID' do
+ jenkins_service_id = services.where(type: 'JenkinsService').order(:id).pluck(:id).first
+ jira_service_id = services.where(type: 'JiraService').pluck(:id).first
+
+ migrate!
+
+ expect(services.pluck(:id)).to contain_exactly(jenkins_service_id, jira_service_id)
+ end
+end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index eaf19bd4fea..7e0c491bdfa 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -17,6 +17,16 @@ describe Service do
expect(build(:service, project_id: nil, template: true)).to be_valid
expect(build(:service, project_id: nil, template: false)).to be_invalid
end
+
+ context 'with an existing service template' do
+ before do
+ create(:service, type: 'Service', template: true)
+ end
+
+ it 'validates only one service template per type' do
+ expect(build(:service, type: 'Service', template: true)).to be_invalid
+ end
+ end
end
describe 'Scopes' do
diff --git a/spec/services/ci/find_exposed_artifacts_service_spec.rb b/spec/services/ci/find_exposed_artifacts_service_spec.rb
index b0f190b0e7a..16e23253c34 100644
--- a/spec/services/ci/find_exposed_artifacts_service_spec.rb
+++ b/spec/services/ci/find_exposed_artifacts_service_spec.rb
@@ -172,5 +172,47 @@ describe Ci::FindExposedArtifactsService do
])
end
end
+
+ context 'cross-project MR' do
+ let!(:foreign_project) { create(:project) }
+ let!(:pipeline) { create(:ci_pipeline, project: foreign_project) }
+
+ let!(:job_show) do
+ create_job_with_artifacts({
+ artifacts: {
+ expose_as: 'file artifact',
+ paths: ['ci_artifacts.txt']
+ }
+ })
+ end
+
+ let!(:job_browse) do
+ create_job_with_artifacts({
+ artifacts: {
+ expose_as: 'directory artifact',
+ paths: ['tests_encoding/']
+ }
+ })
+ end
+
+ subject { described_class.new(project, user).for_pipeline(pipeline, limit: 2) }
+
+ it 'returns the correct path for cross-project MRs' do
+ expect(subject).to eq([
+ {
+ text: 'file artifact',
+ url: file_project_job_artifacts_path(foreign_project, job_show, 'ci_artifacts.txt'),
+ job_name: job_show.name,
+ job_path: project_job_path(foreign_project, job_show)
+ },
+ {
+ text: 'directory artifact',
+ url: browse_project_job_artifacts_path(foreign_project, job_browse),
+ job_name: job_browse.name,
+ job_path: project_job_path(foreign_project, job_browse)
+ }
+ ])
+ end
+ end
end
end
diff --git a/spec/services/deployments/after_create_service_spec.rb b/spec/services/deployments/after_create_service_spec.rb
index 605700c73b2..3aa137a866e 100644
--- a/spec/services/deployments/after_create_service_spec.rb
+++ b/spec/services/deployments/after_create_service_spec.rb
@@ -49,7 +49,7 @@ describe Deployments::AfterCreateService do
it 'creates ref' do
expect_any_instance_of(Repository)
.to receive(:create_ref)
- .with(deployment.sha, deployment.send(:ref_path))
+ .with(deployment.sha, "refs/environments/production/deployments/#{deployment.iid}")
service.execute
end