Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-01-22 12:08:40 +00:00
parent 0012439861
commit be3e24ea3c
68 changed files with 1821 additions and 651 deletions

View File

@ -13,6 +13,7 @@
.default-before_script: .default-before_script:
before_script: before_script:
- date - date
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/'
- export GOPATH=$CI_PROJECT_DIR/.go - export GOPATH=$CI_PROJECT_DIR/.go
- mkdir -p $GOPATH - mkdir -p $GOPATH
- source scripts/utils.sh - source scripts/utils.sh

View File

@ -40,6 +40,7 @@
paths: paths:
- vendor/ruby - vendor/ruby
before_script: before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/'
- cd qa/ - cd qa/
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet - bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet
- bundle check - bundle check

View File

@ -347,6 +347,7 @@ RSpec/HaveGitlabHttpStatus:
Enabled: true Enabled: true
Include: Include:
- 'spec/support/shared_examples/**/*' - 'spec/support/shared_examples/**/*'
- 'ee/spec/support/shared_examples/**/*'
Style/MultilineWhenThen: Style/MultilineWhenThen:
Enabled: false Enabled: false

View File

@ -38,12 +38,12 @@ export default {
<icon name="comment" /> <icon name="comment" />
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div v-html="timelineContent"></div> <div ref="timelineContent" v-html="timelineContent"></div>
<div class="discussion-filter-actions mt-2"> <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') }} {{ __('Show all activity') }}
</gl-button> </gl-button>
<gl-button variant="default" @click="selectFilter(1)"> <gl-button ref="showComments" variant="default" @click="selectFilter(1)">
{{ __('Show comments only') }} {{ __('Show comments only') }}
</gl-button> </gl-button>
</div> </div>

View File

@ -12,11 +12,23 @@ export default {
<template> <template>
<div class="note-attachment"> <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" /> <img :src="attachment.url" class="note-image-attach" />
</a> </a>
<div class="attachment"> <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 }} <i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }}
</a> </a>
</div> </div>

View File

