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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml1
-rw-r--r--.rubocop.yml1
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter_note.vue6
-rw-r--r--app/assets/javascripts/notes/components/note_attachment.vue16
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue7
-rw-r--r--app/assets/javascripts/releases/list/components/release_block.vue12
-rw-r--r--app/graphql/types/blob_viewers/type_enum.rb14
-rw-r--r--app/graphql/types/snippet_type.rb8
-rw-r--r--app/graphql/types/snippets/blob_type.rb46
-rw-r--r--app/graphql/types/snippets/blob_viewer_type.rb41
-rw-r--r--app/models/epic.rb2
-rw-r--r--app/presenters/blob_presenter.rb6
-rw-r--r--app/presenters/snippet_blob_presenter.rb31
-rw-r--r--app/services/submit_usage_ping_service.rb6
-rw-r--r--app/workers/all_queues.yml129
-rw-r--r--changelogs/unreleased/fj-add-graphql-blob-viewer-to-snippets.yml5
-rw-r--r--changelogs/unreleased/fj-remove-storage-version-column-from-snippets.yml5
-rw-r--r--changelogs/unreleased/remove_milestone_id_from_epics3.yml5
-rw-r--r--changelogs/unreleased/sh-fix-mermaid-releases-page.yml5
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--db/post_migrate/20200120083607_remove_storage_version_column_from_snippets.rb30
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/operations/extra_sidekiq_processes.md5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql98
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json313
-rw-r--r--doc/api/graphql/reference/index.md31
-rw-r--r--doc/development/fe_guide/graphql.md2
-rw-r--r--doc/development/sidekiq_style_guide.md7
-rw-r--r--doc/user/application_security/sast/index.md3
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/sidekiq_config.rb56
-rw-r--r--lib/gitlab/sidekiq_config/worker.rb51
-rw-r--r--lib/tasks/gitlab/sidekiq.rake48
-rw-r--r--lib/tasks/lint.rake1
-rw-r--r--package.json2
-rw-r--r--spec/factories/project_error_tracking_settings.rb2
-rw-r--r--spec/features/error_tracking/user_sees_error_details_spec.rb32
-rw-r--r--spec/features/error_tracking/user_sees_error_index_spec.rb69
-rw-r--r--spec/fixtures/sentry/issue_sample_response.json282
-rw-r--r--spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap3
-rw-r--r--spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js2
-rw-r--r--spec/frontend/notes/components/discussion_filter_note_spec.js89
-rw-r--r--spec/frontend/notes/components/note_attachment_spec.js58
-rw-r--r--spec/frontend/notes/components/note_header_spec.js210
-rw-r--r--spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap3
-rw-r--r--spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap6
-rw-r--r--spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap3
-rw-r--r--spec/frontend/releases/list/components/release_block_spec.js7
-rw-r--r--spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap2
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap88
-rw-r--r--spec/frontend/vue_shared/components/expand_button_spec.js4
-rw-r--r--spec/graphql/types/blob_viewers/type_enum_spec.rb11
-rw-r--r--spec/graphql/types/snippet_type_spec.rb4
-rw-r--r--spec/graphql/types/snippets/blob_type_spec.rb13
-rw-r--r--spec/graphql/types/snippets/blob_viewer_type_spec.rb12
-rw-r--r--spec/lib/gitlab/sidekiq_config/worker_spec.rb84
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb42
-rw-r--r--spec/lib/sentry/client/issue_spec.rb4
-rw-r--r--spec/presenters/snippet_blob_presenter_spec.rb60
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb4
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb122
-rw-r--r--spec/support/matchers/log_spam.rb2
-rw-r--r--spec/support/shared_contexts/features/error_tracking_shared_context.rb23
-rw-r--r--spec/support/shared_examples/features/error_tracking_shared_example.rb86
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb14
-rw-r--r--yarn.lock119
68 files changed, 1816 insertions, 646 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 4c407045411..121ce76d1dd 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -13,6 +13,7 @@
.default-before_script:
before_script:
- date
+ - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/'
- export GOPATH=$CI_PROJECT_DIR/.go
- mkdir -p $GOPATH
- source scripts/utils.sh
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 5a58c3f9416..effc950f3c8 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -40,6 +40,7 @@
paths:
- vendor/ruby
before_script:
+ - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/'
- cd qa/
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet
- bundle check
diff --git a/.rubocop.yml b/.rubocop.yml
index b374012cac5..b6a5c686f84 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -347,6 +347,7 @@ RSpec/HaveGitlabHttpStatus:
Enabled: true
Include:
- 'spec/support/shared_examples/**/*'
+ - 'ee/spec/support/shared_examples/**/*'
Style/MultilineWhenThen:
Enabled: false
diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue
index 889731df180..8dc4b43d69a 100644
--- a/app/assets/javascripts/notes/components/discussion_filter_note.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue
@@ -38,12 +38,12 @@ export default {
<icon name="comment" />
</div>
<div class="timeline-content">
- <div v-html="timelineContent"></div>
+ <div ref="timelineContent" v-html="timelineContent"></div>
<div class="discussion-filter-actions mt-2">
- <gl-button variant="default" @click="selectFilter(0)">
+ <gl-button ref="showAllActivity" variant="default" @click="selectFilter(0)">
{{ __('Show all activity') }}
</gl-button>
- <gl-button variant="default" @click="selectFilter(1)">
+ <gl-button ref="showComments" variant="default" @click="selectFilter(1)">
{{ __('Show comments only') }}
</gl-button>
</div>
diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue
index b6d8c831e2e..72f9a4c7e74 100644
--- a/app/assets/javascripts/notes/components/note_attachment.vue
+++ b/app/assets/javascripts/notes/components/note_attachment.vue
@@ -12,11 +12,23 @@ export default {
<template>
<div class="note-attachment">
- <a v-if="attachment.image" :href="attachment.url" target="_blank" rel="noopener noreferrer">
+ <a
+ v-if="attachment.image"
+ ref="attachmentImage"
+ :href="attachment.url"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
<img :src="attachment.url" class="note-image-attach" />
</a>
<div class="attachment">
- <a v-if="attachment.url" :href="attachment.url" target="_blank" rel="noopener noreferrer">
+ <a
+ v-if="attachment.url"
+ ref="attachmentUrl"
+ :href="attachment.url"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
<i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }}
</a>
</div>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index e4f09492d9c..16351baedb7 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -63,13 +63,13 @@ export default {
<template>
<div class="note-header-info">
- <div v-if="includeToggle" class="discussion-actions">
+ <div v-if="includeToggle" ref="discussionActions" class="discussion-actions">
<button
class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button"
@click="handleToggle"
>
- <i :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
+ <i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle thread') }}
</button>
</div>
@@ -90,10 +90,11 @@ export default {
<span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
- <span class="system-note-separator">
+ <span ref="actionText" class="system-note-separator">
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
+ ref="noteTimestamp"
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
@click="updateTargetNoteHash"
diff --git a/app/assets/javascripts/releases/list/components/release_block.vue b/app/assets/javascripts/releases/list/components/release_block.vue
index d924b5795f0..e6bb5325120 100644
--- a/app/assets/javascripts/releases/list/components/release_block.vue
+++ b/app/assets/javascripts/releases/list/components/release_block.vue
@@ -1,9 +1,11 @@
<script>
import _ from 'underscore';
+import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility';
import { scrollToElement } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import '~/behaviors/markdown/render_gfm';
import EvidenceBlock from './evidence_block.vue';
import ReleaseBlockAssets from './release_block_assets.vue';
import ReleaseBlockFooter from './release_block_footer.vue';
@@ -65,7 +67,10 @@ export default {
return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones));
},
},
+
mounted() {
+ this.renderGFM();
+
const hash = getLocationHash();
if (hash && slugify(hash) === this.id) {
this.isHighlighted = true;
@@ -76,6 +81,11 @@ export default {
scrollToElement(this.$el);
}
},
+ methods: {
+ renderGFM() {
+ $(this.$refs['gfm-content']).renderGFM();
+ },
+ },
};
</script>
<template>
@@ -91,7 +101,7 @@ export default {
<release-block-assets v-if="shouldRenderAssets" :assets="assets" />
<evidence-block v-if="hasEvidence && shouldShowEvidence" :release="release" />
- <div class="card-text prepend-top-default">
+ <div ref="gfm-content" class="card-text prepend-top-default">
<div v-html="release.description_html"></div>
</div>
</div>
diff --git a/app/graphql/types/blob_viewers/type_enum.rb b/app/graphql/types/blob_viewers/type_enum.rb
new file mode 100644
index 00000000000..35e659197e5
--- /dev/null
+++ b/app/graphql/types/blob_viewers/type_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module BlobViewers
+ class TypeEnum < BaseEnum
+ graphql_name 'BlobViewersType'
+ description 'Types of blob viewers'
+
+ value 'rich', value: :rich
+ value 'simple', value: :simple
+ value 'auxiliary', value: :auxiliary
+ end
+ end
+end
diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb
index 3f780528945..c4d65174990 100644
--- a/app/graphql/types/snippet_type.rb
+++ b/app/graphql/types/snippet_type.rb
@@ -36,10 +36,6 @@ module Types
description: 'File Name of the snippet',
null: true
- field :content, GraphQL::STRING_TYPE,
- description: 'Content of the snippet',
- null: false
-
field :description, GraphQL::STRING_TYPE,
description: 'Description of the snippet',
null: true
@@ -64,6 +60,10 @@ module Types
description: 'Raw URL of the snippet',
null: false
+ field :blob, type: Types::Snippets::BlobType,
+ description: 'Snippet blob',
+ null: false
+
markdown_field :description_html, null: true, method: :description
end
end
diff --git a/app/graphql/types/snippets/blob_type.rb b/app/graphql/types/snippets/blob_type.rb
new file mode 100644
index 00000000000..f398fe9c121
--- /dev/null
+++ b/app/graphql/types/snippets/blob_type.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Types
+ module Snippets
+ # rubocop: disable Graphql/AuthorizeTypes
+ class BlobType < BaseObject
+ graphql_name 'SnippetBlob'
+ description 'Represents the snippet blob'
+ present_using SnippetBlobPresenter
+
+ field :highlighted_data, GraphQL::STRING_TYPE,
+ description: 'Blob highlighted data',
+ null: true
+
+ field :raw_path, GraphQL::STRING_TYPE,
+ description: 'Blob raw content endpoint path',
+ null: false
+
+ field :size, GraphQL::INT_TYPE,
+ description: 'Blob size',
+ null: false
+
+ field :binary, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob is binary',
+ method: :binary?,
+ null: false
+
+ field :name, GraphQL::STRING_TYPE,
+ description: 'Blob name',
+ null: true
+
+ field :path, GraphQL::STRING_TYPE,
+ description: 'Blob path',
+ null: true
+
+ field :simple_viewer, type: Types::Snippets::BlobViewerType,
+ description: 'Blob content simple viewer',
+ null: false
+
+ field :rich_viewer, type: Types::Snippets::BlobViewerType,
+ description: 'Blob content rich viewer',
+ null: true
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/snippets/blob_viewer_type.rb b/app/graphql/types/snippets/blob_viewer_type.rb
new file mode 100644
index 00000000000..3e653576d07
--- /dev/null
+++ b/app/graphql/types/snippets/blob_viewer_type.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Types
+ module Snippets
+ class BlobViewerType < BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'SnippetBlobViewer'
+ description 'Represents how the blob content should be displayed'
+
+ field :type, Types::BlobViewers::TypeEnum,
+ description: 'Type of blob viewer',
+ null: false
+
+ field :load_async, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob content is loaded async',
+ null: false
+
+ field :collapsed, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob should be displayed collapsed',
+ method: :collapsed?,
+ null: false
+
+ field :too_large, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob too large to be displayed',
+ method: :too_large?,
+ null: false
+
+ field :render_error, GraphQL::STRING_TYPE,
+ description: 'Error rendering the blob content',
+ null: true
+
+ field :file_type, GraphQL::STRING_TYPE,
+ description: 'Content file type',
+ method: :partial_name,
+ null: false
+
+ field :loading_partial_name, GraphQL::STRING_TYPE,
+ description: 'Loading partial name',
+ null: false
+ end
+ end
+end
diff --git a/app/models/epic.rb b/app/models/epic.rb
index 1203c6c1fc3..ea4a231931d 100644
--- a/app/models/epic.rb
+++ b/app/models/epic.rb
@@ -5,8 +5,6 @@
class Epic < ApplicationRecord
include IgnorableColumns
- ignore_column :milestone_id, remove_after: '2020-02-01', remove_with: '12.8'
-
def self.link_reference_pattern
nil
end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 3a71d2b87f3..e0077db8d5c 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -9,7 +9,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Highlight.highlight(
blob.path,
limited_blob_data(to: to),
- language: blob.language_from_gitattributes,
+ language: language,
plain: plain
)
end
@@ -37,4 +37,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def all_lines
@all_lines ||= blob.data.lines
end
+
+ def language
+ blob.language_from_gitattributes
+ end
end
diff --git a/app/presenters/snippet_blob_presenter.rb b/app/presenters/snippet_blob_presenter.rb
new file mode 100644
index 00000000000..9baaacdbb24
--- /dev/null
+++ b/app/presenters/snippet_blob_presenter.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class SnippetBlobPresenter < BlobPresenter
+ def highlighted_data
+ return if blob.binary?
+
+ if blob.rich_viewer&.partial_name == 'markup'
+ blob.rendered_markup
+ else
+ highlight
+ end
+ end
+
+ def raw_path
+ if snippet.is_a?(ProjectSnippet)
+ raw_project_snippet_path(snippet.project, snippet)
+ else
+ raw_snippet_path(snippet)
+ end
+ end
+
+ private
+
+ def snippet
+ blob.snippet
+ end
+
+ def language
+ nil
+ end
+end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 7927ab265c5..3265eb106eb 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -36,10 +36,12 @@ class SubmitUsagePingService
private
def store_metrics(response)
- return unless response['conv_index'].present?
+ metrics = response['conv_index'] || response['dev_ops_score']
+
+ return unless metrics.present?
DevOpsScore::Metric.create!(
- response['conv_index'].slice(*METRICS)
+ metrics.slice(*METRICS)
)
end
end
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index f19dd0e4a48..87feecf4bbb 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1,93 +1,93 @@
+# This file is generated automatically by
+# bin/rake gitlab:sidekiq:all_queues_yml:generate
+#
+# Do not edit it manually!
---
- auto_devops:auto_devops_disable
-
- auto_merge:auto_merge_process
-
- chaos:chaos_cpu_spin
- chaos:chaos_db_spin
- chaos:chaos_kill
- chaos:chaos_leak_mem
- chaos:chaos_sleep
-
+- container_repository:cleanup_container_repository
+- container_repository:delete_container_repository
- cronjob:admin_email
+- cronjob:ci_archive_traces_cron
- cronjob:container_expiration_policy
- cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping
- cronjob:import_export_project_cleanup
-- cronjob:pages_domain_verification_cron
+- cronjob:issue_due_scheduler
+- cronjob:namespaces_prune_aggregation_schedules
- cronjob:pages_domain_removal_cron
- cronjob:pages_domain_ssl_renewal_cron
+- cronjob:pages_domain_verification_cron
- cronjob:personal_access_tokens_expiring
- cronjob:pipeline_schedule
- cronjob:prune_old_events
+- cronjob:prune_web_hook_logs
- cronjob:remove_expired_group_links
- cronjob:remove_expired_members
- cronjob:remove_unreferenced_lfs_objects
- cronjob:repository_archive_cache
- cronjob:repository_check_dispatch
- cronjob:requests_profiles
+- cronjob:schedule_migrate_external_diffs
- cronjob:stuck_ci_jobs
- cronjob:stuck_import_jobs
- cronjob:stuck_merge_jobs
-- cronjob:ci_archive_traces_cron
- cronjob:trending_projects
-- cronjob:issue_due_scheduler
-- cronjob:prune_web_hook_logs
-- cronjob:schedule_migrate_external_diffs
-- cronjob:namespaces_prune_aggregation_schedules
-
+- deployment:deployments_finished
+- deployment:deployments_success
+- gcp_cluster:cluster_configure
- gcp_cluster:cluster_install_app
- gcp_cluster:cluster_patch_app
-- gcp_cluster:cluster_upgrade_app
+- gcp_cluster:cluster_project_configure
- gcp_cluster:cluster_provision
-- gcp_cluster:clusters_cleanup_app
-- gcp_cluster:clusters_cleanup_project_namespace
-- gcp_cluster:clusters_cleanup_service_account
+- gcp_cluster:cluster_upgrade_app
- gcp_cluster:cluster_wait_for_app_installation
-- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address
-- gcp_cluster:cluster_configure
-- gcp_cluster:cluster_project_configure
-- gcp_cluster:clusters_applications_wait_for_uninstall_app
+- gcp_cluster:clusters_applications_activate_service
+- gcp_cluster:clusters_applications_deactivate_service
- gcp_cluster:clusters_applications_uninstall
+- gcp_cluster:clusters_applications_wait_for_uninstall_app
- gcp_cluster:clusters_cleanup_app
- gcp_cluster:clusters_cleanup_project_namespace
- gcp_cluster:clusters_cleanup_service_account
-- gcp_cluster:clusters_applications_activate_service
-- gcp_cluster:clusters_applications_deactivate_service
-
-- github_import_advance_stage
+- gcp_cluster:wait_for_cluster_creation
- github_importer:github_import_import_diff_note
- github_importer:github_import_import_issue
-- github_importer:github_import_import_note
- github_importer:github_import_import_lfs_object
+- github_importer:github_import_import_note
- github_importer:github_import_import_pull_request
- github_importer:github_import_refresh_import_jid
- github_importer:github_import_stage_finish_import
- github_importer:github_import_stage_import_base_data
- github_importer:github_import_stage_import_issues_and_diff_notes
-- github_importer:github_import_stage_import_notes
- github_importer:github_import_stage_import_lfs_objects
+- github_importer:github_import_stage_import_notes
- github_importer:github_import_stage_import_pull_requests
- github_importer:github_import_stage_import_repository
-
- hashed_storage:hashed_storage_migrator
-- hashed_storage:hashed_storage_rollbacker
- hashed_storage:hashed_storage_project_migrate
- hashed_storage:hashed_storage_project_rollback
-
+- hashed_storage:hashed_storage_rollbacker
- mail_scheduler:mail_scheduler_issue_due
- mail_scheduler:mail_scheduler_notification_service
-
+- notifications:new_release
+- object_pool:object_pool_create
+- object_pool:object_pool_destroy
+- object_pool:object_pool_join
+- object_pool:object_pool_schedule_join
- object_storage:object_storage_background_move
- object_storage:object_storage_migrate_uploads
-
+- pipeline_background:archive_trace
+- pipeline_background:ci_build_trace_chunk_flush
- pipeline_cache:expire_job_cache
- pipeline_cache:expire_pipeline_cache
- pipeline_creation:create_pipeline
- pipeline_creation:run_pipeline_schedule
-- pipeline_background:archive_trace
-- pipeline_background:ci_build_trace_chunk_flush
- pipeline_default:build_coverage
- pipeline_default:build_trace_sections
- pipeline_default:pipeline_metrics
@@ -95,74 +95,67 @@
- pipeline_hooks:build_hooks
- pipeline_hooks:pipeline_hooks
- pipeline_processing:build_finished
-- pipeline_processing:ci_build_prepare
- pipeline_processing:build_queue
- pipeline_processing:build_success
+- pipeline_processing:ci_build_prepare
+- pipeline_processing:ci_build_schedule
+- pipeline_processing:ci_resource_groups_assign_resource_from_resource_group
- pipeline_processing:pipeline_process
- pipeline_processing:pipeline_success
- pipeline_processing:pipeline_update
- pipeline_processing:stage_update
- pipeline_processing:update_head_pipeline_for_merge_request
-- pipeline_processing:ci_build_schedule
-- pipeline_processing:ci_resource_groups_assign_resource_from_resource_group
-
-- deployment:deployments_success
-- deployment:deployments_finished
-
-- repository_check:repository_check_clear
- repository_check:repository_check_batch
+- repository_check:repository_check_clear
- repository_check:repository_check_single_repository
-
- todos_destroyer:todos_destroyer_confidential_issue
- todos_destroyer:todos_destroyer_entity_leave
- todos_destroyer:todos_destroyer_group_private
-- todos_destroyer:todos_destroyer_project_private
- todos_destroyer:todos_destroyer_private_features
-
-- update_namespace_statistics:namespaces_schedule_aggregation
+- todos_destroyer:todos_destroyer_project_private
- update_namespace_statistics:namespaces_root_statistics
-
-- object_pool:object_pool_create
-- object_pool:object_pool_schedule_join
-- object_pool:object_pool_join
-- object_pool:object_pool_destroy
-
-- container_repository:delete_container_repository
-- container_repository:cleanup_container_repository
-
-- notifications:new_release
-
-- default
-- mailers # ActionMailer::DeliveryJob.queue_name
-
+- update_namespace_statistics:namespaces_schedule_aggregation
- authorized_projects
- background_migration
- chat_notification
+- create_evidence
- create_gpg_signature
+- create_note_diff_file
+- default
+- delete_diff_files
- delete_merged_branches
+- delete_stored_files
- delete_user
+- detect_repository_languages
- email_receiver
- emails_on_push
- error_tracking_issue_link
- expire_build_instance_artifacts
+- file_hook
- git_garbage_collect
+- github_import_advance_stage
- gitlab_shell
- group_destroy
+- group_export
+- import_issues_csv
- invalid_gpg_signature_update
- irker
+- mailers
- merge
+- merge_request_mergeability_check
- migrate_external_diffs
- namespaceless_project_destroy
- new_issue
- new_merge_request
- new_note
- pages
-- pages_domain_verification
- pages_domain_ssl_renewal
-- file_hook
+- pages_domain_verification
+- phabricator_import_import_tasks
- post_receive
- process_commit
- project_cache
+- project_daily_statistics
- project_destroy
- project_export
- project_service
@@ -170,26 +163,16 @@
- reactive_caching
- rebase
- remote_mirror_notification
+- repository_cleanup
- repository_fork
- repository_import
- repository_remove_remote
+- repository_update_remote_mirror
+- self_monitoring_project_create
+- self_monitoring_project_delete
- system_hook_push
- update_external_pull_requests
- update_merge_requests
- update_project_statistics
- upload_checksum
- web_hook
-- repository_update_remote_mirror
-- create_note_diff_file
-- delete_diff_files
-- detect_repository_languages
-- repository_cleanup
-- delete_stored_files
-- import_issues_csv
-- project_daily_statistics
-- create_evidence
-- group_export
-- self_monitoring_project_create
-- self_monitoring_project_delete
-- merge_request_mergeability_check
-- phabricator_import_import_tasks
diff --git a/changelogs/unreleased/fj-add-graphql-blob-viewer-to-snippets.yml b/changelogs/unreleased/fj-add-graphql-blob-viewer-to-snippets.yml
new file mode 100644
index 00000000000..09a97f1777f
--- /dev/null
+++ b/changelogs/unreleased/fj-add-graphql-blob-viewer-to-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Add blob and blob_viewer fields to graphql snippet type
+merge_request: 22960
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-remove-storage-version-column-from-snippets.yml b/changelogs/unreleased/fj-remove-storage-version-column-from-snippets.yml
new file mode 100644
index 00000000000..de69a2552dd
--- /dev/null
+++ b/changelogs/unreleased/fj-remove-storage-version-column-from-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Remove storage_version column from snippets
+merge_request: 23315
+author:
+type: other
diff --git a/changelogs/unreleased/remove_milestone_id_from_epics3.yml b/changelogs/unreleased/remove_milestone_id_from_epics3.yml
new file mode 100644
index 00000000000..cc12ea780cf
--- /dev/null
+++ b/changelogs/unreleased/remove_milestone_id_from_epics3.yml
@@ -0,0 +1,5 @@
+---
+title: Remove milestone_id from epics
+merge_request: 23282
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/sh-fix-mermaid-releases-page.yml b/changelogs/unreleased/sh-fix-mermaid-releases-page.yml
new file mode 100644
index 00000000000..5eb3f4d7680
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-mermaid-releases-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Markdown not rendering on releases page
+merge_request: 23370
+author:
+type: fixed
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 5f078459bc2..62fbc642908 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -916,7 +916,7 @@ production: &base
# Gitaly settings
gitaly:
# Path to the directory containing Gitaly client executables.
- client_path: /home/git/gitaly/bin
+ client_path: /home/git/gitaly
# Default Gitaly authentication token. Can be overridden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly.
diff --git a/db/post_migrate/20200120083607_remove_storage_version_column_from_snippets.rb b/db/post_migrate/20200120083607_remove_storage_version_column_from_snippets.rb
new file mode 100644
index 00000000000..62bb3f46cae
--- /dev/null
+++ b/db/post_migrate/20200120083607_remove_storage_version_column_from_snippets.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveStorageVersionColumnFromSnippets < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ return unless column_exists?(:snippets, :storage_version)
+
+ remove_column :snippets, :storage_version
+ end
+
+ def down
+ return if column_exists?(:snippets, :storage_version)
+
+ add_column_with_default( # rubocop:disable Migration/AddColumnWithDefault
+ :snippets,
+ :storage_version,
+ :integer,
+ default: 2,
+ allow_null: false
+ )
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ae1b8533102..c6c51481d1b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3843,7 +3843,6 @@ ActiveRecord::Schema.define(version: 2020_01_21_132641) do
t.string "encrypted_secret_token_iv", limit: 255
t.boolean "secret", default: false, null: false
t.string "repository_storage", limit: 255, default: "default", null: false
- t.integer "storage_version", default: 2, null: false
t.index ["author_id"], name: "index_snippets_on_author_id"
t.index ["content"], name: "index_snippets_on_content_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["created_at"], name: "index_snippets_on_created_at"
diff --git a/doc/administration/operations/extra_sidekiq_processes.md b/doc/administration/operations/extra_sidekiq_processes.md
index 1be89f759da..acb57debe26 100644
--- a/doc/administration/operations/extra_sidekiq_processes.md
+++ b/doc/administration/operations/extra_sidekiq_processes.md
@@ -268,8 +268,9 @@ default value can be found in `/opt/gitlab/etc/gitlab-rails/env/RAILS_ENV`.
### Using negation
-You're able to run all queues in `sidekiq_queues.yml` file on a single or
-multiple processes with exceptions using the `--negate` flag.
+You're able to run all queues in the `all_queues.yml` file (or the equivalent EE
+file) on a single or multiple processes with exceptions using the `--negate`
+flag.
For example, say you want to run a single process for all queues,
except `process_commit` and `post_receive`:
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 23ffae3b097..f3bf45b0b3d 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -150,6 +150,15 @@ type BlobEdge {
node: Blob
}
+"""
+Types of blob viewers
+"""
+enum BlobViewersType {
+ auxiliary
+ rich
+ simple
+}
+
type Commit {
"""
Author of the commit
@@ -5934,9 +5943,9 @@ type Snippet implements Noteable {
author: User!
"""
- Content of the snippet
+ Snippet blob
"""
- content: String!
+ blob: SnippetBlob!
"""
Timestamp this snippet was created
@@ -6050,6 +6059,91 @@ type Snippet implements Noteable {
}
"""
+Represents the snippet blob
+"""
+type SnippetBlob {
+ """
+ Shows whether the blob is binary
+ """
+ binary: Boolean!
+
+ """
+ Blob highlighted data
+ """
+ highlightedData: String
+
+ """
+ Blob name
+ """
+ name: String
+
+ """
+ Blob path
+ """
+ path: String
+
+ """
+ Blob raw content endpoint path
+ """
+ rawPath: String!
+
+ """
+ Blob content rich viewer
+ """
+ richViewer: SnippetBlobViewer
+
+ """
+ Blob content simple viewer
+ """
+ simpleViewer: SnippetBlobViewer!
+
+ """
+ Blob size
+ """
+ size: Int!
+}
+
+"""
+Represents how the blob content should be displayed
+"""
+type SnippetBlobViewer {
+ """
+ Shows whether the blob should be displayed collapsed
+ """
+ collapsed: Boolean!
+
+ """
+ Content file type
+ """
+ fileType: String!
+
+ """
+ Shows whether the blob content is loaded async
+ """
+ loadAsync: Boolean!
+
+ """
+ Loading partial name
+ """
+ loadingPartialName: String!
+
+ """
+ Error rendering the blob content
+ """
+ renderError: String
+
+ """
+ Shows whether the blob too large to be displayed
+ """
+ tooLarge: Boolean!
+
+ """
+ Type of blob viewer
+ """
+ type: BlobViewersType!
+}
+
+"""
The connection type for Snippet.
"""
type SnippetConnection {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 6239a398c7e..645df8c0184 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -6275,8 +6275,8 @@
"deprecationReason": null
},
{
- "name": "content",
- "description": "Content of the snippet",
+ "name": "blob",
+ "description": "Snippet blob",
"args": [
],
@@ -6284,8 +6284,8 @@
"kind": "NON_NULL",
"name": null,
"ofType": {
- "kind": "SCALAR",
- "name": "String",
+ "kind": "OBJECT",
+ "name": "SnippetBlob",
"ofType": null
}
},
@@ -7005,6 +7005,311 @@
"possibleTypes": null
},
{
+ "kind": "OBJECT",
+ "name": "SnippetBlob",
+ "description": "Represents the snippet blob",
+ "fields": [
+ {
+ "name": "binary",
+ "description": "Shows whether the blob is binary",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "highlightedData",
+ "description": "Blob highlighted data",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Blob name",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "path",
+ "description": "Blob path",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "rawPath",
+ "description": "Blob raw content endpoint path",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "richViewer",
+ "description": "Blob content rich viewer",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "SnippetBlobViewer",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "simpleViewer",
+ "description": "Blob content simple viewer",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "SnippetBlobViewer",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "size",
+ "description": "Blob size",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "SnippetBlobViewer",
+ "description": "Represents how the blob content should be displayed",
+ "fields": [
+ {
+ "name": "collapsed",
+ "description": "Shows whether the blob should be displayed collapsed",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fileType",
+ "description": "Content file type",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "loadAsync",
+ "description": "Shows whether the blob content is loaded async",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "loadingPartialName",
+ "description": "Loading partial name",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "renderError",
+ "description": "Error rendering the blob content",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "tooLarge",
+ "description": "Shows whether the blob too large to be displayed",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "type",
+ "description": "Type of blob viewer",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "BlobViewersType",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "BlobViewersType",
+ "description": "Types of blob viewers",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "rich",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "simple",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "auxiliary",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
"kind": "ENUM",
"name": "VisibilityScopesEnum",
"description": null,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 72fc82444ca..6696863faff 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -926,15 +926,44 @@ Represents a snippet entry
| `project` | Project | The project the snippet is associated with |
| `author` | User! | The owner of the snippet |
| `fileName` | String | File Name of the snippet |
-| `content` | String! | Content of the snippet |
| `description` | String | Description of the snippet |
| `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet |
| `createdAt` | Time! | Timestamp this snippet was created |
| `updatedAt` | Time! | Timestamp this snippet was updated |
| `webUrl` | String! | Web URL of the snippet |
| `rawUrl` | String! | Raw URL of the snippet |
+| `blob` | SnippetBlob! | Snippet blob |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+## SnippetBlob
+
+Represents the snippet blob
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `highlightedData` | String | Blob highlighted data |
+| `rawPath` | String! | Blob raw content endpoint path |
+| `size` | Int! | Blob size |
+| `binary` | Boolean! | Shows whether the blob is binary |
+| `name` | String | Blob name |
+| `path` | String | Blob path |
+| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |
+| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
+
+## SnippetBlobViewer
+
+Represents how the blob content should be displayed
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `type` | BlobViewersType! | Type of blob viewer |
+| `loadAsync` | Boolean! | Shows whether the blob content is loaded async |
+| `collapsed` | Boolean! | Shows whether the blob should be displayed collapsed |
+| `tooLarge` | Boolean! | Shows whether the blob too large to be displayed |
+| `renderError` | String | Error rendering the blob content |
+| `fileType` | String! | Content file type |
+| `loadingPartialName` | String! | Loading partial name |
+
## SnippetPermissions
| Name | Type | Description |
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 1639029d193..8c284ae955d 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -312,7 +312,7 @@ function createComponent(props = {}) {
`ApolloMutation` component exposes `mutate` method via scoped slot. If we want to test this method, we need to add it to mocks:
```javascript
-const mutate = jest.fn(() => Promise.resolve());
+const mutate = jest.fn().mockResolvedValue();
const $apollo = {
mutate,
};
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index 77663b0bb29..062a3e13c39 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -17,8 +17,11 @@ would be `process_something`. If you're not sure what queue a worker uses,
you can find it using `SomeWorker.queue`. There is almost never a reason to
manually override the queue name using `sidekiq_options queue: :some_queue`.
-You must always add any new queues to `app/workers/all_queues.yml` or `ee/app/workers/all_queues.yml`
-otherwise your worker will not run.
+After adding a new queue, run `bin/rake
+gitlab:sidekiq:all_queues_yml:generate` to regenerate
+`app/workers/all_queues.yml` or `ee/app/workers/all_queues.yml` so that
+it can be picked up by
+[`sidekiq-cluster`](../administration/operations/extra_sidekiq_processes.md).
## Queue Namespaces
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 2672b0f3461..cd1dabb6ef4 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -207,7 +207,8 @@ variables:
If your project requires custom build configurations, it can be preferable to avoid
compilation during your SAST execution and instead pass all job artifacts from an
-earlier stage within the pipeline.
+earlier stage within the pipeline. This is the current strategy when requiring
+a `before_script` execution to prepare your scan job.
To pass your project's dependencies as artifacts, the dependencies must be included
in the project's working directory and specified using the `artifacts:path` configuration.
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 2acb79e3e22..afa575241a1 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -178,7 +178,6 @@ excluded_attributes:
- :encrypted_secret_token
- :encrypted_secret_token_iv
- :repository_storage
- - :storage_version
merge_request_diff:
- :external_diff
- :stored_externally
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index b246c507e9e..c96212f27a7 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -4,6 +4,22 @@ require 'yaml'
module Gitlab
module SidekiqConfig
+ FOSS_QUEUE_CONFIG_PATH = 'app/workers/all_queues.yml'
+ EE_QUEUE_CONFIG_PATH = 'ee/app/workers/all_queues.yml'
+
+ QUEUE_CONFIG_PATHS = [
+ FOSS_QUEUE_CONFIG_PATH,
+ (EE_QUEUE_CONFIG_PATH if Gitlab.ee?)
+ ].compact.freeze
+
+ # For queues that don't have explicit workers - default and mailers
+ DummyWorker = Struct.new(:queue)
+
+ DEFAULT_WORKERS = [
+ Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('default'), ee: false),
+ Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('mailers'), ee: false)
+ ].freeze
+
class << self
include Gitlab::SidekiqConfig::CliMethods
@@ -25,28 +41,46 @@ module Gitlab
def workers
@workers ||= begin
- result = find_workers(Rails.root.join('app', 'workers'))
- result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'))) if Gitlab.ee?
+ result = []
+ result.concat(DEFAULT_WORKERS)
+ result.concat(find_workers(Rails.root.join('app', 'workers'), ee: false))
+
+ if Gitlab.ee?
+ result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'), ee: true))
+ end
+
result
end
end
+ def workers_for_all_queues_yml
+ workers.partition(&:ee?).reverse.map(&:sort)
+ end
+
+ def all_queues_yml_outdated?
+ foss_workers, ee_workers = workers_for_all_queues_yml
+
+ return true if foss_workers != YAML.safe_load(File.read(FOSS_QUEUE_CONFIG_PATH))
+
+ Gitlab.ee? && ee_workers != YAML.safe_load(File.read(EE_QUEUE_CONFIG_PATH))
+ end
+
private
- def find_workers(root)
+ def find_workers(root, ee:)
concerns = root.join('concerns').to_s
- workers = Dir[root.join('**', '*.rb')]
+ Dir[root.join('**', '*.rb')]
.reject { |path| path.start_with?(concerns) }
+ .map { |path| worker_from_path(path, root) }
+ .select { |worker| worker < Sidekiq::Worker }
+ .map { |worker| Gitlab::SidekiqConfig::Worker.new(worker, ee: ee) }
+ end
- workers.map! do |path|
- ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
-
- ns.camelize.constantize
- end
+ def worker_from_path(path, root)
+ ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
- # Skip things that aren't workers
- workers.select { |w| w < Sidekiq::Worker }
+ ns.camelize.constantize
end
end
end
diff --git a/lib/gitlab/sidekiq_config/worker.rb b/lib/gitlab/sidekiq_config/worker.rb
new file mode 100644
index 00000000000..313d9b17b16
--- /dev/null
+++ b/lib/gitlab/sidekiq_config/worker.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqConfig
+ class Worker
+ include Comparable
+
+ attr_reader :klass
+ delegate :feature_category_not_owned?, :get_feature_category,
+ :get_worker_resource_boundary, :latency_sensitive_worker?,
+ :queue, :worker_has_external_dependencies?,
+ to: :klass
+
+ def initialize(klass, ee:)
+ @klass = klass
+ @ee = ee
+ end
+
+ def ee?
+ @ee
+ end
+
+ def ==(other)
+ to_yaml == case other
+ when self.class
+ other.to_yaml
+ else
+ other
+ end
+ end
+
+ def <=>(other)
+ to_sort <=> other.to_sort
+ end
+
+ # Put namespaced queues first
+ def to_sort
+ [queue.include?(':') ? 0 : 1, queue]
+ end
+
+ # YAML representation
+ def encode_with(coder)
+ coder.represent_scalar(nil, to_yaml)
+ end
+
+ def to_yaml
+ queue
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake
new file mode 100644
index 00000000000..f6bb0196236
--- /dev/null
+++ b/lib/tasks/gitlab/sidekiq.rake
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
+namespace :gitlab do
+ namespace :sidekiq do
+ namespace :all_queues_yml do
+ def write_yaml(path, object)
+ banner = <<~BANNER
+ # This file is generated automatically by
+ # bin/rake gitlab:sidekiq:all_queues_yml:generate
+ #
+ # Do not edit it manually!
+ BANNER
+
+ File.write(path, banner + YAML.dump(object))
+ end
+
+ desc 'GitLab | Generate all_queues.yml based on worker definitions'
+ task generate: :environment do
+ foss_workers, ee_workers = Gitlab::SidekiqConfig.workers_for_all_queues_yml
+
+ write_yaml(Gitlab::SidekiqConfig::FOSS_QUEUE_CONFIG_PATH, foss_workers)
+
+ if Gitlab.ee?
+ write_yaml(Gitlab::SidekiqConfig::EE_QUEUE_CONFIG_PATH, ee_workers)
+ end
+ end
+
+ desc 'GitLab | Validate that all_queues.yml matches worker definitions'
+ task check: :environment do
+ if Gitlab::SidekiqConfig.all_queues_yml_outdated?
+ raise <<~MSG
+ Changes in worker queues found, please update the metadata by running:
+
+ bin/rake gitlab:sidekiq:all_queues_yml:generate
+
+ Then commit and push the changes from:
+
+ - #{Gitlab::SidekiqConfig::FOSS_QUEUE_CONFIG_PATH}
+ - #{Gitlab::SidekiqConfig::EE_QUEUE_CONFIG_PATH}
+
+ MSG
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 9a5693e78a2..42a9c027b6a 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -34,6 +34,7 @@ unless Rails.env.production?
scss_lint
gettext:lint
lint:static_verification
+ gitlab:sidekiq:all_queues_yml:check
]
if Gitlab.ee?
diff --git a/package.json b/package.json
index 9a3553dcce3..f6007d831e5 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.6.2",
"@gitlab/svgs": "^1.89.0",
- "@gitlab/ui": "8.17.0",
+ "@gitlab/ui": "^8.18.0",
"@gitlab/visual-review-tools": "1.5.1",
"@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "^0.0.18",
diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb
index 7af881f4214..5d3fb284eef 100644
--- a/spec/factories/project_error_tracking_settings.rb
+++ b/spec/factories/project_error_tracking_settings.rb
@@ -3,7 +3,7 @@
FactoryBot.define do
factory :project_error_tracking_setting, class: 'ErrorTracking::ProjectErrorTrackingSetting' do
project
- api_url { 'https://gitlab.com/api/0/projects/sentry-org/sentry-project' }
+ api_url { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
enabled { true }
token { 'access_token_123' }
project_name { 'Sentry Project' }
diff --git a/spec/features/error_tracking/user_sees_error_details_spec.rb b/spec/features/error_tracking/user_sees_error_details_spec.rb
new file mode 100644
index 00000000000..6f72c44c689
--- /dev/null
+++ b/spec/features/error_tracking/user_sees_error_details_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'View error details page', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+ include_context 'sentry error tracking context feature'
+
+ context 'with current user as project owner' do
+ before do
+ sign_in(project.owner)
+
+ visit details_project_error_tracking_index_path(project, issue_id: issue_id)
+ end
+
+ it_behaves_like 'error tracking show page'
+ end
+
+ context 'with current user as project guest' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_guest(user)
+ sign_in(user)
+
+ visit details_project_error_tracking_index_path(project, issue_id: issue_id)
+ end
+
+ it 'renders not found' do
+ expect(page).to have_content('Page Not Found')
+ end
+ end
+end
diff --git a/spec/features/error_tracking/user_sees_error_index_spec.rb b/spec/features/error_tracking/user_sees_error_index_spec.rb
new file mode 100644
index 00000000000..0d23df31e29
--- /dev/null
+++ b/spec/features/error_tracking/user_sees_error_index_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'View error details page', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+ include_context 'sentry error tracking context feature'
+
+ let_it_be(:issues_response_body) { fixture_file('sentry/issues_sample_response.json') }
+ let_it_be(:issues_response) { JSON.parse(issues_response_body) }
+ let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" }
+
+ before do
+ stub_request(:get, issues_api_url).with(
+ headers: { 'Authorization' => 'Bearer access_token_123' }
+ ).to_return(status: 200, body: issues_response_body, headers: { 'Content-Type' => 'application/json' })
+ end
+
+ context 'with current user as project owner' do
+ before do
+ sign_in(project.owner)
+
+ visit project_error_tracking_index_path(project)
+ end
+
+ it_behaves_like 'error tracking index page'
+ end
+
+ # A bug caused the detail link to be broken for all users but the project owner
+ context 'with current user as project maintainer' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit project_error_tracking_index_path(project)
+ end
+
+ it_behaves_like 'error tracking index page'
+ end
+
+ context 'with error tracking settings disabled' do
+ before do
+ project_error_tracking_settings.update(enabled: false)
+ sign_in(project.owner)
+
+ visit project_error_tracking_index_path(project)
+ end
+
+ it 'renders call to action' do
+ expect(page).to have_content('Enable error tracking')
+ end
+ end
+
+ context 'with current user as project guest' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_guest(user)
+ sign_in(user)
+
+ visit project_error_tracking_index_path(project)
+ end
+
+ it 'renders not found' do
+ expect(page).to have_content('Page Not Found')
+ end
+ end
+end
diff --git a/spec/fixtures/sentry/issue_sample_response.json b/spec/fixtures/sentry/issue_sample_response.json
index a320a21de34..43d55f584b8 100644
--- a/spec/fixtures/sentry/issue_sample_response.json
+++ b/spec/fixtures/sentry/issue_sample_response.json
@@ -38,7 +38,7 @@
},
"firstSeen": "2018-11-06T21:19:55Z",
"hasSeen": false,
- "id": "503504",
+ "id": "11",
"isBookmarked": false,
"isPublic": false,
"isSubscribed": true,
@@ -72,232 +72,64 @@
"shortId": "PUMP-STATION-1",
"stats": {
"24h": [
- [
- 1541451600.0,
- 557
- ],
- [
- 1541455200.0,
- 473
- ],
- [
- 1541458800.0,
- 914
- ],
- [
- 1541462400.0,
- 991
- ],
- [
- 1541466000.0,
- 925
- ],
- [
- 1541469600.0,
- 881
- ],
- [
- 1541473200.0,
- 182
- ],
- [
- 1541476800.0,
- 490
- ],
- [
- 1541480400.0,
- 820
- ],
- [
- 1541484000.0,
- 322
- ],
- [
- 1541487600.0,
- 836
- ],
- [
- 1541491200.0,
- 565
- ],
- [
- 1541494800.0,
- 758
- ],
- [
- 1541498400.0,
- 880
- ],
- [
- 1541502000.0,
- 677
- ],
- [
- 1541505600.0,
- 381
- ],
- [
- 1541509200.0,
- 814
- ],
- [
- 1541512800.0,
- 329
- ],
- [
- 1541516400.0,
- 446
- ],
- [
- 1541520000.0,
- 731
- ],
- [
- 1541523600.0,
- 111
- ],
- [
- 1541527200.0,
- 926
- ],
- [
- 1541530800.0,
- 772
- ],
- [
- 1541534400.0,
- 400
- ],
- [
- 1541538000.0,
- 943
- ]
+ [1541451600.0, 557],
+ [1541455200.0, 473],
+ [1541458800.0, 914],
+ [1541462400.0, 991],
+ [1541466000.0, 925],
+ [1541469600.0, 881],
+ [1541473200.0, 182],
+ [1541476800.0, 490],
+ [1541480400.0, 820],
+ [1541484000.0, 322],
+ [1541487600.0, 836],
+ [1541491200.0, 565],
+ [1541494800.0, 758],
+ [1541498400.0, 880],
+ [1541502000.0, 677],
+ [1541505600.0, 381],
+ [1541509200.0, 814],
+ [1541512800.0, 329],
+ [1541516400.0, 446],
+ [1541520000.0, 731],
+ [1541523600.0, 111],
+ [1541527200.0, 926],
+ [1541530800.0, 772],
+ [1541534400.0, 400],
+ [1541538000.0, 943]
],
"30d": [
- [
- 1538870400.0,
- 565
- ],
- [
- 1538956800.0,
- 12862
- ],
- [
- 1539043200.0,
- 15617
- ],
- [
- 1539129600.0,
- 10809
- ],
- [
- 1539216000.0,
- 15065
- ],
- [
- 1539302400.0,
- 12927
- ],
- [
- 1539388800.0,
- 12994
- ],
- [
- 1539475200.0,
- 13139
- ],
- [
- 1539561600.0,
- 11838
- ],
- [
- 1539648000.0,
- 12088
- ],
- [
- 1539734400.0,
- 12338
- ],
- [
- 1539820800.0,
- 12768
- ],
- [
- 1539907200.0,
- 12816
- ],
- [
- 1539993600.0,
- 15356
- ],
- [
- 1540080000.0,
- 10910
- ],
- [
- 1540166400.0,
- 12306
- ],
- [
- 1540252800.0,
- 12912
- ],
- [
- 1540339200.0,
- 14700
- ],
- [
- 1540425600.0,
- 11890
- ],
- [
- 1540512000.0,
- 11684
- ],
- [
- 1540598400.0,
- 13510
- ],
- [
- 1540684800.0,
- 12625
- ],
- [
- 1540771200.0,
- 12811
- ],
- [
- 1540857600.0,
- 13180
- ],
- [
- 1540944000.0,
- 14651
- ],
- [
- 1541030400.0,
- 14161
- ],
- [
- 1541116800.0,
- 12612
- ],
- [
- 1541203200.0,
- 14316
- ],
- [
- 1541289600.0,
- 14742
- ],
- [
- 1541376000.0,
- 12505
- ],
- [
- 1541462400.0,
- 14180
- ]
+ [1538870400.0, 565],
+ [1538956800.0, 12862],
+ [1539043200.0, 15617],
+ [1539129600.0, 10809],
+ [1539216000.0, 15065],
+ [1539302400.0, 12927],
+ [1539388800.0, 12994],
+ [1539475200.0, 13139],
+ [1539561600.0, 11838],
+ [1539648000.0, 12088],
+ [1539734400.0, 12338],
+ [1539820800.0, 12768],
+ [1539907200.0, 12816],
+ [1539993600.0, 15356],
+ [1540080000.0, 10910],
+ [1540166400.0, 12306],
+ [1540252800.0, 12912],
+ [1540339200.0, 14700],
+ [1540425600.0, 11890],
+ [1540512000.0, 11684],
+ [1540598400.0, 13510],
+ [1540684800.0, 12625],
+ [1540771200.0, 12811],
+ [1540857600.0, 13180],
+ [1540944000.0, 14651],
+ [1541030400.0, 14161],
+ [1541116800.0, 12612],
+ [1541203200.0, 14316],
+ [1541289600.0, 14742],
+ [1541376000.0, 12505],
+ [1541462400.0, 14180]
]
},
"status": "unresolved",
diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
index 5c784c8000f..3d56bef4b33 100644
--- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
+++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
@@ -18,6 +18,8 @@ exports[`grafana integration component default state to match the default snapsh
<gl-button-stub
class="js-settings-toggle"
+ size="md"
+ variant="secondary"
>
Expand
</gl-button-stub>
@@ -89,6 +91,7 @@ exports[`grafana integration component default state to match the default snapsh
</gl-form-group-stub>
<gl-button-stub
+ size="md"
variant="success"
>
diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js
index 180e41861f4..340143a6b53 100644
--- a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js
+++ b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js
@@ -12,7 +12,7 @@ describe('DateTimePicker', () => {
const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle');
const dropdownMenu = () => dateTimePicker.find('.dropdown-menu');
- const applyButtonElement = () => dateTimePicker.find('button[variant="success"]').element;
+ const applyButtonElement = () => dateTimePicker.find('button.btn-success').element;
const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element;
const fillInputAndBlur = (input, val) => {
dateTimePicker.find(input).setValue(val);
diff --git a/spec/frontend/notes/components/discussion_filter_note_spec.js b/spec/frontend/notes/components/discussion_filter_note_spec.js
index 6b5f42a84e8..4701108d315 100644
--- a/spec/frontend/notes/components/discussion_filter_note_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_note_spec.js
@@ -1,93 +1,40 @@
-import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
import eventHub from '~/notes/event_hub';
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
describe('DiscussionFilterNote component', () => {
- let vm;
+ let wrapper;
const createComponent = () => {
- const Component = Vue.extend(DiscussionFilterNote);
-
- return mountComponent(Component);
+ wrapper = shallowMount(DiscussionFilterNote);
};
beforeEach(() => {
- vm = createComponent();
+ createComponent();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- describe('computed', () => {
- describe('timelineContent', () => {
- it('returns string containing instruction for switching feed type', () => {
- expect(vm.timelineContent).toBe(
- "You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.",
- );
- });
- });
+ it('timelineContent renders a string containing instruction for switching feed type', () => {
+ expect(wrapper.find({ ref: 'timelineContent' }).html()).toBe(
+ "<div>You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.</div>",
+ );
});
- describe('methods', () => {
- describe('selectFilter', () => {
- it('emits `dropdownSelect` event on `eventHub` with provided param', () => {
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ it('emits `dropdownSelect` event with 0 parameter on clicking Show all activity button', () => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ wrapper.find({ ref: 'showAllActivity' }).vm.$emit('click');
- vm.selectFilter(1);
-
- expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
- });
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 0);
});
- describe('template', () => {
- it('renders component container element', () => {
- expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true);
- });
-
- it('renders comment icon element', () => {
- expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain(
- 'comment',
- );
- });
-
- it('renders filter information note', () => {
- expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain(
- "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
- );
- });
-
- it('renders filter buttons', () => {
- const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions');
-
- expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain(
- 'Show all activity',
- );
-
- expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain(
- 'Show comments only',
- );
- });
-
- it('clicking `Show all activity` button calls `selectFilter("all")` method', () => {
- const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child');
- jest.spyOn(vm, 'selectFilter').mockImplementation(() => {});
-
- showAllBtn.dispatchEvent(new Event('click'));
-
- expect(vm.selectFilter).toHaveBeenCalledWith(0);
- });
-
- it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => {
- const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child');
- jest.spyOn(vm, 'selectFilter').mockImplementation(() => {});
-
- showAllBtn.dispatchEvent(new Event('click'));
+ it('emits `dropdownSelect` event with 1 parameter on clicking Show comments only button', () => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ wrapper.find({ ref: 'showComments' }).vm.$emit('click');
- expect(vm.selectFilter).toHaveBeenCalledWith(1);
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
});
});
diff --git a/spec/frontend/notes/components/note_attachment_spec.js b/spec/frontend/notes/components/note_attachment_spec.js
index b14a518b622..9d1051676e1 100644
--- a/spec/frontend/notes/components/note_attachment_spec.js
+++ b/spec/frontend/notes/components/note_attachment_spec.js
@@ -1,23 +1,45 @@
-import Vue from 'vue';
-import noteAttachment from '~/notes/components/note_attachment.vue';
-
-describe('issue note attachment', () => {
- it('should render properly', () => {
- const props = {
- attachment: {
- filename: 'dk.png',
- image: true,
- url: '/dk.png',
+import { shallowMount } from '@vue/test-utils';
+import NoteAttachment from '~/notes/components/note_attachment.vue';
+
+describe('Issue note attachment', () => {
+ let wrapper;
+
+ const findImage = () => wrapper.find({ ref: 'attachmentImage' });
+ const findUrl = () => wrapper.find({ ref: 'attachmentUrl' });
+
+ const createComponent = attachment => {
+ wrapper = shallowMount(NoteAttachment, {
+ propsData: {
+ attachment,
},
- };
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders attachment image if it is passed in attachment prop', () => {
+ createComponent({
+ image: 'test-image',
+ });
+
+ expect(findImage().exists()).toBe(true);
+ });
+
+ it('renders attachment url if it is passed in attachment prop', () => {
+ createComponent({
+ url: 'test-url',
+ });
+
+ expect(findUrl().exists()).toBe(true);
+ });
- const Component = Vue.extend(noteAttachment);
- const vm = new Component({
- propsData: props,
- }).$mount();
+ it('does not render image and url if attachment object is empty', () => {
+ createComponent({});
- expect(vm.$el.classList.contains('note-attachment')).toBeTruthy();
- expect(vm.$el.querySelector('img').src).toContain(props.attachment.url);
- expect(vm.$el.querySelector('a').href).toContain(props.attachment.url);
+ expect(findImage().exists()).toBe(false);
+ expect(findUrl().exists()).toBe(false);
});
});
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 9b432387654..6544ad3e1fe 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -1,125 +1,141 @@
-import Vue from 'vue';
-import noteHeader from '~/notes/components/note_header.vue';
-import createStore from '~/notes/stores';
-
-describe('note_header component', () => {
- let store;
- let vm;
- let Component;
-
- beforeEach(() => {
- Component = Vue.extend(noteHeader);
- store = createStore();
- });
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import NoteHeader from '~/notes/components/note_header.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const actions = {
+ setTargetNoteHash: jest.fn(),
+};
+
+describe('NoteHeader component', () => {
+ let wrapper;
+
+ const findActionsWrapper = () => wrapper.find({ ref: 'discussionActions' });
+ const findChevronIcon = () => wrapper.find({ ref: 'chevronIcon' });
+ const findActionText = () => wrapper.find({ ref: 'actionText' });
+ const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
+
+ const createComponent = props => {
+ wrapper = shallowMount(NoteHeader, {
+ localVue,
+ store: new Vuex.Store({
+ actions,
+ }),
+ propsData: {
+ ...props,
+ actionTextHtml: '',
+ noteId: '1394',
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- describe('individual note', () => {
- beforeEach(() => {
- vm = new Component({
- store,
- propsData: {
- actionText: 'commented',
- actionTextHtml: '',
- author: {
- avatar_url: null,
- id: 1,
- name: 'Root',
- path: '/root',
- state: 'active',
- username: 'root',
- },
- createdAt: '2017-08-02T10:51:58.559Z',
- includeToggle: false,
- noteId: '1394',
- expanded: true,
- },
- }).$mount();
+ it('does not render discussion actions when includeToggle is false', () => {
+ createComponent({
+ includeToggle: false,
});
- it('should render user information', () => {
- expect(vm.$el.querySelector('.note-header-author-name').textContent.trim()).toEqual('Root');
- expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual('/root');
- expect(vm.$el.querySelector('.note-header-info a').dataset.userId).toEqual('1');
- expect(vm.$el.querySelector('.note-header-info a').dataset.username).toEqual('root');
- expect(vm.$el.querySelector('.note-header-info a').classList).toContain('js-user-link');
+ expect(findActionsWrapper().exists()).toBe(false);
+ });
+
+ describe('when includes a toggle', () => {
+ it('renders discussion actions', () => {
+ createComponent({
+ includeToggle: true,
+ });
+
+ expect(findActionsWrapper().exists()).toBe(true);
});
- it('should render timestamp link', () => {
- expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined();
+ it('emits toggleHandler event on button click', () => {
+ createComponent({
+ includeToggle: true,
+ });
+
+ wrapper.find('.note-action-button').trigger('click');
+ expect(wrapper.emitted('toggleHandler')).toBeDefined();
+ expect(wrapper.emitted('toggleHandler')).toHaveLength(1);
});
- it('should not render user information when prop `author` is empty object', done => {
- vm.author = {};
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.note-header-author-name')).toBeNull();
- })
- .then(done)
- .catch(done.fail);
+ it('has chevron-up icon if expanded prop is true', () => {
+ createComponent({
+ includeToggle: true,
+ expanded: true,
+ });
+
+ expect(findChevronIcon().classes()).toContain('fa-chevron-up');
});
- });
- describe('discussion', () => {
- beforeEach(() => {
- vm = new Component({
- store,
- propsData: {
- actionText: 'started a discussion',
- actionTextHtml: '',
- author: {
- avatar_url: null,
- id: 1,
- name: 'Root',
- path: '/root',
- state: 'active',
- username: 'root',
- },
- createdAt: '2017-08-02T10:51:58.559Z',
- includeToggle: true,
- noteId: '1395',
- expanded: true,
- },
- }).$mount();
+ it('has chevron-down icon if expanded prop is false', () => {
+ createComponent({
+ includeToggle: true,
+ expanded: false,
+ });
+
+ expect(findChevronIcon().classes()).toContain('fa-chevron-down');
});
+ });
- it('should render toggle button', () => {
- expect(vm.$el.querySelector('.js-vue-toggle-button')).toBeDefined();
+ it('renders an author link if author is passed to props', () => {
+ createComponent({
+ author: {
+ avatar_url: null,
+ id: 1,
+ name: 'Root',
+ path: '/root',
+ state: 'active',
+ username: 'root',
+ },
});
- it('emits toggle event on click', done => {
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
+ expect(wrapper.find('.js-user-link').exists()).toBe(true);
+ });
- vm.$el.querySelector('.js-vue-toggle-button').click();
+ it('renders deleted user text if author is not passed as a prop', () => {
+ createComponent();
- Vue.nextTick(() => {
- expect(vm.$emit).toHaveBeenCalledWith('toggleHandler');
- done();
- });
- });
+ expect(wrapper.text()).toContain('A deleted user');
+ });
+
+ it('does not render created at information if createdAt is not passed as a prop', () => {
+ createComponent();
- it('renders up arrow when open', done => {
- vm.expanded = true;
+ expect(findActionText().exists()).toBe(false);
+ expect(findTimestamp().exists()).toBe(false);
+ });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
- 'fa-chevron-up',
- );
- done();
+ describe('when createdAt is passed as a prop', () => {
+ it('renders action text and a timestamp', () => {
+ createComponent({
+ createdAt: '2017-08-02T10:51:58.559Z',
});
+
+ expect(findActionText().exists()).toBe(true);
+ expect(findTimestamp().exists()).toBe(true);
});
- it('renders down arrow when closed', done => {
- vm.expanded = false;
+ it('renders correct actionText if passed', () => {
+ createComponent({
+ createdAt: '2017-08-02T10:51:58.559Z',
+ actionText: 'Test action text',
+ });
+
+ expect(findActionText().text()).toBe('Test action text');
+ });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
- 'fa-chevron-down',
- );
- done();
+ it('calls an action when timestamp is clicked', () => {
+ createComponent({
+ createdAt: '2017-08-02T10:51:58.559Z',
});
+ findTimestamp().trigger('click');
+
+ expect(actions.setTargetNoteHash).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
index d5ce2c1ee24..9b723ccc3dc 100644
--- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
+++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
@@ -39,6 +39,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
</form>
<gl-button-stub
+ size="md"
variant="secondary"
>
Cancel
@@ -46,6 +47,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
<gl-button-stub
disabled="true"
+ size="md"
variant="warning"
>
@@ -55,6 +57,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
<gl-button-stub
disabled="true"
+ size="md"
variant="danger"
>
action
diff --git a/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
index d11a9bdeb51..c8482bf08ca 100644
--- a/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
+++ b/spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap
@@ -84,7 +84,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append"
>
<button
- class="btn input-group-text btn-secondary btn-default"
+ class="btn input-group-text btn-secondary btn-md btn-default"
data-clipboard-text="docker login host"
title="Copy login command"
type="button"
@@ -122,7 +122,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append"
>
<button
- class="btn input-group-text btn-secondary btn-default"
+ class="btn input-group-text btn-secondary btn-md btn-default"
data-clipboard-text="docker build -t url ."
title="Copy build command"
type="button"
@@ -152,7 +152,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append"
>
<button
- class="btn input-group-text btn-secondary btn-default"
+ class="btn input-group-text btn-secondary btn-md btn-default"
data-clipboard-text="docker push url"
title="Copy push command"
type="button"
diff --git a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap
index 1d8627da181..eccfbaa62da 100644
--- a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap
+++ b/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap
@@ -159,7 +159,9 @@ exports[`Settings Form renders 1`] = `
>
<glbutton-stub
class="mr-2 d-block"
+ size="md"
type="reset"
+ variant="secondary"
>
Cancel
@@ -168,6 +170,7 @@ exports[`Settings Form renders 1`] = `
<glbutton-stub
class="d-flex justify-content-center align-items-center js-no-auto-disable"
+ size="md"
type="submit"
variant="success"
>
diff --git a/spec/frontend/releases/list/components/release_block_spec.js b/spec/frontend/releases/list/components/release_block_spec.js
index 20c25a4aac2..0e8d569f326 100644
--- a/spec/frontend/releases/list/components/release_block_spec.js
+++ b/spec/frontend/releases/list/components/release_block_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { mount } from '@vue/test-utils';
import { first } from 'underscore';
import EvidenceBlock from '~/releases/list/components/evidence_block.vue';
@@ -43,6 +44,7 @@ describe('Release block', () => {
const editButton = () => wrapper.find('.js-edit-button');
beforeEach(() => {
+ jest.spyOn($.fn, 'renderGFM');
releaseClone = JSON.parse(JSON.stringify(release));
});
@@ -66,6 +68,11 @@ describe('Release block', () => {
expect(wrapper.text()).toContain(release.name);
});
+ it('renders release description', () => {
+ expect(wrapper.vm.$refs['gfm-content']).toBeDefined();
+ expect($.fn.renderGFM).toHaveBeenCalledTimes(1);
+ });
+
it('renders release date', () => {
expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at));
});
diff --git a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap
index 1d0f0c024d6..b1644ac2b1f 100644
--- a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap
+++ b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap
@@ -17,6 +17,8 @@ exports[`self monitor component When the self monitor project has not been creat
<gl-button-stub
class="js-settings-toggle"
+ size="md"
+ variant="secondary"
>
Expand
</gl-button-stub>
diff --git a/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
index 3a518029702..2abcc53bf14 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/expand_button_spec.js.snap
@@ -1,14 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Expand button on click when short text is provided renders button after text 1`] = `
-"<span><button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-prepend text-expander btn-blank btn-secondary\\" style=\\"display: none;\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\"><use xlink:href=\\"#ellipsis_h\\"></use></svg></button> <!----> <span><p>Expanded!</p></span> <button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-append text-expander btn-blank btn-secondary\\" style=\\"\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\">
- <use xlink:href=\\"#ellipsis_h\\"></use>
- </svg></button></span>"
+<span>
+ <button
+ aria-label="Click to expand text"
+ class="btn js-text-expander-prepend text-expander btn-blank btn-secondary btn-md"
+ style="display: none;"
+ type="button"
+ >
+ <svg
+ aria-hidden="true"
+ class="s12 ic-ellipsis_h"
+ >
+ <use
+ xlink:href="#ellipsis_h"
+ />
+ </svg>
+ </button>
+
+ <!---->
+
+ <span>
+ <p>
+ Expanded!
+ </p>
+ </span>
+
+ <button
+ aria-label="Click to expand text"
+ class="btn js-text-expander-append text-expander btn-blank btn-secondary btn-md"
+ style=""
+ type="button"
+ >
+ <svg
+ aria-hidden="true"
+ class="s12 ic-ellipsis_h"
+ >
+ <use
+ xlink:href="#ellipsis_h"
+ />
+ </svg>
+ </button>
+</span>
`;
exports[`Expand button when short text is provided renders button before text 1`] = `
-"<span><button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-prepend text-expander btn-blank btn-secondary\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\"><use xlink:href=\\"#ellipsis_h\\"></use></svg></button> <span><p>Short</p></span>
-<!----> <button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-append text-expander btn-blank btn-secondary\\" style=\\"display: none;\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\">
- <use xlink:href=\\"#ellipsis_h\\"></use>
- </svg></button></span>"
+<span>
+ <button
+ aria-label="Click to expand text"
+ class="btn js-text-expander-prepend text-expander btn-blank btn-secondary btn-md"
+ type="button"
+ >
+ <svg
+ aria-hidden="true"
+ class="s12 ic-ellipsis_h"
+ >
+ <use
+ xlink:href="#ellipsis_h"
+ />
+ </svg>
+ </button>
+
+ <span>
+ <p>
+ Short
+ </p>
+ </span>
+
+ <!---->
+
+ <button
+ aria-label="Click to expand text"
+ class="btn js-text-expander-append text-expander btn-blank btn-secondary btn-md"
+ style="display: none;"
+ type="button"
+ >
+ <svg
+ aria-hidden="true"
+ class="s12 ic-ellipsis_h"
+ >
+ <use
+ xlink:href="#ellipsis_h"
+ />
+ </svg>
+ </button>
+</span>
`;
diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js
index 3b1c8f6219c..aea90e5b31f 100644
--- a/spec/frontend/vue_shared/components/expand_button_spec.js
+++ b/spec/frontend/vue_shared/components/expand_button_spec.js
@@ -71,7 +71,7 @@ describe('Expand button', () => {
it('renders button before text', () => {
expect(expanderPrependEl().isVisible()).toBe(true);
expect(expanderAppendEl().isVisible()).toBe(false);
- expect(wrapper.find(ExpandButton).html()).toMatchSnapshot();
+ expect(wrapper.find(ExpandButton).element).toMatchSnapshot();
});
});
@@ -119,7 +119,7 @@ describe('Expand button', () => {
it('renders button after text', () => {
expect(expanderPrependEl().isVisible()).toBe(false);
expect(expanderAppendEl().isVisible()).toBe(true);
- expect(wrapper.find(ExpandButton).html()).toMatchSnapshot();
+ expect(wrapper.find(ExpandButton).element).toMatchSnapshot();
});
});
});
diff --git a/spec/graphql/types/blob_viewers/type_enum_spec.rb b/spec/graphql/types/blob_viewers/type_enum_spec.rb
new file mode 100644
index 00000000000..7bd4352f388
--- /dev/null
+++ b/spec/graphql/types/blob_viewers/type_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::BlobViewers::TypeEnum do
+ it { expect(described_class.graphql_name).to eq('BlobViewersType') }
+
+ it 'exposes all tree entry types' do
+ expect(described_class.values.keys).to include(*%w[rich simple auxiliary])
+ end
+end
diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb
index 5524e7a415d..a06d372f668 100644
--- a/spec/graphql/types/snippet_type_spec.rb
+++ b/spec/graphql/types/snippet_type_spec.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
describe GitlabSchema.types['Snippet'] do
it 'has the correct fields' do
expected_fields = [:id, :title, :project, :author,
- :file_name, :content, :description,
+ :file_name, :description,
:visibility_level, :created_at, :updated_at,
:web_url, :raw_url, :notes, :discussions,
- :user_permissions, :description_html]
+ :user_permissions, :description_html, :blob]
is_expected.to have_graphql_fields(*expected_fields)
end
diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb
new file mode 100644
index 00000000000..f1837538b53
--- /dev/null
+++ b/spec/graphql/types/snippets/blob_type_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['SnippetBlob'] do
+ it 'has the correct fields' do
+ expected_fields = [:highlighted_data, :raw_path,
+ :size, :binary, :name, :path,
+ :simple_viewer, :rich_viewer]
+
+ is_expected.to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/snippets/blob_viewer_type_spec.rb b/spec/graphql/types/snippets/blob_viewer_type_spec.rb
new file mode 100644
index 00000000000..f1f7608cb69
--- /dev/null
+++ b/spec/graphql/types/snippets/blob_viewer_type_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['SnippetBlobViewer'] do
+ it 'has the correct fields' do
+ expected_fields = [:type, :load_async, :too_large, :collapsed,
+ :render_error, :file_type, :loading_partial_name]
+
+ is_expected.to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_config/worker_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
new file mode 100644
index 00000000000..f2fe51abd5e
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::SidekiqConfig::Worker do
+ def worker_with_queue(queue)
+ described_class.new(double(queue: queue), ee: false)
+ end
+
+ describe '#ee?' do
+ it 'returns the EE status set on creation' do
+ expect(described_class.new(double, ee: true)).to be_ee
+ expect(described_class.new(double, ee: false)).not_to be_ee
+ end
+ end
+
+ describe '#==' do
+ def worker_with_yaml(yaml)
+ described_class.new(double, ee: false).tap do |worker|
+ allow(worker).to receive(:to_yaml).and_return(yaml)
+ end
+ end
+
+ it 'defines two workers as equal if their YAML representations are equal' do
+ expect(worker_with_yaml('a')).to eq(worker_with_yaml('a'))
+ expect(worker_with_yaml('a')).not_to eq(worker_with_yaml('b'))
+ end
+
+ it 'returns true when a worker is compared with its YAML representation' do
+ expect(worker_with_yaml('a')).to eq('a')
+ expect(worker_with_yaml(a: 1, b: 2)).to eq(a: 1, b: 2)
+ end
+ end
+
+ describe 'delegations' do
+ [
+ :feature_category_not_owned?, :get_feature_category,
+ :get_worker_resource_boundary, :latency_sensitive_worker?, :queue,
+ :worker_has_external_dependencies?
+ ].each do |meth|
+ it "delegates #{meth} to the worker class" do
+ worker = double
+
+ expect(worker).to receive(meth)
+
+ described_class.new(worker, ee: false).send(meth)
+ end
+ end
+ end
+
+ describe 'sorting' do
+ it 'sorts queues with a namespace before those without a namespace' do
+ namespaced_worker = worker_with_queue('namespace:queue')
+ plain_worker = worker_with_queue('a_queue')
+
+ expect([plain_worker, namespaced_worker].sort)
+ .to eq([namespaced_worker, plain_worker])
+ end
+
+ it 'sorts alphabetically by queue' do
+ workers = [
+ worker_with_queue('namespace:a'),
+ worker_with_queue('namespace:b'),
+ worker_with_queue('other_namespace:a'),
+ worker_with_queue('other_namespace:b'),
+ worker_with_queue('a'),
+ worker_with_queue('b')
+ ]
+
+ expect(workers.shuffle.sort).to eq(workers)
+ end
+ end
+
+ describe 'YAML encoding' do
+ it 'encodes the worker in YAML as a string of the queue' do
+ worker_a = worker_with_queue('a')
+ worker_b = worker_with_queue('b')
+
+ expect(YAML.dump(worker_a)).to eq(YAML.dump('a'))
+ expect(YAML.dump([worker_a, worker_b]))
+ .to eq(YAML.dump(%w[a b]))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 49efbac160a..39bb149cf73 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
describe Gitlab::SidekiqConfig do
describe '.workers' do
it 'includes all workers' do
- workers = described_class.workers
+ worker_classes = described_class.workers.map(&:klass)
- expect(workers).to include(PostReceive)
- expect(workers).to include(MergeWorker)
+ expect(worker_classes).to include(PostReceive)
+ expect(worker_classes).to include(MergeWorker)
end
end
@@ -44,4 +44,40 @@ describe Gitlab::SidekiqConfig do
expect(queues).to include('unknown')
end
end
+
+ describe '.workers_for_all_queues_yml' do
+ it 'returns a tuple with FOSS workers first' do
+ expect(described_class.workers_for_all_queues_yml.first)
+ .to include(an_object_having_attributes(queue: 'post_receive'))
+ end
+ end
+
+ describe '.all_queues_yml_outdated?' do
+ before do
+ workers = [
+ PostReceive,
+ MergeWorker,
+ ProcessCommitWorker
+ ].map { |worker| described_class::Worker.new(worker, ee: false) }
+
+ allow(described_class).to receive(:workers).and_return(workers)
+ allow(Gitlab).to receive(:ee?).and_return(false)
+ end
+
+ it 'returns true if the YAML file does not match the application code' do
+ allow(File).to receive(:read)
+ .with(described_class::FOSS_QUEUE_CONFIG_PATH)
+ .and_return(YAML.dump(%w[post_receive merge]))
+
+ expect(described_class.all_queues_yml_outdated?).to be(true)
+ end
+
+ it 'returns false if the YAML file matches the application code' do
+ allow(File).to receive(:read)
+ .with(described_class::FOSS_QUEUE_CONFIG_PATH)
+ .and_return(YAML.dump(%w[merge post_receive process_commit]))
+
+ expect(described_class.all_queues_yml_outdated?).to be(false)
+ end
+ end
end
diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb
index 061ebcfdc06..2762c5b5cb9 100644
--- a/spec/lib/sentry/client/issue_spec.rb
+++ b/spec/lib/sentry/client/issue_spec.rb
@@ -8,7 +8,7 @@ describe Sentry::Client::Issue do
let(:token) { 'test-token' }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
let(:client) { Sentry::Client.new(sentry_url, token) }
- let(:issue_id) { 503504 }
+ let(:issue_id) { 11 }
describe '#list_issues' do
shared_examples 'issues have correct return type' do |klass|
@@ -243,7 +243,7 @@ describe Sentry::Client::Issue do
end
it 'has a correct external URL' do
- expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/503504')
+ expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/11')
end
it 'issue has a correct external base url' do
diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb
new file mode 100644
index 00000000000..2a113e353c8
--- /dev/null
+++ b/spec/presenters/snippet_blob_presenter_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SnippetBlobPresenter do
+ describe '#highlighted_data' do
+ let(:snippet) { build(:personal_snippet) }
+
+ subject { described_class.new(snippet.blob).highlighted_data }
+
+ it 'returns nil when the snippet blob is binary' do
+ allow(snippet.blob).to receive(:binary?).and_return(true)
+
+ expect(subject).to be_nil
+ end
+
+ it 'returns markdown content when snippet file is markup' do
+ snippet.file_name = 'test.md'
+ snippet.content = '*foo*'
+
+ expect(subject).to eq '<p data-sourcepos="1:1-1:5" dir="auto"><em>foo</em></p>'
+ end
+
+ it 'returns syntax highlighted content' do
+ snippet.file_name = 'test.rb'
+ snippet.content = 'class Foo;end'
+
+ expect(subject)
+ .to eq '<span id="LC1" class="line" lang="ruby"><span class="k">class</span> <span class="nc">Foo</span><span class="p">;</span><span class="k">end</span></span>'
+ end
+
+ it 'returns plain text highlighted content' do
+ snippet.file_name = 'test'
+ snippet.content = 'foo'
+
+ expect(described_class.new(snippet.blob).highlighted_data).to eq '<span id="LC1" class="line" lang="plaintext">foo</span>'
+ end
+ end
+
+ describe '#raw_path' do
+ subject { described_class.new(snippet.blob).raw_path }
+
+ context 'with ProjectSnippet' do
+ let!(:project) { create(:project) }
+ let(:snippet) { build(:project_snippet, project: project, id: 1) }
+
+ it 'returns the raw path' do
+ expect(subject).to eq "/#{snippet.project.full_path}/snippets/1/raw"
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ let(:snippet) { build(:personal_snippet, id: 1) }
+
+ it 'returns the raw path' do
+ expect(subject).to eq "/snippets/1/raw"
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index 9ef45c0f6bc..876eff8c753 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -67,7 +67,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['snippet']['content']).to eq(content)
+ expect(mutation_response['snippet']['blob']['highlightedData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name)
@@ -92,7 +92,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['snippet']['content']).to eq(content)
+ expect(mutation_response['snippet']['blob']['highlightedData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name)
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index deaa9e8a237..f4c0b646c01 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -56,7 +56,7 @@ describe 'Updating a Snippet' do
it 'returns the updated Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['snippet']['content']).to eq(updated_content)
+ expect(mutation_response['snippet']['blob']['highlightedData']).to match(updated_content)
expect(mutation_response['snippet']['title']).to eq(updated_title)
expect(mutation_response['snippet']['description']).to eq(updated_description)
expect(mutation_response['snippet']['fileName']).to eq(updated_file_name)
@@ -77,7 +77,7 @@ describe 'Updating a Snippet' do
it 'returns the Snippet with its original values' do
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['snippet']['content']).to eq(original_content)
+ expect(mutation_response['snippet']['blob']['highlightedData']).to match(original_content)
expect(mutation_response['snippet']['title']).to eq(original_title)
expect(mutation_response['snippet']['description']).to eq(original_description)
expect(mutation_response['snippet']['fileName']).to eq(original_file_name)
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 719b374553c..e2f1ef089bf 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -5,6 +5,49 @@ require 'spec_helper'
describe SubmitUsagePingService do
include StubRequests
+ let(:score_params) do
+ {
+ score: {
+ leader_issues: 10.2,
+ instance_issues: 3.2,
+ percentage_issues: 31.37,
+
+ leader_notes: 25.3,
+ instance_notes: 23.2,
+
+ leader_milestones: 16.2,
+ instance_milestones: 5.5,
+
+ leader_boards: 5.2,
+ instance_boards: 3.2,
+
+ leader_merge_requests: 5.2,
+ instance_merge_requests: 3.2,
+
+ leader_ci_pipelines: 25.1,
+ instance_ci_pipelines: 21.3,
+
+ leader_environments: 3.3,
+ instance_environments: 2.2,
+
+ leader_deployments: 41.3,
+ instance_deployments: 15.2,
+
+ leader_projects_prometheus_active: 0.31,
+ instance_projects_prometheus_active: 0.30,
+
+ leader_service_desk_issues: 15.8,
+ instance_service_desk_issues: 15.1,
+
+ non_existing_column: 'value'
+ }
+ }
+ end
+
+ let(:with_dev_ops_score_params) { { dev_ops_score: score_params[:score] } }
+ let(:with_conv_index_params) { { conv_index: score_params[:score] } }
+ let(:without_dev_ops_score_params) { { dev_ops_score: {} } }
+
context 'when usage ping is disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
@@ -19,13 +62,25 @@ describe SubmitUsagePingService do
end
end
+ shared_examples 'saves DevOps score data from the response' do
+ it do
+ expect { subject.execute }
+ .to change { DevOpsScore::Metric.count }
+ .by(1)
+
+ expect(DevOpsScore::Metric.last.leader_issues).to eq 10.2
+ expect(DevOpsScore::Metric.last.instance_issues).to eq 3.2
+ expect(DevOpsScore::Metric.last.percentage_issues).to eq 31.37
+ end
+ end
+
context 'when usage ping is enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
end
it 'sends a POST request' do
- response = stub_response(without_conv_index_params)
+ response = stub_response(without_dev_ops_score_params)
subject.execute
@@ -33,7 +88,7 @@ describe SubmitUsagePingService do
end
it 'refreshes usage data statistics before submitting' do
- stub_response(without_conv_index_params)
+ stub_response(without_dev_ops_score_params)
expect(Gitlab::UsageData).to receive(:to_json)
.with(force_refresh: true)
@@ -42,62 +97,21 @@ describe SubmitUsagePingService do
subject.execute
end
- it 'saves DevOps Score data from the response' do
- stub_response(with_conv_index_params)
+ context 'when conv_index data is passed' do
+ before do
+ stub_response(with_conv_index_params)
+ end
- expect { subject.execute }
- .to change { DevOpsScore::Metric.count }
- .by(1)
-
- expect(DevOpsScore::Metric.last.leader_issues).to eq 10.2
- expect(DevOpsScore::Metric.last.instance_issues).to eq 3.2
- expect(DevOpsScore::Metric.last.percentage_issues).to eq 31.37
+ it_behaves_like 'saves DevOps score data from the response'
end
- end
-
- def without_conv_index_params
- {
- conv_index: {}
- }
- end
- def with_conv_index_params
- {
- conv_index: {
- leader_issues: 10.2,
- instance_issues: 3.2,
- percentage_issues: 31.37,
-
- leader_notes: 25.3,
- instance_notes: 23.2,
-
- leader_milestones: 16.2,
- instance_milestones: 5.5,
+ context 'when DevOps score data is passed' do
+ before do
+ stub_response(with_dev_ops_score_params)
+ end
- leader_boards: 5.2,
- instance_boards: 3.2,
-
- leader_merge_requests: 5.2,
- instance_merge_requests: 3.2,
-
- leader_ci_pipelines: 25.1,
- instance_ci_pipelines: 21.3,
-
- leader_environments: 3.3,
- instance_environments: 2.2,
-
- leader_deployments: 41.3,
- instance_deployments: 15.2,
-
- leader_projects_prometheus_active: 0.31,
- instance_projects_prometheus_active: 0.30,
-
- leader_service_desk_issues: 15.8,
- instance_service_desk_issues: 15.1,
-
- non_existing_column: 'value'
- }
- }
+ it_behaves_like 'saves DevOps score data from the response'
+ end
end
def stub_response(body)
diff --git a/spec/support/matchers/log_spam.rb b/spec/support/matchers/log_spam.rb
index 541cacf558c..f6aa7dbd152 100644
--- a/spec/support/matchers/log_spam.rb
+++ b/spec/support/matchers/log_spam.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# This matcher checkes if one spam log with provided attributes was created
+# This matcher checks if one spam log with provided attributes was created
#
# Example:
#
diff --git a/spec/support/shared_contexts/features/error_tracking_shared_context.rb b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
new file mode 100644
index 00000000000..230554ce7ac
--- /dev/null
+++ b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+shared_context 'sentry error tracking context feature' do
+ include ReactiveCachingHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_error_tracking_settings) { create(:project_error_tracking_setting, project: project) }
+ let_it_be(:issue_response_body) { fixture_file('sentry/issue_sample_response.json') }
+ let_it_be(:issue_response) { JSON.parse(issue_response_body) }
+ let_it_be(:event_response_body) { fixture_file('sentry/issue_latest_event_sample_response.json') }
+ let_it_be(:event_response) { JSON.parse(event_response_body) }
+ let(:sentry_api_urls) { Sentry::ApiUrls.new(project_error_tracking_settings.api_url) }
+ let(:issue_id) { issue_response['id'] }
+
+ before do
+ stub_request(:get, sentry_api_urls.issue_url(issue_id)).with(
+ headers: { 'Authorization' => 'Bearer access_token_123' }
+ ).to_return(status: 200, body: issue_response_body, headers: { 'Content-Type' => 'application/json' })
+ stub_request(:get, sentry_api_urls.issue_latest_event_url(issue_id)).with(
+ headers: { 'Authorization' => 'Bearer access_token_123' }
+ ).to_return(status: 200, body: event_response_body, headers: { 'Content-Type' => 'application/json' })
+ end
+end
diff --git a/spec/support/shared_examples/features/error_tracking_shared_example.rb b/spec/support/shared_examples/features/error_tracking_shared_example.rb
new file mode 100644
index 00000000000..4343ffe9255
--- /dev/null
+++ b/spec/support/shared_examples/features/error_tracking_shared_example.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'error tracking index page' do
+ it 'renders the error index page' do
+ within('div.js-title-container') do
+ expect(page).to have_content(project.namespace.name)
+ expect(page).to have_content(project.name)
+ end
+
+ within('div.error-list') do
+ expect(page).to have_content('Error')
+ expect(page).to have_content('Events')
+ expect(page).to have_content('Users')
+ expect(page).to have_content('Last Seen')
+ end
+ end
+
+ it 'renders the error index data' do
+ Timecop.freeze(2020, 01, 01, 12, 0, 0) do
+ within('div.error-list') do
+ expect(page).to have_content(issues_response[0]['title'])
+ expect(page).to have_content(issues_response[0]['count'].to_s)
+ expect(page).to have_content(issues_response[0]['last_seen'])
+ expect(page).to have_content('1 year ago')
+ end
+ end
+ end
+
+ context 'when error is clicked' do
+ before do
+ click_on issues_response[0]['title']
+ end
+
+ it 'loads the error page' do
+ expect(page).to have_content('Error details')
+ end
+ end
+end
+
+shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
+ it 'expands the stack trace context' do
+ within('div.stacktrace') do
+ find("div.file-holder:nth-child(#{selected_line}) svg.ic-chevron-right").click if selected_line
+
+ expanded_line = find("div.file-holder:nth-child(#{expected_line})")
+ expect(expanded_line).to have_css('svg.ic-chevron-down')
+
+ event_response['entries'][0]['data']['values'][0]['stacktrace']['frames'][-expected_line]['context'].each do |context|
+ expect(page).to have_content(context[0])
+ end
+ end
+ end
+end
+
+shared_examples 'error tracking show page' do
+ it 'renders the error details' do
+ release_short_version = issue_response['firstRelease']['shortVersion']
+
+ Timecop.freeze(2020, 01, 01, 12, 0, 0) do
+ expect(page).to have_content('1 month ago by raven.scripts.runner in main')
+ expect(page).to have_content(issue_response['metadata']['title'])
+ expect(page).to have_content('level: error')
+ expect(page).to have_content('Error details')
+ expect(page).to have_content('GitLab Issue: https://gitlab.com/gitlab-org/gitlab/issues/1')
+ expect(page).to have_content("Sentry event: https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/#{issue_id}")
+ expect(page).to have_content("First seen: 1 year ago (2018-11-06 9:19:55PM UTC) Release: #{release_short_version}")
+ expect(page).to have_content('Events: 1')
+ expect(page).to have_content('Users: 0')
+ end
+ end
+
+ it 'renders the stack trace heading' do
+ expect(page).to have_content('Stack trace')
+ end
+
+ it 'renders the stack trace' do
+ event_response['entries'][0]['data']['values'][0]['stacktrace']['frames'].each do |frame|
+ expect(frame['filename']).not_to be_nil
+ expect(page).to have_content(frame['filename'])
+ end
+ end
+
+ # The first line is expanded by default if no line is selected
+ it_behaves_like 'expanded stack trace context', selected_line: nil, expected_line: 1
+ it_behaves_like 'expanded stack trace context', selected_line: 8, expected_line: 8
+end
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 5ceb54eb2d5..f3ee1dc8435 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -3,8 +3,12 @@
require 'spec_helper'
describe 'Every Sidekiq worker' do
+ let(:workers_without_defaults) do
+ Gitlab::SidekiqConfig.workers - Gitlab::SidekiqConfig::DEFAULT_WORKERS
+ end
+
it 'does not use the default queue' do
- expect(Gitlab::SidekiqConfig.workers.map(&:queue)).not_to include('default')
+ expect(workers_without_defaults.map(&:queue)).not_to include('default')
end
it 'uses the cronjob queue when the worker runs as a cronjob' do
@@ -45,7 +49,7 @@ describe 'Every Sidekiq worker' do
# or explicitly be excluded with the `feature_category_not_owned!` annotation.
# Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do
- Gitlab::SidekiqConfig.workers.each do |worker|
+ workers_without_defaults.each do |worker|
expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
end
end
@@ -54,7 +58,7 @@ describe 'Every Sidekiq worker' do
# The category should match a value in `config/feature_categories.yml`.
# Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
it 'has a feature_category that maps to a value in feature_categories.yml', :aggregate_failures do
- workers_with_feature_categories = Gitlab::SidekiqConfig.workers
+ workers_with_feature_categories = workers_without_defaults
.select(&:get_feature_category)
.reject(&:feature_category_not_owned?)
@@ -69,7 +73,7 @@ describe 'Every Sidekiq worker' do
# rather than scaling the hardware to meet the SLO. For this reason, memory-bound,
# latency-sensitive jobs are explicitly discouraged and disabled.
it 'is (exclusively) memory-bound or latency-sentitive, not both', :aggregate_failures do
- latency_sensitive_workers = Gitlab::SidekiqConfig.workers
+ latency_sensitive_workers = workers_without_defaults
.select(&:latency_sensitive_worker?)
latency_sensitive_workers.each do |worker|
@@ -86,7 +90,7 @@ describe 'Every Sidekiq worker' do
# Please see doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies for more
# details.
it 'has (exclusively) external dependencies or is latency-sentitive, not both', :aggregate_failures do
- latency_sensitive_workers = Gitlab::SidekiqConfig.workers
+ latency_sensitive_workers = workers_without_defaults
.select(&:latency_sensitive_worker?)
latency_sensitive_workers.each do |worker|
diff --git a/yarn.lock b/yarn.lock
index a3c8f4b2297..0f608dd5099 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -655,9 +655,9 @@
semver "^5.5.0"
"@babel/standalone@^7.0.0":
- version "7.5.5"
- resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.5.5.tgz#9d3143f6078ff408db694a4254bd6f03c5c33962"
- integrity sha512-YIp5taErC4uvp4d5urJtWMui3cpvZt83x57l4oVJNvFtDzumf3pMgRmoTSpGuEzh1yzo7jHhg3mbQmMhmKPbjA==
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.8.3.tgz#0674730a8c5fbb9352de5342bf0c0c040d658380"
+ integrity sha512-WRYZUuGBYpmfUL50f2h3Cvw7s1F4wTVT5iIeT01tHo+LyB9QwrTJ6GF5J6YrtJHQqxMxt8zEl1d7I0Uhyz9NyQ==
"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
version "7.6.0"
@@ -737,10 +737,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.89.0.tgz#5bdaff1b0af1cc07ed34e89c21c34c7c6a3e1caa"
integrity sha512-vI6VobZs6mq2Bbiej5bYMHyvtn8kD1O/uHSlyY9jgJoa2TXU+jFI9DqUpJmx8EIHt+o0qm/8G3XsFGEr5gLb7Q==
-"@gitlab/ui@8.17.0":
- version "8.17.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-8.17.0.tgz#674baa9b5c05fa6ecb23b233c5b308ff82ba5660"
- integrity sha512-klWzMFU3IdoLUgRP6OTYUyO+EDfckG9/cphPKVBaf0MLx4HpjiW5LwGW3stL3A9SlyauCwAZOLkqbJKbN5pxCQ==
+"@gitlab/ui@^8.18.0":
+ version "8.18.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-8.18.0.tgz#11bd7d5fb2db10034fdf2544847dc9afd24cc02c"
+ integrity sha512-ihcXJDVUNvp8kv+ha+0d1rrRIG8IEWfDNICremTpl62V5kN9Eiwo0Csb8Gj20sBp9ERYCycjwpjvfU7dUwyAiw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
@@ -1168,6 +1168,21 @@
source-map "~0.6.1"
vue-template-es2015-compiler "^1.9.0"
+"@vue/component-compiler-utils@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.1.1.tgz#d4ef8f80292674044ad6211e336a302e4d2a6575"
+ integrity sha512-+lN3nsfJJDGMNz7fCpcoYIORrXo0K3OTsdr8jCM7FuqdI4+70TY6gxY6viJ2Xi1clqyPg7LpeOWwjF31vSMmUw==
+ dependencies:
+ consolidate "^0.15.1"
+ hash-sum "^1.0.2"
+ lru-cache "^4.1.2"
+ merge-source-map "^1.1.0"
+ postcss "^7.0.14"
+ postcss-selector-parser "^6.0.2"
+ prettier "^1.18.2"
+ source-map "~0.6.1"
+ vue-template-es2015-compiler "^1.9.0"
+
"@vue/test-utils@^1.0.0-beta.30":
version "1.0.0-beta.30"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.30.tgz#d5f26d1e2411fdb7fa7fdedb61b4b4ea4194c49d"
@@ -2072,11 +2087,16 @@ bootstrap-vue@2.0.0-rc.27:
portal-vue "^2.1.5"
vue-functional-data-merge "^3.1.0"
-bootstrap@4.3.1, bootstrap@^4.3.1:
+bootstrap@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
+bootstrap@^4.3.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.4.1.tgz#8582960eea0c5cd2bede84d8b0baf3789c3e8b01"
+ integrity sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==
+
boxen@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
@@ -2894,9 +2914,9 @@ connect@^3.6.0:
utils-merge "1.0.1"
consola@^2.3.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/consola/-/consola-2.9.0.tgz#57760e3a65a53ec27337f4add31505802d902278"
- integrity sha512-34Iue+LRcWbndFIfZc5boNizWlsrRjqIBJZTe591vImgbnq7nx2EzlrLtANj9TH2Fxm7puFJBJAOk5BhvZOddQ==
+ version "2.11.3"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-2.11.3.tgz#f7315836224c143ac5094b47fd4c816c2cd1560e"
+ integrity sha512-aoW0YIIAmeftGR8GSpw6CGQluNdkWMWh3yEFjH/hmynTYnMtibXszii3lxCXmk8YxJtI3FAK5aTiquA5VH68Gw==
console-browserify@^1.1.0:
version "1.1.0"
@@ -2979,11 +2999,11 @@ copy-descriptor@^0.1.0:
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-to-clipboard@^3.0.8:
- version "3.0.8"
- resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
- integrity sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467"
+ integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==
dependencies:
- toggle-selection "^1.0.3"
+ toggle-selection "^1.0.6"
copy-webpack-plugin@^5.0.4:
version "5.0.4"
@@ -3993,11 +4013,11 @@ ecc-jsbn@~0.1.1:
safer-buffer "^2.1.0"
echarts@^4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.1.tgz#9a8ea3b03354f86f824d97625c334cf16965ef03"
- integrity sha512-pw4xScRPsLegD/cqEcoXRKeA2SD4+s+Kyo0Na166NamOWhzNl2yI5RZ2rE97tBlAopNmhyMeBVpAeD5qb+ee1A==
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.6.0.tgz#b5a47a1046cec93ceeef954f9ee54751340558ec"
+ integrity sha512-xKkcr6v9UVOSF+PMuj7Ngt3bnzLwN1sSXWCvpvX+jYb3mePYsZnABq7wGkPac/m0nV653uGHXoHK8DCKCprdNg==
dependencies:
- zrender "4.0.7"
+ zrender "4.2.0"
editions@^1.3.3:
version "1.3.4"
@@ -5500,7 +5520,12 @@ he@^1.1.0, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-highlight.js@^9.13.1, highlight.js@~9.13.0:
+highlight.js@^9.13.1:
+ version "9.18.0"
+ resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.0.tgz#6b1763cfcd53744313bd3f31f1210f7beb962c79"
+ integrity sha512-A97kI1KAUzKoAiEoaGcf2O9YPS8nbDTCRFokaaeBhnqjQTvbAuAJrQMm21zw8s8xzaMtCQBtgbyGXLGxdxQyqQ==
+
+highlight.js@~9.13.0:
version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
@@ -6778,7 +6803,7 @@ js-base64@^2.1.8:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
-js-beautify@^1.6.12, js-beautify@^1.8.8:
+js-beautify@^1.6.12:
version "1.10.2"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.2.tgz#88c9099cd6559402b124cfab18754936f8a7b178"
integrity sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==
@@ -6789,6 +6814,17 @@ js-beautify@^1.6.12, js-beautify@^1.8.8:
mkdirp "~0.5.1"
nopt "~4.0.1"
+js-beautify@^1.8.8:
+ version "1.10.3"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.3.tgz#c73fa10cf69d3dfa52d8ed624f23c64c0a6a94c1"
+ integrity sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==
+ dependencies:
+ config-chain "^1.1.12"
+ editorconfig "^0.15.3"
+ glob "^7.1.3"
+ mkdirp "~0.5.1"
+ nopt "~4.0.1"
+
js-cookie@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526"
@@ -8800,11 +8836,16 @@ pofile@^1:
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==
-popper.js@^1.14.7, popper.js@^1.15.0:
+popper.js@^1.14.7:
version "1.15.0"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
+popper.js@^1.15.0:
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.0.tgz#2e1816bcbbaa518ea6c2e15a466f4cb9c6e2fbb3"
+ integrity sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==
+
portal-vue@^2.1.5, portal-vue@^2.1.6:
version "2.1.7"
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.7.tgz#ea08069b25b640ca08a5b86f67c612f15f4e4ad4"
@@ -9006,6 +9047,11 @@ prettier@1.18.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
+prettier@^1.18.2:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
+ integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+
pretty-format@^24.8.0:
version "24.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
@@ -11113,7 +11159,7 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
-toggle-selection@^1.0.3:
+toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
@@ -11525,9 +11571,9 @@ url-parse@^1.4.3:
requires-port "^1.0.0"
url-search-params-polyfill@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-5.0.0.tgz#09b98337c89dcf6c6a6a0bfeb096f6ba83b7526b"
- integrity sha512-+SCD22QJp4UnqPOI5UTTR0Ljuh8cHbjEf1lIiZrZ8nHTlTixqwVsVQTSfk5vrmDz7N09/Y+ka5jQr0ff35FnQQ==
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-5.1.0.tgz#f0405dcc2e921bf7f5fdf8c4e616f1e8088ef31b"
+ integrity sha512-yjFY7uw2xRf9e8Mg4ZVkZwtp8dMCC4cbBkEIZiTDpuSY2WJ9+Quw0wRhxncv32qaMQwmBQT+P847rO8PrFhhDA==
url@0.10.3:
version "0.10.3"
@@ -11713,7 +11759,18 @@ vue-jest@^4.0.0-beta.2:
source-map "^0.5.6"
ts-jest "^23.10.5"
-vue-loader@^15.4.2, vue-loader@^15.7.1:
+vue-loader@^15.4.2:
+ version "15.8.3"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.8.3.tgz#857cb9e30eb5fc25e66db48dce7e4f768602a23c"
+ integrity sha512-yFksTFbhp+lxlm92DrKdpVIWMpranXnTEuGSc0oW+Gk43M9LWaAmBTnfj5+FCdve715mTHvo78IdaXf5TbiTJg==
+ dependencies:
+ "@vue/component-compiler-utils" "^3.1.0"
+ hash-sum "^1.0.2"
+ loader-utils "^1.1.0"
+ vue-hot-reload-api "^2.3.0"
+ vue-style-loader "^4.1.0"
+
+vue-loader@^15.7.1:
version "15.7.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd"
integrity sha512-fwIKtA23Pl/rqfYP5TSGK7gkEuLhoTvRYW+TU7ER3q9GpNLt/PjG5NLv3XHRDiTg7OPM1JcckBgds+VnAc+HbA==
@@ -12308,7 +12365,7 @@ zen-observable@^0.8.0:
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199"
integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==
-zrender@4.0.7:
- version "4.0.7"
- resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.7.tgz#15ae960822f5efed410995d37e5107fe3de10e6d"
- integrity sha512-TNloHe0ums6zxbHfnaCryM61J4IWDajZwNq6dHk9vfWhhysO/OeFvvR0drBs/nbXha2YxSzfQj2FiCd6RVBe+Q==
+zrender@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.2.0.tgz#d001302e155f28de1f9fc7fcd5c254bad28471cf"
+ integrity sha512-YJ9hxt5uFincYYU3KK31+Ce+B6PJmYYK0Q9fQ6jOUAoC/VHbe4kCKAPkxKeT7jGTxrK5wYu18R0TLGqj2zbEOA==