diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-05 15:07:52 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-05 15:07:52 +0000 |
commit | afe2b984524ae4b0c8a0636db7ec5b2c452f0734 (patch) | |
tree | 3de39f954c7239e09a9afe84263a64e7042b2b60 | |
parent | 5a6b36b60502c50ab59c0bc3c345793b70a3d548 (diff) | |
download | gitlab-ce-afe2b984524ae4b0c8a0636db7ec5b2c452f0734.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 |