@ -63,13 +63,13 @@ export default {
<template> <template>
<div class="note-header-info"> <div class="note-header-info">
<div v-if="includeToggle" class="discussion-actions"> <div v-if="includeToggle" ref="discussionActions" class="discussion-actions">
<button <button
class="note-action-button discussion-toggle-button js-vue-toggle-button" class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button" type="button"
@click="handleToggle" @click="handleToggle"
> >
<i :class="toggleChevronClass" class="fa" aria-hidden="true"></i> <i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle thread') }} {{ __('Toggle thread') }}
</button> </button>
</div> </div>
@ -90,10 +90,11 @@ export default {
<span class="note-headline-light note-headline-meta"> <span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span> <span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt"> <template v-if="createdAt">
<span class="system-note-separator"> <span ref="actionText" class="system-note-separator">
<template v-if="actionText">{{ actionText }}</template> <template v-if="actionText">{{ actionText }}</template>
</span> </span>
<a <a
ref="noteTimestamp"
:href="noteTimestampLink" :href="noteTimestampLink"
class="note-timestamp system-note-separator" class="note-timestamp system-note-separator"
@click="updateTargetNoteHash" @click="updateTargetNoteHash"

View File

@ -1,9 +1,11 @@
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility'; import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility'; import { getLocationHash } from '~/lib/utils/url_utility';
import { scrollToElement } from '~/lib/utils/common_utils'; import { scrollToElement } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import '~/behaviors/markdown/render_gfm';
import EvidenceBlock from './evidence_block.vue'; import EvidenceBlock from './evidence_block.vue';
import ReleaseBlockAssets from './release_block_assets.vue'; import ReleaseBlockAssets from './release_block_assets.vue';
import ReleaseBlockFooter from './release_block_footer.vue'; import ReleaseBlockFooter from './release_block_footer.vue';
@ -65,7 +67,10 @@ export default {
return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones)); return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones));
}, },
}, },
mounted() { mounted() {
this.renderGFM();
const hash = getLocationHash(); const hash = getLocationHash();
if (hash && slugify(hash) === this.id) { if (hash && slugify(hash) === this.id) {
this.isHighlighted = true; this.isHighlighted = true;
@ -76,6 +81,11 @@ export default {
scrollToElement(this.$el); scrollToElement(this.$el);
} }
}, },
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
},
},
}; };
</script> </script>
<template> <template>
@ -91,7 +101,7 @@ export default {
<release-block-assets v-if="shouldRenderAssets" :assets="assets" /> <release-block-assets v-if="shouldRenderAssets" :assets="assets" />
<evidence-block v-if="hasEvidence && shouldShowEvidence" :release="release" /> <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 v-html="release.description_html"></div>
</div> </div>
</div> </div>

View File

@ -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

View File

@ -36,10 +36,6 @@ module Types
description: 'File Name of the snippet', description: 'File Name of the snippet',
null: true null: true
field :content, GraphQL::STRING_TYPE,
description: 'Content of the snippet',
null: false
field :description, GraphQL::STRING_TYPE, field :description, GraphQL::STRING_TYPE,
description: 'Description of the snippet', description: 'Description of the snippet',
null: true null: true
@ -64,6 +60,10 @@ module Types
description: 'Raw URL of the snippet', description: 'Raw URL of the snippet',
null: false null: false
field :blob, type: Types::Snippets::BlobType,
description: 'Snippet blob',
null: false
markdown_field :description_html, null: true, method: :description markdown_field :description_html, null: true, method: :description
end end
end end

View File

@ -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

View File

@ -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

View File

@ -5,8 +5,6 @@
class Epic < ApplicationRecord class Epic < ApplicationRecord
include IgnorableColumns include IgnorableColumns
ignore_column :milestone_id, remove_after: '2020-02-01', remove_with: '12.8'
def self.link_reference_pattern def self.link_reference_pattern
nil nil
end end

View File

@ -9,7 +9,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Highlight.highlight( Gitlab::Highlight.highlight(
blob.path, blob.path,
limited_blob_data(to: to), limited_blob_data(to: to),
language: blob.language_from_gitattributes, language: language,
plain: plain plain: plain
) )
end end
@ -37,4 +37,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def all_lines def all_lines
@all_lines ||= blob.data.lines @all_lines ||= blob.data.lines
end end
def language
blob.language_from_gitattributes
end
end end

View File

@ -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

View File

@ -36,10 +36,12 @@ class SubmitUsagePingService
private private
def store_metrics(response) 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!( DevOpsScore::Metric.create!(
response['conv_index'].slice(*METRICS) metrics.slice(*METRICS)
) )
end end
end end

View File

@ -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_devops:auto_devops_disable
- auto_merge:auto_merge_process - auto_merge:auto_merge_process
- chaos:chaos_cpu_spin - chaos:chaos_cpu_spin
- chaos:chaos_db_spin - chaos:chaos_db_spin
- chaos:chaos_kill - chaos:chaos_kill
- chaos:chaos_leak_mem - chaos:chaos_leak_mem
- chaos:chaos_sleep - chaos:chaos_sleep
- container_repository:cleanup_container_repository
- container_repository:delete_container_repository
- cronjob:admin_email - cronjob:admin_email
- cronjob:ci_archive_traces_cron
- cronjob:container_expiration_policy - cronjob:container_expiration_policy
- cronjob:expire_build_artifacts - cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping - cronjob:gitlab_usage_ping
- cronjob:import_export_project_cleanup - 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_removal_cron
- cronjob:pages_domain_ssl_renewal_cron - cronjob:pages_domain_ssl_renewal_cron
- cronjob:pages_domain_verification_cron
- cronjob:personal_access_tokens_expiring - cronjob:personal_access_tokens_expiring
- cronjob:pipeline_schedule - cronjob:pipeline_schedule
- cronjob:prune_old_events - cronjob:prune_old_events
- cronjob:prune_web_hook_logs
- cronjob:remove_expired_group_links - cronjob:remove_expired_group_links
- cronjob:remove_expired_members - cronjob:remove_expired_members
- cronjob:remove_unreferenced_lfs_objects - cronjob:remove_unreferenced_lfs_objects
- cronjob:repository_archive_cache - cronjob:repository_archive_cache
- cronjob:repository_check_dispatch - cronjob:repository_check_dispatch
- cronjob:requests_profiles - cronjob:requests_profiles
- cronjob:schedule_migrate_external_diffs
- cronjob:stuck_ci_jobs - cronjob:stuck_ci_jobs
- cronjob:stuck_import_jobs - cronjob:stuck_import_jobs
- cronjob:stuck_merge_jobs - cronjob:stuck_merge_jobs
- cronjob:ci_archive_traces_cron
- cronjob:trending_projects - cronjob:trending_projects
- cronjob:issue_due_scheduler - deployment:deployments_finished
- cronjob:prune_web_hook_logs - deployment:deployments_success
- cronjob:schedule_migrate_external_diffs - gcp_cluster:cluster_configure
- cronjob:namespaces_prune_aggregation_schedules
- gcp_cluster:cluster_install_app - gcp_cluster:cluster_install_app
- gcp_cluster:cluster_patch_app - gcp_cluster:cluster_patch_app
- gcp_cluster:cluster_upgrade_app
- gcp_cluster:cluster_provision
- gcp_cluster:clusters_cleanup_app
- gcp_cluster:clusters_cleanup_project_namespace
- gcp_cluster:clusters_cleanup_service_account
- 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:cluster_project_configure
- gcp_cluster:clusters_applications_wait_for_uninstall_app - gcp_cluster:cluster_provision
- gcp_cluster:clusters_applications_uninstall - gcp_cluster:cluster_upgrade_app
- gcp_cluster:clusters_cleanup_app - gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:clusters_cleanup_project_namespace - gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:clusters_cleanup_service_account
- gcp_cluster:clusters_applications_activate_service - gcp_cluster:clusters_applications_activate_service
- gcp_cluster:clusters_applications_deactivate_service - gcp_cluster:clusters_applications_deactivate_service
- gcp_cluster:clusters_applications_uninstall
- github_import_advance_stage - 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:wait_for_cluster_creation
- github_importer:github_import_import_diff_note - github_importer:github_import_import_diff_note
- github_importer:github_import_import_issue - github_importer:github_import_import_issue
- github_importer:github_import_import_note
- github_importer:github_import_import_lfs_object - github_importer:github_import_import_lfs_object
- github_importer:github_import_import_note
- github_importer:github_import_import_pull_request - github_importer:github_import_import_pull_request
- github_importer:github_import_refresh_import_jid - github_importer:github_import_refresh_import_jid
- github_importer:github_import_stage_finish_import - github_importer:github_import_stage_finish_import
- github_importer:github_import_stage_import_base_data - github_importer:github_import_stage_import_base_data
- github_importer:github_import_stage_import_issues_and_diff_notes - 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_lfs_objects
- github_importer:github_import_stage_import_notes
- github_importer:github_import_stage_import_pull_requests - github_importer:github_import_stage_import_pull_requests
- github_importer:github_import_stage_import_repository - github_importer:github_import_stage_import_repository
- hashed_storage:hashed_storage_migrator - hashed_storage:hashed_storage_migrator
- hashed_storage:hashed_storage_rollbacker
- hashed_storage:hashed_storage_project_migrate - hashed_storage:hashed_storage_project_migrate
- hashed_storage:hashed_storage_project_rollback - hashed_storage:hashed_storage_project_rollback
- hashed_storage:hashed_storage_rollbacker
- mail_scheduler:mail_scheduler_issue_due - mail_scheduler:mail_scheduler_issue_due
- mail_scheduler:mail_scheduler_notification_service - 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_background_move
- object_storage:object_storage_migrate_uploads - 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_job_cache
- pipeline_cache:expire_pipeline_cache - pipeline_cache:expire_pipeline_cache
- pipeline_creation:create_pipeline - pipeline_creation:create_pipeline
- pipeline_creation:run_pipeline_schedule - pipeline_creation:run_pipeline_schedule
- pipeline_background:archive_trace
- pipeline_background:ci_build_trace_chunk_flush
- pipeline_default:build_coverage - pipeline_default:build_coverage
- pipeline_default:build_trace_sections - pipeline_default:build_trace_sections
- pipeline_default:pipeline_metrics - pipeline_default:pipeline_metrics
@ -95,74 +95,67 @@
- pipeline_hooks:build_hooks - pipeline_hooks:build_hooks
- pipeline_hooks:pipeline_hooks - pipeline_hooks:pipeline_hooks
- pipeline_processing:build_finished - pipeline_processing:build_finished
- pipeline_processing:ci_build_prepare
- pipeline_processing:build_queue - pipeline_processing:build_queue
- pipeline_processing:build_success - 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_process
- pipeline_processing:pipeline_success - pipeline_processing:pipeline_success
- pipeline_processing:pipeline_update - pipeline_processing:pipeline_update
- pipeline_processing:stage_update - pipeline_processing:stage_update
- pipeline_processing:update_head_pipeline_for_merge_request - 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_batch
- repository_check:repository_check_clear
- repository_check:repository_check_single_repository - repository_check:repository_check_single_repository
- todos_destroyer:todos_destroyer_confidential_issue - todos_destroyer:todos_destroyer_confidential_issue
- todos_destroyer:todos_destroyer_entity_leave - todos_destroyer:todos_destroyer_entity_leave
- todos_destroyer:todos_destroyer_group_private - todos_destroyer:todos_destroyer_group_private
- todos_destroyer:todos_destroyer_project_private
- todos_destroyer:todos_destroyer_private_features - todos_destroyer:todos_destroyer_private_features
- todos_destroyer:todos_destroyer_project_private
- update_namespace_statistics:namespaces_schedule_aggregation
- update_namespace_statistics:namespaces_root_statistics - update_namespace_statistics:namespaces_root_statistics
- update_namespace_statistics:namespaces_schedule_aggregation
- 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
- authorized_projects - authorized_projects
- background_migration - background_migration
- chat_notification - chat_notification
- create_evidence
- create_gpg_signature - create_gpg_signature
- create_note_diff_file
- default
- delete_diff_files
- delete_merged_branches - delete_merged_branches
- delete_stored_files
- delete_user - delete_user
- detect_repository_languages
- email_receiver - email_receiver
- emails_on_push - emails_on_push
- error_tracking_issue_link - error_tracking_issue_link
- expire_build_instance_artifacts - expire_build_instance_artifacts
- file_hook
- git_garbage_collect - git_garbage_collect
- github_import_advance_stage
- gitlab_shell - gitlab_shell
- group_destroy - group_destroy
- group_export
- import_issues_csv
- invalid_gpg_signature_update - invalid_gpg_signature_update
- irker - irker
- mailers
- merge - merge
- merge_request_mergeability_check
- migrate_external_diffs - migrate_external_diffs
- namespaceless_project_destroy - namespaceless_project_destroy
- new_issue - new_issue
- new_merge_request - new_merge_request
- new_note - new_note
- pages - pages
- pages_domain_verification
- pages_domain_ssl_renewal - pages_domain_ssl_renewal
- file_hook - pages_domain_verification
- phabricator_import_import_tasks
- post_receive - post_receive
- process_commit - process_commit
- project_cache - project_cache
- project_daily_statistics
- project_destroy - project_destroy
- project_export - project_export
- project_service - project_service
@ -170,26 +163,16 @@
- reactive_caching - reactive_caching
- rebase - rebase
- remote_mirror_notification - remote_mirror_notification
- repository_cleanup
- repository_fork - repository_fork
- repository_import - repository_import
- repository_remove_remote - repository_remove_remote
- repository_update_remote_mirror
- self_monitoring_project_create
- self_monitoring_project_delete
- system_hook_push - system_hook_push
- update_external_pull_requests - update_external_pull_requests
- update_merge_requests - update_merge_requests
- update_project_statistics - update_project_statistics
- upload_checksum - upload_checksum
- web_hook - 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

View File

@ -0,0 +1,5 @@
---
title: Add blob and blob_viewer fields to graphql snippet type
merge_request: 22960
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove storage_version column from snippets
merge_request: 23315
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Remove milestone_id from epics
merge_request: 23282
author: Lee Tickett
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix Markdown not rendering on releases page
merge_request: 23370
author:
type: fixed

View File

@ -916,7 +916,7 @@ production: &base
# Gitaly settings # Gitaly settings
gitaly: gitaly:
# Path to the directory containing Gitaly client executables. # 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 # Default Gitaly authentication token. Can be overridden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which # be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly. # is the normal way to deploy Gitaly.

View File

@ -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

View File

@ -3843,7 +3843,6 @@ ActiveRecord::Schema.define(version: 2020_01_21_132641) do
t.string "encrypted_secret_token_iv", limit: 255 t.string "encrypted_secret_token_iv", limit: 255
t.boolean "secret", default: false, null: false t.boolean "secret", default: false, null: false
t.string "repository_storage", limit: 255, default: "default", 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 ["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 ["content"], name: "index_snippets_on_content_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["created_at"], name: "index_snippets_on_created_at" t.index ["created_at"], name: "index_snippets_on_created_at"

View File

@ -268,8 +268,9 @@ default value can be found in `/opt/gitlab/etc/gitlab-rails/env/RAILS_ENV`.
### Using negation ### Using negation
You're able to run all queues in `sidekiq_queues.yml` file on a single or You're able to run all queues in the `all_queues.yml` file (or the equivalent EE
multiple processes with exceptions using the `--negate` flag. 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, For example, say you want to run a single process for all queues,
except `process_commit` and `post_receive`: except `process_commit` and `post_receive`:

View File

@ -150,6 +150,15 @@ type BlobEdge {
node: Blob node: Blob
} }
"""
Types of blob viewers
"""
enum BlobViewersType {
auxiliary
rich
simple
}
type Commit { type Commit {
""" """
Author of the commit Author of the commit
@ -5934,9 +5943,9 @@ type Snippet implements Noteable {
author: User! author: User!
""" """
Content of the snippet Snippet blob
""" """
content: String! blob: SnippetBlob!
""" """
Timestamp this snippet was created Timestamp this snippet was created
@ -6049,6 +6058,91 @@ type Snippet implements Noteable {
webUrl: String! webUrl: String!
} }
"""
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. The connection type for Snippet.
""" """

View File

@ -6275,8 +6275,8 @@
"deprecationReason": null "deprecationReason": null
}, },
{ {
"name": "content", "name": "blob",
"description": "Content of the snippet", "description": "Snippet blob",
"args": [ "args": [
], ],
@ -6284,8 +6284,8 @@
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "OBJECT",
"name": "String", "name": "SnippetBlob",
"ofType": null "ofType": null
} }
}, },
@ -7004,6 +7004,311 @@
], ],
"possibleTypes": null "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", "kind": "ENUM",
"name": "VisibilityScopesEnum", "name": "VisibilityScopesEnum",

View File

@ -926,15 +926,44 @@ Represents a snippet entry
| `project` | Project | The project the snippet is associated with | | `project` | Project | The project the snippet is associated with |
| `author` | User! | The owner of the snippet | | `author` | User! | The owner of the snippet |
| `fileName` | String | File Name of the snippet | | `fileName` | String | File Name of the snippet |
| `content` | String! | Content of the snippet |
| `description` | String | Description of the snippet | | `description` | String | Description of the snippet |
| `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet | | `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet |
| `createdAt` | Time! | Timestamp this snippet was created | | `createdAt` | Time! | Timestamp this snippet was created |
| `updatedAt` | Time! | Timestamp this snippet was updated | | `updatedAt` | Time! | Timestamp this snippet was updated |
| `webUrl` | String! | Web URL of the snippet | | `webUrl` | String! | Web URL of the snippet |
| `rawUrl` | String! | Raw URL of the snippet | | `rawUrl` | String! | Raw URL of the snippet |
| `blob` | SnippetBlob! | Snippet blob |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `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 ## SnippetPermissions
| Name | Type | Description | | Name | Type | Description |

View File

@ -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: `ApolloMutation` component exposes `mutate` method via scoped slot. If we want to test this method, we need to add it to mocks:
```javascript ```javascript
const mutate = jest.fn(() => Promise.resolve()); const mutate = jest.fn().mockResolvedValue();
const $apollo = { const $apollo = {
mutate, mutate,
}; };

View File

@ -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 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`. 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` After adding a new queue, run `bin/rake
otherwise your worker will not run. 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 ## Queue Namespaces

View File

@ -207,7 +207,8 @@ variables:
If your project requires custom build configurations, it can be preferable to avoid 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 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 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. in the project's working directory and specified using the `artifacts:path` configuration.

View File

@ -178,7 +178,6 @@ excluded_attributes:
- :encrypted_secret_token - :encrypted_secret_token
- :encrypted_secret_token_iv - :encrypted_secret_token_iv
- :repository_storage - :repository_storage
- :storage_version
merge_request_diff: merge_request_diff:
- :external_diff - :external_diff
- :stored_externally - :stored_externally

View File

@ -4,6 +4,22 @@ require 'yaml'
module Gitlab module Gitlab
module SidekiqConfig 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 class << self
include Gitlab::SidekiqConfig::CliMethods include Gitlab::SidekiqConfig::CliMethods
@ -25,28 +41,46 @@ module Gitlab
def workers def workers
@workers ||= begin @workers ||= begin
result = find_workers(Rails.root.join('app', 'workers')) result = []
result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'))) if Gitlab.ee? 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 result
end end
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 private
def find_workers(root) def find_workers(root, ee:)
concerns = root.join('concerns').to_s concerns = root.join('concerns').to_s
workers = Dir[root.join('**', '*.rb')] Dir[root.join('**', '*.rb')]
.reject { |path| path.start_with?(concerns) } .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| def worker_from_path(path, root)
ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '') ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
ns.camelize.constantize ns.camelize.constantize
end
# Skip things that aren't workers
workers.select { |w| w < Sidekiq::Worker }
end end
end end
end end

View File

@ -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

View File

@ -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

View File

@ -34,6 +34,7 @@ unless Rails.env.production?
scss_lint scss_lint
gettext:lint gettext:lint
lint:static_verification lint:static_verification
gitlab:sidekiq:all_queues_yml:check
] ]
if Gitlab.ee? if Gitlab.ee?

View File

@ -40,7 +40,7 @@
"@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.6.2", "@babel/preset-env": "^7.6.2",
"@gitlab/svgs": "^1.89.0", "@gitlab/svgs": "^1.89.0",
"@gitlab/ui": "8.17.0", "@gitlab/ui": "^8.18.0",
"@gitlab/visual-review-tools": "1.5.1", "@gitlab/visual-review-tools": "1.5.1",
"@sentry/browser": "^5.10.2", "@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "^0.0.18", "@sourcegraph/code-host-integration": "^0.0.18",

View File

@ -3,7 +3,7 @@
FactoryBot.define do FactoryBot.define do
factory :project_error_tracking_setting, class: 'ErrorTracking::ProjectErrorTrackingSetting' do factory :project_error_tracking_setting, class: 'ErrorTracking::ProjectErrorTrackingSetting' do
project 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 } enabled { true }
token { 'access_token_123' } token { 'access_token_123' }
project_name { 'Sentry Project' } project_name { 'Sentry Project' }

View File

@ -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

View File

@ -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

View File

@ -38,7 +38,7 @@
}, },
"firstSeen": "2018-11-06T21:19:55Z", "firstSeen": "2018-11-06T21:19:55Z",
"hasSeen": false, "hasSeen": false,
"id": "503504", "id": "11",
"isBookmarked": false, "isBookmarked": false,
"isPublic": false, "isPublic": false,
"isSubscribed": true, "isSubscribed": true,
@ -72,232 +72,64 @@
"shortId": "PUMP-STATION-1", "shortId": "PUMP-STATION-1",
"stats": { "stats": {
"24h": [ "24h": [
[ [1541451600.0, 557],
1541451600.0, [1541455200.0, 473],
557 [1541458800.0, 914],
], [1541462400.0, 991],
[ [1541466000.0, 925],
1541455200.0, [1541469600.0, 881],
473 [1541473200.0, 182],
], [1541476800.0, 490],
[ [1541480400.0, 820],
1541458800.0, [1541484000.0, 322],
914 [1541487600.0, 836],
], [1541491200.0, 565],
[ [1541494800.0, 758],
1541462400.0, [1541498400.0, 880],
991 [1541502000.0, 677],
], [1541505600.0, 381],
[ [1541509200.0, 814],
1541466000.0, [1541512800.0, 329],
925 [1541516400.0, 446],
], [1541520000.0, 731],
[ [1541523600.0, 111],
1541469600.0, [1541527200.0, 926],
881 [1541530800.0, 772],
], [1541534400.0, 400],
[ [1541538000.0, 943]
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": [ "30d": [
[ [1538870400.0, 565],
1538870400.0, [1538956800.0, 12862],
565 [1539043200.0, 15617],
], [1539129600.0, 10809],
[ [1539216000.0, 15065],
1538956800.0, [1539302400.0, 12927],
12862 [1539388800.0, 12994],
], [1539475200.0, 13139],
[ [1539561600.0, 11838],
1539043200.0, [1539648000.0, 12088],
15617 [1539734400.0, 12338],
], [1539820800.0, 12768],
[ [1539907200.0, 12816],
1539129600.0, [1539993600.0, 15356],
10809 [1540080000.0, 10910],
], [1540166400.0, 12306],
[ [1540252800.0, 12912],
1539216000.0, [1540339200.0, 14700],
15065 [1540425600.0, 11890],
], [1540512000.0, 11684],
[ [1540598400.0, 13510],
1539302400.0, [1540684800.0, 12625],
12927 [1540771200.0, 12811],
], [1540857600.0, 13180],
[ [1540944000.0, 14651],
1539388800.0, [1541030400.0, 14161],
12994 [1541116800.0, 12612],
], [1541203200.0, 14316],
[ [1541289600.0, 14742],
1539475200.0, [1541376000.0, 12505],
13139 [1541462400.0, 14180]
],
[
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", "status": "unresolved",

View File

@ -18,6 +18,8 @@ exports[`grafana integration component default state to match the default snapsh
<gl-button-stub <gl-button-stub
class="js-settings-toggle" class="js-settings-toggle"
size="md"
variant="secondary"
> >
Expand Expand
</gl-button-stub> </gl-button-stub>
@ -89,6 +91,7 @@ exports[`grafana integration component default state to match the default snapsh
</gl-form-group-stub> </gl-form-group-stub>
<gl-button-stub <gl-button-stub
size="md"
variant="success" variant="success"
> >

View File

@ -12,7 +12,7 @@ describe('DateTimePicker', () => {
const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle'); const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle');
const dropdownMenu = () => dateTimePicker.find('.dropdown-menu'); 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 cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element;
const fillInputAndBlur = (input, val) => { const fillInputAndBlur = (input, val) => {
dateTimePicker.find(input).setValue(val); dateTimePicker.find(input).setValue(val);

View File

@ -1,93 +1,40 @@
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue'; import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
import eventHub from '~/notes/event_hub'; import eventHub from '~/notes/event_hub';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('DiscussionFilterNote component', () => { describe('DiscussionFilterNote component', () => {
let vm; let wrapper;
const createComponent = () => { const createComponent = () => {
const Component = Vue.extend(DiscussionFilterNote); wrapper = shallowMount(DiscussionFilterNote);
return mountComponent(Component);
}; };
beforeEach(() => { beforeEach(() => {
vm = createComponent(); createComponent();
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('computed', () => { it('timelineContent renders a string containing instruction for switching feed type', () => {
describe('timelineContent', () => { expect(wrapper.find({ ref: 'timelineContent' }).html()).toBe(
it('returns string containing instruction for switching feed type', () => { "<div>You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.</div>",
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.",
);
});
});
}); });
describe('methods', () => { it('emits `dropdownSelect` event with 0 parameter on clicking Show all activity button', () => {
describe('selectFilter', () => { jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
it('emits `dropdownSelect` event on `eventHub` with provided param', () => { wrapper.find({ ref: 'showAllActivity' }).vm.$emit('click');
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm.selectFilter(1); expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 0);
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
});
});
}); });
describe('template', () => { it('emits `dropdownSelect` event with 1 parameter on clicking Show comments only button', () => {
it('renders component container element', () => { jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true); wrapper.find({ ref: 'showComments' }).vm.$emit('click');
});
it('renders comment icon element', () => { expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
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'));
expect(vm.selectFilter).toHaveBeenCalledWith(1);
});
}); });
}); });

View File

@ -1,23 +1,45 @@
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import noteAttachment from '~/notes/components/note_attachment.vue'; import NoteAttachment from '~/notes/components/note_attachment.vue';
describe('issue note attachment', () => { describe('Issue note attachment', () => {
it('should render properly', () => { let wrapper;
const props = {
attachment: { const findImage = () => wrapper.find({ ref: 'attachmentImage' });
filename: 'dk.png', const findUrl = () => wrapper.find({ ref: 'attachmentUrl' });
image: true,
url: '/dk.png', const createComponent = attachment => {
wrapper = shallowMount(NoteAttachment, {
propsData: {
attachment,
}, },
}; });
};
const Component = Vue.extend(noteAttachment); afterEach(() => {
const vm = new Component({ wrapper.destroy();
propsData: props, wrapper = null;
}).$mount(); });
expect(vm.$el.classList.contains('note-attachment')).toBeTruthy(); it('renders attachment image if it is passed in attachment prop', () => {
expect(vm.$el.querySelector('img').src).toContain(props.attachment.url); createComponent({
expect(vm.$el.querySelector('a').href).toContain(props.attachment.url); 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);
});
it('does not render image and url if attachment object is empty', () => {
createComponent({});
expect(findImage().exists()).toBe(false);
expect(findUrl().exists()).toBe(false);
}); });
}); });

View File

@ -1,125 +1,141 @@
import Vue from 'vue'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import noteHeader from '~/notes/components/note_header.vue'; import Vuex from 'vuex';
import createStore from '~/notes/stores'; import NoteHeader from '~/notes/components/note_header.vue';
describe('note_header component', () => { const localVue = createLocalVue();
let store; localVue.use(Vuex);
let vm;
let Component;
beforeEach(() => { const actions = {
Component = Vue.extend(noteHeader); setTargetNoteHash: jest.fn(),
store = createStore(); };
});
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(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('individual note', () => { it('does not render discussion actions when includeToggle is false', () => {
beforeEach(() => { createComponent({
vm = new Component({ includeToggle: false,
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('should render user information', () => { expect(findActionsWrapper().exists()).toBe(false);
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'); describe('when includes a toggle', () => {
expect(vm.$el.querySelector('.note-header-info a').dataset.username).toEqual('root'); it('renders discussion actions', () => {
expect(vm.$el.querySelector('.note-header-info a').classList).toContain('js-user-link'); createComponent({
includeToggle: true,
});
expect(findActionsWrapper().exists()).toBe(true);
}); });
it('should render timestamp link', () => { it('emits toggleHandler event on button click', () => {
expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined(); 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 => { it('has chevron-up icon if expanded prop is true', () => {
vm.author = {}; createComponent({
Vue.nextTick() includeToggle: true,
.then(() => { expanded: true,
expect(vm.$el.querySelector('.note-header-author-name')).toBeNull(); });
})
.then(done) expect(findChevronIcon().classes()).toContain('fa-chevron-up');
.catch(done.fail); });
it('has chevron-down icon if expanded prop is false', () => {
createComponent({
includeToggle: true,
expanded: false,
});
expect(findChevronIcon().classes()).toContain('fa-chevron-down');
}); });
}); });
describe('discussion', () => { it('renders an author link if author is passed to props', () => {
beforeEach(() => { createComponent({
vm = new Component({ author: {
store, avatar_url: null,
propsData: { id: 1,
actionText: 'started a discussion', name: 'Root',
actionTextHtml: '', path: '/root',
author: { state: 'active',
avatar_url: null, username: 'root',
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('should render toggle button', () => { expect(wrapper.find('.js-user-link').exists()).toBe(true);
expect(vm.$el.querySelector('.js-vue-toggle-button')).toBeDefined(); });
});
it('emits toggle event on click', done => { it('renders deleted user text if author is not passed as a prop', () => {
jest.spyOn(vm, '$emit').mockImplementation(() => {}); createComponent();
vm.$el.querySelector('.js-vue-toggle-button').click(); expect(wrapper.text()).toContain('A deleted user');
});
Vue.nextTick(() => { it('does not render created at information if createdAt is not passed as a prop', () => {
expect(vm.$emit).toHaveBeenCalledWith('toggleHandler'); createComponent();
done();
expect(findActionText().exists()).toBe(false);
expect(findTimestamp().exists()).toBe(false);
});
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 up arrow when open', done => { it('renders correct actionText if passed', () => {
vm.expanded = true; createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
Vue.nextTick(() => { actionText: 'Test action text',
expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
'fa-chevron-up',
);
done();
}); });
expect(findActionText().text()).toBe('Test action text');
}); });
it('renders down arrow when closed', done => { it('calls an action when timestamp is clicked', () => {
vm.expanded = false; createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
Vue.nextTick(() => {
expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
'fa-chevron-down',
);
done();
}); });
findTimestamp().trigger('click');
expect(actions.setTargetNoteHash).toHaveBeenCalled();
}); });
}); });
}); });

View File

@ -39,6 +39,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
</form> </form>
<gl-button-stub <gl-button-stub
size="md"
variant="secondary" variant="secondary"
> >
Cancel Cancel
@ -46,6 +47,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
<gl-button-stub <gl-button-stub
disabled="true" disabled="true"
size="md"
variant="warning" variant="warning"
> >
@ -55,6 +57,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
<gl-button-stub <gl-button-stub
disabled="true" disabled="true"
size="md"
variant="danger" variant="danger"
> >
action action

View File

@ -84,7 +84,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append" class="input-group-append"
> >
<button <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" data-clipboard-text="docker login host"
title="Copy login command" title="Copy login command"
type="button" type="button"
@ -122,7 +122,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append" class="input-group-append"
> >
<button <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 ." data-clipboard-text="docker build -t url ."
title="Copy build command" title="Copy build command"
type="button" type="button"
@ -152,7 +152,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append" class="input-group-append"
> >
<button <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" data-clipboard-text="docker push url"
title="Copy push command" title="Copy push command"
type="button" type="button"

View File

@ -159,7 +159,9 @@ exports[`Settings Form renders 1`] = `
> >
<glbutton-stub <glbutton-stub
class="mr-2 d-block" class="mr-2 d-block"
size="md"
type="reset" type="reset"
variant="secondary"
> >
Cancel Cancel
@ -168,6 +170,7 @@ exports[`Settings Form renders 1`] = `
<glbutton-stub <glbutton-stub
class="d-flex justify-content-center align-items-center js-no-auto-disable" class="d-flex justify-content-center align-items-center js-no-auto-disable"
size="md"
type="submit" type="submit"
variant="success" variant="success"
> >

View File

@ -1,3 +1,4 @@
import $ from 'jquery';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { first } from 'underscore'; import { first } from 'underscore';
import EvidenceBlock from '~/releases/list/components/evidence_block.vue'; import EvidenceBlock from '~/releases/list/components/evidence_block.vue';
@ -43,6 +44,7 @@ describe('Release block', () => {
const editButton = () => wrapper.find('.js-edit-button'); const editButton = () => wrapper.find('.js-edit-button');
beforeEach(() => { beforeEach(() => {
jest.spyOn($.fn, 'renderGFM');
releaseClone = JSON.parse(JSON.stringify(release)); releaseClone = JSON.parse(JSON.stringify(release));
}); });
@ -66,6 +68,11 @@ describe('Release block', () => {
expect(wrapper.text()).toContain(release.name); 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', () => { it('renders release date', () => {
expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at)); expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at));
}); });

View File

@ -17,6 +17,8 @@ exports[`self monitor component When the self monitor project has not been creat
<gl-button-stub <gl-button-stub
class="js-settings-toggle" class="js-settings-toggle"
size="md"
variant="secondary"
> >
Expand Expand
</gl-button-stub> </gl-button-stub>

View File

@ -1,14 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Expand button on click when short text is provided renders button after text 1`] = ` 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\\"> <span>
<use xlink:href=\\"#ellipsis_h\\"></use> <button
</svg></button></span>" 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`] = ` 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> <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\\"> <button
<use xlink:href=\\"#ellipsis_h\\"></use> aria-label="Click to expand text"
</svg></button></span>" 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>
`; `;

View File

@ -71,7 +71,7 @@ describe('Expand button', () => {
it('renders button before text', () => { it('renders button before text', () => {
expect(expanderPrependEl().isVisible()).toBe(true); expect(expanderPrependEl().isVisible()).toBe(true);
expect(expanderAppendEl().isVisible()).toBe(false); 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', () => { it('renders button after text', () => {
expect(expanderPrependEl().isVisible()).toBe(false); expect(expanderPrependEl().isVisible()).toBe(false);
expect(expanderAppendEl().isVisible()).toBe(true); expect(expanderAppendEl().isVisible()).toBe(true);
expect(wrapper.find(ExpandButton).html()).toMatchSnapshot(); expect(wrapper.find(ExpandButton).element).toMatchSnapshot();
}); });
}); });
}); });

View File

@ -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

View File

@ -5,10 +5,10 @@ require 'spec_helper'
describe GitlabSchema.types['Snippet'] do describe GitlabSchema.types['Snippet'] do
it 'has the correct fields' do it 'has the correct fields' do
expected_fields = [:id, :title, :project, :author, expected_fields = [:id, :title, :project, :author,
:file_name, :content, :description, :file_name, :description,
:visibility_level, :created_at, :updated_at, :visibility_level, :created_at, :updated_at,
:web_url, :raw_url, :notes, :discussions, :web_url, :raw_url, :notes, :discussions,
:user_permissions, :description_html] :user_permissions, :description_html, :blob]
is_expected.to have_graphql_fields(*expected_fields) is_expected.to have_graphql_fields(*expected_fields)
end end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -5,10 +5,10 @@ require 'spec_helper'
describe Gitlab::SidekiqConfig do describe Gitlab::SidekiqConfig do
describe '.workers' do describe '.workers' do
it 'includes all workers' do it 'includes all workers' do
workers = described_class.workers worker_classes = described_class.workers.map(&:klass)
expect(workers).to include(PostReceive) expect(worker_classes).to include(PostReceive)
expect(workers).to include(MergeWorker) expect(worker_classes).to include(MergeWorker)
end end
end end
@ -44,4 +44,40 @@ describe Gitlab::SidekiqConfig do
expect(queues).to include('unknown') expect(queues).to include('unknown')
end end
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 end

View File

@ -8,7 +8,7 @@ describe Sentry::Client::Issue do
let(:token) { 'test-token' } let(:token) { 'test-token' }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' } let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
let(:client) { Sentry::Client.new(sentry_url, token) } let(:client) { Sentry::Client.new(sentry_url, token) }
let(:issue_id) { 503504 } let(:issue_id) { 11 }
describe '#list_issues' do describe '#list_issues' do
shared_examples 'issues have correct return type' do |klass| shared_examples 'issues have correct return type' do |klass|
@ -243,7 +243,7 @@ describe Sentry::Client::Issue do
end end
it 'has a correct external URL' do 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 end
it 'issue has a correct external base url' do it 'issue has a correct external base url' do

View File

@ -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

View File

@ -67,7 +67,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user) 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']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description) expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name) expect(mutation_response['snippet']['fileName']).to eq(file_name)
@ -92,7 +92,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user) 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']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description) expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name) expect(mutation_response['snippet']['fileName']).to eq(file_name)

View File

@ -56,7 +56,7 @@ describe 'Updating a Snippet' do
it 'returns the updated Snippet' do it 'returns the updated Snippet' do
post_graphql_mutation(mutation, current_user: current_user) 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']['title']).to eq(updated_title)
expect(mutation_response['snippet']['description']).to eq(updated_description) expect(mutation_response['snippet']['description']).to eq(updated_description)
expect(mutation_response['snippet']['fileName']).to eq(updated_file_name) 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 it 'returns the Snippet with its original values' do
post_graphql_mutation(mutation, current_user: current_user) 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']['title']).to eq(original_title)
expect(mutation_response['snippet']['description']).to eq(original_description) expect(mutation_response['snippet']['description']).to eq(original_description)
expect(mutation_response['snippet']['fileName']).to eq(original_file_name) expect(mutation_response['snippet']['fileName']).to eq(original_file_name)

View File

@ -5,65 +5,9 @@ require 'spec_helper'
describe SubmitUsagePingService do describe SubmitUsagePingService do
include StubRequests include StubRequests
context 'when usage ping is disabled' do let(:score_params) do
before do
stub_application_setting(usage_ping_enabled: false)
end
it 'does not run' do
expect(HTTParty).not_to receive(:post)
result = subject.execute
expect(result).to eq false
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)
subject.execute
expect(response).to have_been_requested
end
it 'refreshes usage data statistics before submitting' do
stub_response(without_conv_index_params)
expect(Gitlab::UsageData).to receive(:to_json)
.with(force_refresh: true)
.and_call_original
subject.execute
end
it 'saves DevOps Score data from the response' do
stub_response(with_conv_index_params)
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
def without_conv_index_params
{ {
conv_index: {} score: {
}
end
def with_conv_index_params
{
conv_index: {
leader_issues: 10.2, leader_issues: 10.2,
instance_issues: 3.2, instance_issues: 3.2,
percentage_issues: 31.37, percentage_issues: 31.37,
@ -100,6 +44,76 @@ describe SubmitUsagePingService do
} }
end 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)
end
it 'does not run' do
expect(HTTParty).not_to receive(:post)
result = subject.execute
expect(result).to eq false
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_dev_ops_score_params)
subject.execute
expect(response).to have_been_requested
end
it 'refreshes usage data statistics before submitting' do
stub_response(without_dev_ops_score_params)
expect(Gitlab::UsageData).to receive(:to_json)
.with(force_refresh: true)
.and_call_original
subject.execute
end
context 'when conv_index data is passed' do
before do
stub_response(with_conv_index_params)
end
it_behaves_like 'saves DevOps score data from the response'
end
context 'when DevOps score data is passed' do
before do
stub_response(with_dev_ops_score_params)
end
it_behaves_like 'saves DevOps score data from the response'
end
end
def stub_response(body) def stub_response(body)
stub_full_request('https://version.gitlab.com/usage_data', method: :post) stub_full_request('https://version.gitlab.com/usage_data', method: :post)
.to_return( .to_return(

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # 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: # Example:
# #

View File

@ -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

View File

@ -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

View File

@ -3,8 +3,12 @@
require 'spec_helper' require 'spec_helper'
describe 'Every Sidekiq worker' do 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 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 end
it 'uses the cronjob queue when the worker runs as a cronjob' do 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. # or explicitly be excluded with the `feature_category_not_owned!` annotation.
# Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details. # 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 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!" expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
end end
end end
@ -54,7 +58,7 @@ describe 'Every Sidekiq worker' do
# The category should match a value in `config/feature_categories.yml`. # The category should match a value in `config/feature_categories.yml`.
# Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details. # 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 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) .select(&:get_feature_category)
.reject(&:feature_category_not_owned?) .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, # rather than scaling the hardware to meet the SLO. For this reason, memory-bound,
# latency-sensitive jobs are explicitly discouraged and disabled. # latency-sensitive jobs are explicitly discouraged and disabled.
it 'is (exclusively) memory-bound or latency-sentitive, not both', :aggregate_failures do 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?) .select(&:latency_sensitive_worker?)
latency_sensitive_workers.each do |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 # Please see doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies for more
# details. # details.
it 'has (exclusively) external dependencies or is latency-sentitive, not both', :aggregate_failures do 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?) .select(&:latency_sensitive_worker?)
latency_sensitive_workers.each do |worker| latency_sensitive_workers.each do |worker|

119
yarn.lock
View File

@ -655,9 +655,9 @@
semver "^5.5.0" semver "^5.5.0"
"@babel/standalone@^7.0.0": "@babel/standalone@^7.0.0":
version "7.5.5" version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.5.5.tgz#9d3143f6078ff408db694a4254bd6f03c5c33962" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.8.3.tgz#0674730a8c5fbb9352de5342bf0c0c040d658380"
integrity sha512-YIp5taErC4uvp4d5urJtWMui3cpvZt83x57l4oVJNvFtDzumf3pMgRmoTSpGuEzh1yzo7jHhg3mbQmMhmKPbjA== integrity sha512-WRYZUuGBYpmfUL50f2h3Cvw7s1F4wTVT5iIeT01tHo+LyB9QwrTJ6GF5J6YrtJHQqxMxt8zEl1d7I0Uhyz9NyQ==
"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
version "7.6.0" version "7.6.0"
@ -737,10 +737,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.89.0.tgz#5bdaff1b0af1cc07ed34e89c21c34c7c6a3e1caa" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.89.0.tgz#5bdaff1b0af1cc07ed34e89c21c34c7c6a3e1caa"
integrity sha512-vI6VobZs6mq2Bbiej5bYMHyvtn8kD1O/uHSlyY9jgJoa2TXU+jFI9DqUpJmx8EIHt+o0qm/8G3XsFGEr5gLb7Q== integrity sha512-vI6VobZs6mq2Bbiej5bYMHyvtn8kD1O/uHSlyY9jgJoa2TXU+jFI9DqUpJmx8EIHt+o0qm/8G3XsFGEr5gLb7Q==
"@gitlab/ui@8.17.0": "@gitlab/ui@^8.18.0":
version "8.17.0" version "8.18.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-8.17.0.tgz#674baa9b5c05fa6ecb23b233c5b308ff82ba5660" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-8.18.0.tgz#11bd7d5fb2db10034fdf2544847dc9afd24cc02c"
integrity sha512-klWzMFU3IdoLUgRP6OTYUyO+EDfckG9/cphPKVBaf0MLx4HpjiW5LwGW3stL3A9SlyauCwAZOLkqbJKbN5pxCQ== integrity sha512-ihcXJDVUNvp8kv+ha+0d1rrRIG8IEWfDNICremTpl62V5kN9Eiwo0Csb8Gj20sBp9ERYCycjwpjvfU7dUwyAiw==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"
@ -1168,6 +1168,21 @@
source-map "~0.6.1" source-map "~0.6.1"
vue-template-es2015-compiler "^1.9.0" 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": "@vue/test-utils@^1.0.0-beta.30":
version "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" 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" portal-vue "^2.1.5"
vue-functional-data-merge "^3.1.0" vue-functional-data-merge "^3.1.0"
bootstrap@4.3.1, bootstrap@^4.3.1: bootstrap@4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag== 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: boxen@^1.2.1:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" 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" utils-merge "1.0.1"
consola@^2.3.0: consola@^2.3.0:
version "2.9.0" version "2.11.3"
resolved "https://registry.yarnpkg.com/consola/-/consola-2.9.0.tgz#57760e3a65a53ec27337f4add31505802d902278" resolved "https://registry.yarnpkg.com/consola/-/consola-2.11.3.tgz#f7315836224c143ac5094b47fd4c816c2cd1560e"
integrity sha512-34Iue+LRcWbndFIfZc5boNizWlsrRjqIBJZTe591vImgbnq7nx2EzlrLtANj9TH2Fxm7puFJBJAOk5BhvZOddQ== integrity sha512-aoW0YIIAmeftGR8GSpw6CGQluNdkWMWh3yEFjH/hmynTYnMtibXszii3lxCXmk8YxJtI3FAK5aTiquA5VH68Gw==
console-browserify@^1.1.0: console-browserify@^1.1.0:
version "1.1.0" version "1.1.0"
@ -2979,11 +2999,11 @@ copy-descriptor@^0.1.0:
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-to-clipboard@^3.0.8: copy-to-clipboard@^3.0.8:
version "3.0.8" version "3.2.0"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467"
integrity sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw== integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==
dependencies: dependencies:
toggle-selection "^1.0.3" toggle-selection "^1.0.6"
copy-webpack-plugin@^5.0.4: copy-webpack-plugin@^5.0.4:
version "5.0.4" version "5.0.4"
@ -3993,11 +4013,11 @@ ecc-jsbn@~0.1.1:
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
echarts@^4.2.1: echarts@^4.2.1:
version "4.2.1" version "4.6.0"
resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.1.tgz#9a8ea3b03354f86f824d97625c334cf16965ef03" resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.6.0.tgz#b5a47a1046cec93ceeef954f9ee54751340558ec"
integrity sha512-pw4xScRPsLegD/cqEcoXRKeA2SD4+s+Kyo0Na166NamOWhzNl2yI5RZ2rE97tBlAopNmhyMeBVpAeD5qb+ee1A== integrity sha512-xKkcr6v9UVOSF+PMuj7Ngt3bnzLwN1sSXWCvpvX+jYb3mePYsZnABq7wGkPac/m0nV653uGHXoHK8DCKCprdNg==
dependencies: dependencies:
zrender "4.0.7" zrender "4.2.0"
editions@^1.3.3: editions@^1.3.3:
version "1.3.4" 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" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 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" version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A== 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" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
js-beautify@^1.6.12, js-beautify@^1.8.8: js-beautify@^1.6.12:
version "1.10.2" version "1.10.2"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.2.tgz#88c9099cd6559402b124cfab18754936f8a7b178" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.2.tgz#88c9099cd6559402b124cfab18754936f8a7b178"
integrity sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ== 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" mkdirp "~0.5.1"
nopt "~4.0.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: js-cookie@^2.1.3:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526" 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" resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg== 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" version "1.15.0"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA== 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: portal-vue@^2.1.5, portal-vue@^2.1.6:
version "2.1.7" version "2.1.7"
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.7.tgz#ea08069b25b640ca08a5b86f67c612f15f4e4ad4" 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" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== 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: pretty-format@^24.8.0:
version "24.8.0" version "24.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" 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" regex-not "^1.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
toggle-selection@^1.0.3: toggle-selection@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
@ -11525,9 +11571,9 @@ url-parse@^1.4.3:
requires-port "^1.0.0" requires-port "^1.0.0"
url-search-params-polyfill@^5.0.0: url-search-params-polyfill@^5.0.0:
version "5.0.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-5.0.0.tgz#09b98337c89dcf6c6a6a0bfeb096f6ba83b7526b" resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-5.1.0.tgz#f0405dcc2e921bf7f5fdf8c4e616f1e8088ef31b"
integrity sha512-+SCD22QJp4UnqPOI5UTTR0Ljuh8cHbjEf1lIiZrZ8nHTlTixqwVsVQTSfk5vrmDz7N09/Y+ka5jQr0ff35FnQQ== integrity sha512-yjFY7uw2xRf9e8Mg4ZVkZwtp8dMCC4cbBkEIZiTDpuSY2WJ9+Quw0wRhxncv32qaMQwmBQT+P847rO8PrFhhDA==
url@0.10.3: url@0.10.3:
version "0.10.3" version "0.10.3"
@ -11713,7 +11759,18 @@ vue-jest@^4.0.0-beta.2:
source-map "^0.5.6" source-map "^0.5.6"
ts-jest "^23.10.5" 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" version "15.7.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd"
integrity sha512-fwIKtA23Pl/rqfYP5TSGK7gkEuLhoTvRYW+TU7ER3q9GpNLt/PjG5NLv3XHRDiTg7OPM1JcckBgds+VnAc+HbA== 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" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199"
integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ== integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==
zrender@4.0.7: zrender@4.2.0:
version "4.0.7" version "4.2.0"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.7.tgz#15ae960822f5efed410995d37e5107fe3de10e6d" resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.2.0.tgz#d001302e155f28de1f9fc7fcd5c254bad28471cf"
integrity sha512-TNloHe0ums6zxbHfnaCryM61J4IWDajZwNq6dHk9vfWhhysO/OeFvvR0drBs/nbXha2YxSzfQj2FiCd6RVBe+Q== integrity sha512-YJ9hxt5uFincYYU3KK31+Ce+B6PJmYYK0Q9fQ6jOUAoC/VHbe4kCKAPkxKeT7jGTxrK5wYu18R0TLGqj2zbEOA==