Commit be3e24ea authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 00124398
......@@ -13,6 +13,7 @@
.default-before_script:
before_script:
- date
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/'
- export GOPATH=$CI_PROJECT_DIR/.go
- mkdir -p $GOPATH
- source scripts/utils.sh
......
......@@ -40,6 +40,7 @@
paths:
- vendor/ruby
before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/'
- cd qa/
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet
- bundle check
......
......@@ -347,6 +347,7 @@ RSpec/HaveGitlabHttpStatus:
Enabled: true
Include:
- 'spec/support/shared_examples/**/*'
- 'ee/spec/support/shared_examples/**/*'
Style/MultilineWhenThen:
Enabled: false
......
......@@ -38,12 +38,12 @@ export default {
<icon name="comment" />
</div>
<div class="timeline-content">
<div v-html="timelineContent"></div>
<div ref="timelineContent" v-html="timelineContent"></div>
<div class="discussion-filter-actions mt-2">
<gl-button variant="default" @click="selectFilter(0)">
<gl-button ref="showAllActivity" variant="default" @click="selectFilter(0)">
{{ __('Show all activity') }}
</gl-button>
<gl-button variant="default" @click="selectFilter(1)">
<gl-button ref="showComments" variant="default" @click="selectFilter(1)">
{{ __('Show comments only') }}
</gl-button>
</div>
......
......@@ -12,11 +12,23 @@ export default {
<template>
<div class="note-attachment">
<a v-if="attachment.image" :href="attachment.url" target="_blank" rel="noopener noreferrer">
<a
v-if="attachment.image"
ref="attachmentImage"
:href="attachment.url"
target="_blank"
rel="noopener noreferrer"
>
<img :src="attachment.url" class="note-image-attach" />
</a>
<div class="attachment">
<a v-if="attachment.url" :href="attachment.url" target="_blank" rel="noopener noreferrer">
<a
v-if="attachment.url"
ref="attachmentUrl"
:href="attachment.url"
target="_blank"
rel="noopener noreferrer"
>
<i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }}
</a>
</div>
......
......@@ -63,13 +63,13 @@ export default {
<template>
<div class="note-header-info">
<div v-if="includeToggle" class="discussion-actions">
<div v-if="includeToggle" ref="discussionActions" class="discussion-actions">
<button
class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button"
@click="handleToggle"
>
<i :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
<i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle thread') }}
</button>
</div>
......@@ -90,10 +90,11 @@ export default {
<span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span class="system-note-separator">
<span ref="actionText" class="system-note-separator">
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
ref="noteTimestamp"
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
@click="updateTargetNoteHash"
......
<script>
import _ from 'underscore';
import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility';
import { scrollToElement } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import '~/behaviors/markdown/render_gfm';
import EvidenceBlock from './evidence_block.vue';
import ReleaseBlockAssets from './release_block_assets.vue';
import ReleaseBlockFooter from './release_block_footer.vue';
......@@ -65,7 +67,10 @@ export default {
return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones));
},
},
mounted() {
this.renderGFM();
const hash = getLocationHash();
if (hash && slugify(hash) === this.id) {
this.isHighlighted = true;
......@@ -76,6 +81,11 @@ export default {
scrollToElement(this.$el);
}
},
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
},
},
};
</script>
<template>
......@@ -91,7 +101,7 @@ export default {
<release-block-assets v-if="shouldRenderAssets" :assets="assets" />
<evidence-block v-if="hasEvidence && shouldShowEvidence" :release="release" />
<div class="card-text prepend-top-default">
<div ref="gfm-content" class="card-text prepend-top-default">
<div v-html="release.description_html"></div>
</div>
</div>
......
# 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
......@@ -36,10 +36,6 @@ module Types
description: 'File Name of the snippet',
null: true
field :content, GraphQL::STRING_TYPE,
description: 'Content of the snippet',
null: false
field :description, GraphQL::STRING_TYPE,
description: 'Description of the snippet',
null: true
......@@ -64,6 +60,10 @@ module Types
description: 'Raw URL of the snippet',
null: false
field :blob, type: Types::Snippets::BlobType,
description: 'Snippet blob',
null: false
markdown_field :description_html, null: true, method: :description
end
end
# 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
# 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
......@@ -5,8 +5,6 @@
class Epic < ApplicationRecord
include IgnorableColumns
ignore_column :milestone_id, remove_after: '2020-02-01', remove_with: '12.8'
def self.link_reference_pattern
nil
end
......
......@@ -9,7 +9,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Highlight.highlight(
blob.path,
limited_blob_data(to: to),
language: blob.language_from_gitattributes,
language: language,
plain: plain
)
end
......@@ -37,4 +37,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def all_lines
@all_lines ||= blob.data.lines
end
def language
blob.language_from_gitattributes
end
end
# 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
......@@ -36,10 +36,12 @@ class SubmitUsagePingService
private
def store_metrics(response)
return unless response['conv_index'].present?
metrics = response['conv_index'] || response['dev_ops_score']
return unless metrics.present?
DevOpsScore::Metric.create!(
response['conv_index'].slice(*METRICS)
metrics.slice(*METRICS)
)
end
end
# This file is generated automatically by
# bin/rake gitlab:sidekiq:all_queues_yml:generate
#
# Do not edit it manually!
---
- auto_devops:auto_devops_disable
- auto_merge:auto_merge_process
- chaos:chaos_cpu_spin
- chaos:chaos_db_spin
- chaos:chaos_kill
- chaos:chaos_leak_mem
- chaos:chaos_sleep
- container_repository:cleanup_container_repository
- container_repository:delete_container_repository
- cronjob:admin_email
- cronjob:ci_archive_traces_cron
- cronjob:container_expiration_policy
- cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping
- cronjob:import_export_project_cleanup
- cronjob:pages_domain_verification_cron
- cronjob:issue_due_scheduler
- cronjob:namespaces_prune_aggregation_schedules
- cronjob:pages_domain_removal_cron
- cronjob:pages_domain_ssl_renewal_cron
- cronjob:pages_domain_verification_cron
- cronjob:personal_access_tokens_expiring
- cronjob:pipeline_schedule
- cronjob:prune_old_events
- cronjob:prune_web_hook_logs
- cronjob:remove_expired_group_links
- cronjob:remove_expired_members
- cronjob:remove_unreferenced_lfs_objects
- cronjob:repository_archive_cache
- cronjob:repository_check_dispatch
- cronjob:requests_profiles
- cronjob:schedule_migrate_external_diffs
- cronjob:stuck_ci_jobs
- cronjob:stuck_import_jobs
- cronjob:stuck_merge_jobs
- cronjob:ci_archive_traces_cron
- cronjob:trending_projects
- cronjob:issue_due_scheduler
- cronjob:prune_web_hook_logs
- cronjob:schedule_migrate_external_diffs
- cronjob:namespaces_prune_aggregation_schedules
- deployment:deployments_finished
- deployment:deployments_success
- gcp_cluster:cluster_configure
- gcp_cluster:cluster_install_app
- gcp_cluster:cluster_patch_app
- gcp_cluster:cluster_upgrade_app
- gcp_cluster:cluster_project_configure
- gcp_cluster:cluster_provision
- gcp_cluster:clusters_cleanup_app
- gcp_cluster:clusters_cleanup_project_namespace
- gcp_cluster:clusters_cleanup_service_account
- gcp_cluster:cluster_upgrade_app
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:cluster_configure
- gcp_cluster:cluster_project_configure
- gcp_cluster:clusters_applications_wait_for_uninstall_app
- gcp_cluster:clusters_applications_activate_service
- gcp_cluster:clusters_applications_deactivate_service
- gcp_cluster:clusters_applications_uninstall
- gcp_cluster:clusters_applications_wait_for_uninstall_app
- gcp_cluster:clusters_cleanup_app
- gcp_cluster:clusters_cleanup_project_namespace
- gcp_cluster:clusters_cleanup_service_account
- gcp_cluster:clusters_applications_activate_service
- gcp_cluster:clusters_applications_deactivate_service
- github_import_advance_stage
- gcp_cluster:wait_for_cluster_creation
- github_importer:github_import_import_diff_note
- github_importer:github_import_import_issue
- github_importer:github_import_import_note
- github_importer:github_import_import_lfs_object
- github_importer:github_import_import_note
- github_importer:github_import_import_pull_request
- github_importer:github_import_refresh_import_jid
- github_importer:github_import_stage_finish_import
- github_importer:github_import_stage_import_base_data
- github_importer:github_import_stage_import_issues_and_diff_notes
- github_importer:github_import_stage_import_notes
- github_importer:github_import_stage_import_lfs_objects
- github_importer:github_import_stage_import_notes
- github_importer:github_import_stage_import_pull_requests
- github_importer:github_import_stage_import_repository
- hashed_storage:hashed_storage_migrator
- hashed_storage:hashed_storage_rollbacker
- hashed_storage:hashed_storage_project_migrate
- hashed_storage:hashed_storage_project_rollback
- hashed_storage:hashed_storage_rollbacker
- mail_scheduler:mail_scheduler_issue_due
- mail_scheduler:mail_scheduler_notification_service
- notifications:new_release
- object_pool:object_pool_create
- object_pool:object_pool_destroy
- object_pool:object_pool_join
- object_pool:object_pool_schedule_join
- object_storage:object_storage_background_move
- object_storage:object_storage_migrate_uploads
- pipeline_background:archive_trace
- pipeline_background:ci_build_trace_chunk_flush
- pipeline_cache:expire_job_cache
- pipeline_cache:expire_pipeline_cache
- pipeline_creation:create_pipeline
- pipeline_creation:run_pipeline_schedule
- pipeline_background:archive_trace
- pipeline_background:ci_build_trace_chunk_flush
- pipeline_default:build_coverage
- pipeline_default:build_trace_sections
- pipeline_default:pipeline_metrics
......@@ -95,74 +95,67 @@
- pipeline_hooks:build_hooks
- pipeline_hooks:pipeline_hooks
- pipeline_processing:build_finished
- pipeline_processing:ci_build_prepare
- pipeline_processing:build_queue
- pipeline_processing:build_success
- pipeline_processing:ci_build_prepare
- pipeline_processing:ci_build_schedule
- pipeline_processing:ci_resource_groups_assign_resource_from_resource_group
- pipeline_processing:pipeline_process
- pipeline_processing:pipeline_success
- pipeline_processing:pipeline_update
- pipeline_processing:stage_update
- pipeline_processing:update_head_pipeline_for_merge_request
- pipeline_processing:ci_build_schedule
- pipeline_processing:ci_resource_groups_assign_resource_from_resource_group
- deployment:deployments_success
- deployment:deployments_finished
- repository_check:repository_check_clear
- repository_check:repository_check_batch
- repository_check:repository_check_clear
- repository_check:repository_check_single_repository
- todos_destroyer:todos_destroyer_confidential_issue
- todos_destroyer:todos_destroyer_entity_leave
- todos_destroyer:todos_destroyer_group_private
- todos_destroyer:todos_destroyer_project_private
- todos_destroyer:todos_destroyer_private_features
- update_namespace_statistics:namespaces_schedule_aggregation
- todos_destroyer:todos_destroyer_project_private
- update_namespace_statistics:namespaces_root_statistics
- object_pool:object_pool_create
- object_pool:object_pool_schedule_join
- object_pool:object_pool_join
- object_pool:object_pool_destroy
- container_repository:delete_container_repository
- container_repository:cleanup_container_repository
- notifications:new_release
- default
- mailers # ActionMailer::DeliveryJob.queue_name
- update_namespace_statistics:namespaces_schedule_aggregation
- authorized_projects
- background_migration
- chat_notification
- create_evidence
- create_gpg_signature
- create_note_diff_file
- default
- delete_diff_files
- delete_merged_branches
- delete_stored_files
- delete_user
- detect_repository_languages
- email_receiver
- emails_on_push
- error_tracking_issue_link
- expire_build_instance_artifacts
- file_hook
- git_garbage_collect
- github_import_advance_stage
- gitlab_shell
- group_destroy
- group_export
- import_issues_csv
- invalid_gpg_signature_update
- irker
- mailers
- merge
- merge_request_mergeability_check
- migrate_external_diffs
- namespaceless_project_destroy
- new_issue
- new_merge_request
- new_note
- pages
- pages_domain_verification
- pages_domain_ssl_renewal
- file_hook
- pages_domain_verification
- phabricator_import_import_tasks
- post_receive
- process_commit
- project_cache
- project_daily_statistics
- project_destroy
- project_export
- project_service
......@@ -170,26 +163,16 @@
- reactive_caching
- rebase
- remote_mirror_notification
- repository_cleanup
- repository_fork
- repository_import
- repository_remove_remote
- repository_update_remote_mirror
- self_monitoring_project_create
- self_monitoring_project_delete
- system_hook_push
- update_external_pull_requests
- update_merge_requests
- update_project_statistics
- upload_checksum
- web_hook
- repository_update_remote_mirror
- create_note_diff_file
- delete_diff_files
- detect_repository_languages
- repository_cleanup
- delete_stored_files
- import_issues_csv
- project_daily_statistics
- create_evidence
- group_export
- self_monitoring_project_create
- self_monitoring_project_delete
- merge_request_mergeability_check
- phabricator_import_import_tasks
---
title: Add blob and blob_viewer fields to graphql snippet type
merge_request: 22960
author:
type: changed
---
title: Remove storage_version column from snippets
merge_request: 23315
author:
type: other
---
title: Remove milestone_id from epics
merge_request: 23282
author: Lee Tickett
type: other
---
title: Fix Markdown not rendering on releases page
merge_request: 23370
author:
type: fixed
......@@ -916,7 +916,7 @@ production: &base
# Gitaly settings
gitaly:
# Path to the directory containing Gitaly client executables.
client_path: /home/git/gitaly/bin
client_path: /home/git/gitaly
# Default Gitaly authentication token. Can be overridden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly.
......
# 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
......@@ -3843,7 +3843,6 @@ ActiveRecord::Schema.define(version: 2020_01_21_132641) do
t.string "encrypted_secret_token_iv", limit: 255
t.boolean "secret", default: false, null: false
t.string "repository_storage", limit: 255, default: "default", null: false
t.integer "storage_version", default: 2, null: false
t.index ["author_id"], name: "index_snippets_on_author_id"
t.index ["content"], name: "index_snippets_on_content_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["created_at"], name: "index_snippets_on_created_at"
......
......@@ -268,8 +268,9 @@ default value can be found in `/opt/gitlab/etc/gitlab-rails/env/RAILS_ENV`.
### Using negation
You're able to run all queues in `sidekiq_queues.yml` file on a single or
multiple processes with exceptions using the `--negate` flag.
You're able to run all queues in the `all_queues.yml` file (or the equivalent EE
file) on a single or multiple processes with exceptions using the `--negate`
flag.
For example, say you want to run a single process for all queues,
except `process_commit` and `post_receive`:
......
......@@ -150,6 +150,15 @@ type BlobEdge {
node: Blob
}
"""
Types of blob viewers
"""
enum BlobViewersType {
auxiliary
rich
simple
}
type Commit {
"""
Author of the commit
......@@ -5934,9 +5943,9 @@ type Snippet implements Noteable {
author: User!
"""
Content of the snippet
Snippet blob
"""
content: String!
blob: SnippetBlob!
"""
Timestamp this snippet was created
......@@ -6049,6 +6058,91 @@ type Snippet implements Noteable {
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.
"""
......
......@@ -6275,8 +6275,8 @@
"deprecationReason": null
},
{
"name": "content",
"description": "Content of the snippet",
"name": "blob",
"description": "Snippet blob",
"args": [
],
......@@ -6284,8 +6284,8 @@
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"kind": "OBJECT",
"name": "SnippetBlob",
"ofType": null
}
},
......@@ -7004,6 +7004,311 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SnippetBlob",
"description": "Represents the snippet blob",
"fields": [
{
"name": "binary",
"description": "Shows whether the blob is binary",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "highlightedData",
"description": "Blob highlighted data",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Blob name",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "path",
"description": "Blob path",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "rawPath",
"description": "Blob raw content endpoint path",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "richViewer",
"description": "Blob content rich viewer",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "SnippetBlobViewer",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "simpleViewer",
"description": "Blob content simple viewer",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SnippetBlobViewer",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "size",
"description": "Blob size",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SnippetBlobViewer",
"description": "Represents how the blob content should be displayed",
"fields": [
{
"name": "collapsed",
"description": "Shows whether the blob should be displayed collapsed",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fileType",
"description": "Content file type",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "loadAsync",
"description": "Shows whether the blob content is loaded async",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "loadingPartialName",
"description": "Loading partial name",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "renderError",
"description": "Error rendering the blob content",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "tooLarge",
"description": "Shows whether the blob too large to be displayed",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "Type of blob viewer",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "BlobViewersType",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "BlobViewersType",
"description": "Types of blob viewers",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "rich",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "simple",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "auxiliary",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "VisibilityScopesEnum",
......
......@@ -926,15 +926,44 @@ Represents a snippet entry
| `project` | Project | The project the snippet is associated with |
| `author` | User! | The owner of the snippet |
| `fileName` | String | File Name of the snippet |
| `content` | String! | Content of the snippet |
| `description` | String | Description of the snippet |
| `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet |
| `createdAt` | Time! | Timestamp this snippet was created |
| `updatedAt` | Time! | Timestamp this snippet was updated |
| `webUrl` | String! | Web URL of the snippet |
| `rawUrl` | String! | Raw URL of the snippet |
| `blob` | SnippetBlob! | Snippet blob |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
## SnippetBlob
Represents the snippet blob
| Name | Type | Description |
| --- | ---- | ---------- |
| `highlightedData` | String | Blob highlighted data |
| `rawPath` | String! | Blob raw content endpoint path |
| `size` | Int! | Blob size |
| `binary` | Boolean! | Shows whether the blob is binary |
| `name` | String | Blob name |
| `path` | String | Blob path |
| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |
| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
## SnippetBlobViewer
Represents how the blob content should be displayed
| Name | Type | Description |
| --- | ---- | ---------- |
| `type` | BlobViewersType! | Type of blob viewer |
| `loadAsync` | Boolean! | Shows whether the blob content is loaded async |
| `collapsed` | Boolean! | Shows whether the blob should be displayed collapsed |
| `tooLarge` | Boolean! | Shows whether the blob too large to be displayed |
| `renderError` | String | Error rendering the blob content |
| `fileType` | String! | Content file type |
| `loadingPartialName` | String! | Loading partial name |
## SnippetPermissions
| Name | Type | Description |
......
......@@ -312,7 +312,7 @@ function createComponent(props = {}) {
`ApolloMutation` component exposes `mutate` method via scoped slot. If we want to test this method, we need to add it to mocks:
```javascript
const mutate = jest.fn(() => Promise.resolve());
const mutate = jest.fn().mockResolvedValue();
const $apollo = {
mutate,
};
......
......@@ -17,8 +17,11 @@ would be `process_something`. If you're not sure what queue a worker uses,
you can find it using `SomeWorker.queue`. There is almost never a reason to
manually override the queue name using `sidekiq_options queue: :some_queue`.
You must always add any new queues to `app/workers/all_queues.yml` or `ee/app/workers/all_queues.yml`
otherwise your worker will not run.
After adding a new queue, run `bin/rake
gitlab:sidekiq:all_queues_yml:generate` to regenerate
`app/workers/all_queues.yml` or `ee/app/workers/all_queues.yml` so that
it can be picked up by
[`sidekiq-cluster`](../administration/operations/extra_sidekiq_processes.md).
## Queue Namespaces
......
......@@ -207,7 +207,8 @@ variables:
If your project requires custom build configurations, it can be preferable to avoid
compilation during your SAST execution and instead pass all job artifacts from an
earlier stage within the pipeline.
earlier stage within the pipeline. This is the current strategy when requiring
a `before_script` execution to prepare your scan job.
To pass your project's dependencies as artifacts, the dependencies must be included
in the project's working directory and specified using the `artifacts:path` configuration.
......
......@@ -178,7 +178,6 @@ excluded_attributes:
- :encrypted_secret_token
- :encrypted_secret_token_iv
- :repository_storage
- :storage_version
merge_request_diff:
- :external_diff
- :stored_externally
......
......@@ -4,6 +4,22 @@ require 'yaml'
module Gitlab
module SidekiqConfig
FOSS_QUEUE_CONFIG_PATH = 'app/workers/all_queues.yml'
EE_QUEUE_CONFIG_PATH = 'ee/app/workers/all_queues.yml'
QUEUE_CONFIG_PATHS = [
FOSS_QUEUE_CONFIG_PATH,
(EE_QUEUE_CONFIG_PATH if Gitlab.ee?)
].compact.freeze
# For queues that don't have explicit workers - default and mailers
DummyWorker = Struct.new(:queue)
DEFAULT_WORKERS = [
Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('default'), ee: false),
Gitlab::SidekiqConfig::Worker.new(DummyWorker.new('mailers'), ee: false)
].freeze
class << self
include Gitlab::SidekiqConfig::CliMethods
......@@ -25,28 +41,46 @@ module Gitlab
def workers
@workers ||= begin
result = find_workers(Rails.root.join('app', 'workers'))
result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'))) if Gitlab.ee?
result = []
result.concat(DEFAULT_WORKERS)
result.concat(find_workers(Rails.root.join('app', 'workers'), ee: false))
if Gitlab.ee?
result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'), ee: true))
end
result
end
end
def workers_for_all_queues_yml
workers.partition(&:ee?).reverse.map(&:sort)
end
def all_queues_yml_outdated?
foss_workers, ee_workers = workers_for_all_queues_yml
return true if foss_workers != YAML.safe_load(File.read(FOSS_QUEUE_CONFIG_PATH))
Gitlab.ee? && ee_workers != YAML.safe_load(File.read(EE_QUEUE_CONFIG_PATH))
end
private
def find_workers(root)
def find_workers(root, ee:)
concerns = root.join('concerns').to_s
workers = Dir[root.join('**', '*.rb')]
Dir[root.join('**', '*.rb')]
.reject { |path| path.start_with?(concerns) }
.map { |path| worker_from_path(path, root) }
.select { |worker| worker < Sidekiq::Worker }
.map { |worker| Gitlab::SidekiqConfig::Worker.new(worker, ee: ee) }
end
workers.map! do |path|
ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
ns.camelize.constantize
end
def worker_from_path(path, root)
ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
# Skip things that aren't workers
workers.select { |w| w < Sidekiq::Worker }
ns.camelize.constantize
end
end
end
......
# 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
# 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
......@@ -34,6 +34,7 @@ unless Rails.env.production?
scss_lint
gettext:lint
lint:static_verification
gitlab:sidekiq:all_queues_yml:check
]
if Gitlab.ee?
......
......@@ -3,7 +3,7 @@
FactoryBot.define do
factory :project_error_tracking_setting, class: 'ErrorTracking::ProjectErrorTrackingSetting' do
project
api_url { 'https://gitlab.com/api/0/projects/sentry-org/sentry-project' }
api_url { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
enabled { true }
token { 'access_token_123' }
project_name { 'Sentry Project' }
......
# 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
# 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
......@@ -38,7 +38,7 @@
},
"firstSeen": "2018-11-06T21:19:55Z",
"hasSeen": false,
"id": "503504",
"id": "11",
"isBookmarked": false,
"isPublic": false,
"isSubscribed": true,
......@@ -72,232 +72,64 @@
"shortId": "PUMP-STATION-1",
"stats": {
"24h": [
[
1541451600.0,
557
],
[
1541455200.0,
473
],
[
1541458800.0,
914
],
[
1541462400.0,
991
],
[
1541466000.0,
925
],
[
1541469600.0,
881
],
[
1541473200.0,
182
],
[
1541476800.0,
490
],
[
1541480400.0,
820
],
[
1541484000.0,
322
],
[
1541487600.0,
836
],
[
1541491200.0,
565
],
[
1541494800.0,
758
],
[
1541498400.0,
880
],
[
1541502000.0,
677
],
[
1541505600.0,
381
],
[
1541509200.0,
814
],
[
1541512800.0,
329
],
[
1541516400.0,
446
],
[
1541520000.0,
731
],
[
1541523600.0,
111
],
[
1541527200.0,
926
],
[
1541530800.0,
772
],
[
1541534400.0,
400
],
[
1541538000.0,
943
]
[1541451600.0, 557],
[1541455200.0, 473],
[1541458800.0, 914],
[1541462400.0, 991],
[1541466000.0, 925],
[1541469600.0, 881],
[1541473200.0, 182],
[1541476800.0, 490],
[1541480400.0, 820],
[1541484000.0, 322],
[1541487600.0, 836],
[1541491200.0, 565],
[1541494800.0, 758],
[1541498400.0, 880],
[1541502000.0, 677],
[1541505600.0, 381],
[1541509200.0, 814],
[1541512800.0, 329],
[1541516400.0, 446],
[1541520000.0, 731],
[1541523600.0, 111],
[1541527200.0, 926],
[1541530800.0, 772],
[1541534400.0, 400],
[1541538000.0, 943]
],
"30d": [
[
1538870400.0,
565
],
[
1538956800.0,
12862
],
[
1539043200.0,
15617
],
[
1539129600.0,
10809
],
[
1539216000.0,
15065
],
[
1539302400.0,
12927
],
[
1539388800.0,
12994
],
[
1539475200.0,
13139
],
[
1539561600.0,
11838
],
[
1539648000.0,
12088
],
[
1539734400.0,
12338
],
[
1539820800.0,
12768
],
[
1539907200.0,
12816
],
[
1539993600.0,
15356
],
[
1540080000.0,
10910
],
[
1540166400.0,
12306
],
[
1540252800.0,
12912
],
[
1540339200.0,
14700
],
[
1540425600.0,
11890
],
[
1540512000.0,
11684
],
[
1540598400.0,
13510
],
[
1540684800.0,
12625
],
[
1540771200.0,
12811
],
[
1540857600.0,
13180
],
[
1540944000.0,
14651
],
[
1541030400.0,
14161
],
[
1541116800.0,
12612
],
[
1541203200.0,
14316
],
[
1541289600.0,
14742
],
[
1541376000.0,
12505
],
[
1541462400.0,
14180
]
[1538870400.0, 565],
[1538956800.0, 12862],
[1539043200.0, 15617],
[1539129600.0, 10809],
[1539216000.0, 15065],
[1539302400.0, 12927],
[1539388800.0, 12994],
[1539475200.0, 13139],
[1539561600.0, 11838],
[1539648000.0, 12088],
[1539734400.0, 12338],
[1539820800.0, 12768],
[1539907200.0, 12816],
[1539993600.0, 15356],
[1540080000.0, 10910],
[1540166400.0, 12306],
[1540252800.0, 12912],
[1540339200.0, 14700],
[1540425600.0, 11890],
[1540512000.0, 11684],
[1540598400.0, 13510],
[1540684800.0, 12625],
[1540771200.0, 12811],
[1540857600.0, 13180],
[1540944000.0, 14651],
[1541030400.0, 14161],
[1541116800.0, 12612],
[1541203200.0, 14316],
[1541289600.0, 14742],
[1541376000.0, 12505],
[1541462400.0, 14180]
]
},
"status": "unresolved",
......
......@@ -18,6 +18,8 @@ exports[`grafana integration component default state to match the default snapsh
<gl-button-stub
class="js-settings-toggle"
size="md"
variant="secondary"
>
Expand
</gl-button-stub>
......@@ -89,6 +91,7 @@ exports[`grafana integration component default state to match the default snapsh
</gl-form-group-stub>
<gl-button-stub
size="md"
variant="success"
>
......
......@@ -12,7 +12,7 @@ describe('DateTimePicker', () => {
const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle');
const dropdownMenu = () => dateTimePicker.find('.dropdown-menu');
const applyButtonElement = () => dateTimePicker.find('button[variant="success"]').element;
const applyButtonElement = () => dateTimePicker.find('button.btn-success').element;
const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element;
const fillInputAndBlur = (input, val) => {
dateTimePicker.find(input).setValue(val);
......
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
import eventHub from '~/notes/event_hub';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('DiscussionFilterNote component', () => {
let vm;
let wrapper;
const createComponent = () => {
const Component = Vue.extend(DiscussionFilterNote);
return mountComponent(Component);
wrapper = shallowMount(DiscussionFilterNote);
};
beforeEach(() => {
vm = createComponent();
createComponent();
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
describe('computed', () => {
describe('timelineContent', () => {
it('returns string containing instruction for switching feed type', () => {
expect(vm.timelineContent).toBe(
"You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.",
);
});
});
it('timelineContent renders a string containing instruction for switching feed type', () => {
expect(wrapper.find({ ref: 'timelineContent' }).html()).toBe(
"<div>You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.</div>",
);
});
describe('methods', () => {
describe('selectFilter', () => {
it('emits `dropdownSelect` event on `eventHub` with provided param', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
it('emits `dropdownSelect` event with 0 parameter on clicking Show all activity button', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
wrapper.find({ ref: 'showAllActivity' }).vm.$emit('click');
vm.selectFilter(1);
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
});
});
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 0);
});
describe('template', () => {
it('renders component container element', () => {
expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true);
});
it('renders comment icon element', () => {
expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain(
'comment',
);
});
it('renders filter information note', () => {
expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain(
"You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
);
});
it('renders filter buttons', () => {
const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions');
expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain(
'Show all activity',
);
expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain(
'Show comments only',
);
});
it('clicking `Show all activity` button calls `selectFilter("all")` method', () => {
const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child');
jest.spyOn(vm, 'selectFilter').mockImplementation(() => {});
showAllBtn.dispatchEvent(new Event('click'));
expect(vm.selectFilter).toHaveBeenCalledWith(0);
});
it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => {
const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child');
jest.spyOn(vm, 'selectFilter').mockImplementation(() => {});
showAllBtn.dispatchEvent(new Event('click'));
it('emits `dropdownSelect` event with 1 parameter on clicking Show comments only button', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
wrapper.find({ ref: 'showComments' }).vm.$emit('click');
expect(vm.selectFilter).toHaveBeenCalledWith(1);
});
expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
});
});
import Vue from 'vue';
import noteAttachment from '~/notes/components/note_attachment.vue';
describe('issue note attachment', () => {
it('should render properly', () => {
const props = {
attachment: {
filename: 'dk.png',
image: true,
url: '/dk.png',
import { shallowMount } from '@vue/test-utils';
import NoteAttachment from '~/notes/components/note_attachment.vue';
describe('Issue note attachment', () => {
let wrapper;
const findImage = () => wrapper.find({ ref: 'attachmentImage' });
const findUrl = () => wrapper.find({ ref: 'attachmentUrl' });
const createComponent = attachment => {
wrapper = shallowMount(NoteAttachment, {
propsData: {
attachment,
},
};
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders attachment image if it is passed in attachment prop', () => {
createComponent({
image: 'test-image',
});
expect(findImage().exists()).toBe(true);
});
it('renders attachment url if it is passed in attachment prop', () => {
createComponent({
url: 'test-url',
});
expect(findUrl().exists()).toBe(true);
});
const Component = Vue.extend(noteAttachment);
const vm = new Component({
propsData: props,
}).$mount();
it('does not render image and url if attachment object is empty', () => {
createComponent({});
expect(vm.$el.classList.contains('note-attachment')).toBeTruthy();
expect(vm.$el.querySelector('img').src).toContain(props.attachment.url);
expect(vm.$el.querySelector('a').href).toContain(props.attachment.url);
expect(findImage().exists()).toBe(false);
expect(findUrl().exists()).toBe(false);
});
});
import Vue from 'vue';
import noteHeader from '~/notes/components/note_header.vue';
import createStore from '~/notes/stores';
describe('note_header component', () => {
let store;
let vm;
let Component;
beforeEach(() => {
Component = Vue.extend(noteHeader);
store = createStore();
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import NoteHeader from '~/notes/components/note_header.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const actions = {
setTargetNoteHash: jest.fn(),
};
describe('NoteHeader component', () => {
let wrapper;
const findActionsWrapper = () => wrapper.find({ ref: 'discussionActions' });
const findChevronIcon = () => wrapper.find({ ref: 'chevronIcon' });
const findActionText = () => wrapper.find({ ref: 'actionText' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
const createComponent = props => {
wrapper = shallowMount(NoteHeader, {
localVue,
store: new Vuex.Store({
actions,
}),
propsData: {
...props,
actionTextHtml: '',
noteId: '1394',
},
});
};
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
describe('individual note', () => {
beforeEach(() => {
vm = new Component({
store,
propsData: {
actionText: 'commented',
actionTextHtml: '',
author: {
avatar_url: null,
id: 1,
name: 'Root',
path: '/root',
state: 'active',
username: 'root',
},
createdAt: '2017-08-02T10:51:58.559Z',
includeToggle: false,
noteId: '1394',
expanded: true,
},
}).$mount();
it('does not render discussion actions when includeToggle is false', () => {
createComponent({
includeToggle: false,
});
it('should render user information', () => {
expect(vm.$el.querySelector('.note-header-author-name').textContent.trim()).toEqual('Root');
expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual('/root');
expect(vm.$el.querySelector('.note-header-info a').dataset.userId).toEqual('1');
expect(vm.$el.querySelector('.note-header-info a').dataset.username).toEqual('root');
expect(vm.$el.querySelector('.note-header-info a').classList).toContain('js-user-link');
expect(findActionsWrapper().exists()).toBe(false);
});
describe('when includes a toggle', () => {
it('renders discussion actions', () => {
createComponent({
includeToggle: true,
});
expect(findActionsWrapper().exists()).toBe(true);
});
it('should render timestamp link', () => {
expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined();
it('emits toggleHandler event on button click', () => {
createComponent({
includeToggle: true,
});
wrapper.find('.note-action-button').trigger('click');
expect(wrapper.emitted('toggleHandler')).toBeDefined();
expect(wrapper.emitted('toggleHandler')).toHaveLength(1);
});
it('should not render user information when prop `author` is empty object', done => {
vm.author = {};
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.note-header-author-name')).toBeNull();
})
.then(done)
.catch(done.fail);
it('has chevron-up icon if expanded prop is true', () => {
createComponent({
includeToggle: true,
expanded: true,
});
expect(findChevronIcon().classes()).toContain('fa-chevron-up');
});
});
describe('discussion', () => {
beforeEach(() => {
vm = new Component({
store,
propsData: {
actionText: 'started a discussion',
actionTextHtml: '',
author: {
avatar_url: null,
id: 1,
name: 'Root',
path: '/root',
state: 'active',
username: 'root',
},
createdAt: '2017-08-02T10:51:58.559Z',
includeToggle: true,
noteId: '1395',
expanded: true,
},
}).$mount();
it('has chevron-down icon if expanded prop is false', () => {
createComponent({
includeToggle: true,
expanded: false,
});
expect(findChevronIcon().classes()).toContain('fa-chevron-down');
});
});
it('should render toggle button', () => {
expect(vm.$el.querySelector('.js-vue-toggle-button')).toBeDefined();
it('renders an author link if author is passed to props', () => {
createComponent({
author: {
avatar_url: null,
id: 1,
name: 'Root',
path: '/root',
state: 'active',
username: 'root',
},
});
it('emits toggle event on click', done => {
jest.spyOn(vm, '$emit').mockImplementation(() => {});
expect(wrapper.find('.js-user-link').exists()).toBe(true);
});
vm.$el.querySelector('.js-vue-toggle-button').click();
it('renders deleted user text if author is not passed as a prop', () => {
createComponent();
Vue.nextTick(() => {
expect(vm.$emit).toHaveBeenCalledWith('toggleHandler');
done();
});
});
expect(wrapper.text()).toContain('A deleted user');
});
it('does not render created at information if createdAt is not passed as a prop', () => {
createComponent();
it('renders up arrow when open', done => {
vm.expanded = true;
expect(findActionText().exists()).toBe(false);
expect(findTimestamp().exists()).toBe(false);
});
Vue.nextTick(() => {
expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
'fa-chevron-up',
);
done();
describe('when createdAt is passed as a prop', () => {
it('renders action text and a timestamp', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
});
expect(findActionText().exists()).toBe(true);
expect(findTimestamp().exists()).toBe(true);
});
it('renders down arrow when closed', done => {
vm.expanded = false;
it('renders correct actionText if passed', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
actionText: 'Test action text',
});
expect(findActionText().text()).toBe('Test action text');
});
Vue.nextTick(() => {
expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
'fa-chevron-down',
);
done();
it('calls an action when timestamp is clicked', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
});
findTimestamp().trigger('click');
expect(actions.setTargetNoteHash).toHaveBeenCalled();
});
});
});
......@@ -39,6 +39,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
</form>
<gl-button-stub
size="md"
variant="secondary"
>
Cancel
......@@ -46,6 +47,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
<gl-button-stub
disabled="true"
size="md"
variant="warning"
>
......@@ -55,6 +57,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
<gl-button-stub
disabled="true"
size="md"
variant="danger"
>
action
......
......@@ -84,7 +84,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append"
>
<button
class="btn input-group-text btn-secondary btn-default"
class="btn input-group-text btn-secondary btn-md btn-default"
data-clipboard-text="docker login host"
title="Copy login command"
type="button"
......@@ -122,7 +122,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append"
>
<button
class="btn input-group-text btn-secondary btn-default"
class="btn input-group-text btn-secondary btn-md btn-default"
data-clipboard-text="docker build -t url ."
title="Copy build command"
type="button"
......@@ -152,7 +152,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="input-group-append"
>
<button
class="btn input-group-text btn-secondary btn-default"
class="btn input-group-text btn-secondary btn-md btn-default"
data-clipboard-text="docker push url"
title="Copy push command"
type="button"
......
......@@ -159,7 +159,9 @@ exports[`Settings Form renders 1`] = `
>
<glbutton-stub
class="mr-2 d-block"
size="md"
type="reset"
variant="secondary"
>
Cancel
......@@ -168,6 +170,7 @@ exports[`Settings Form renders 1`] = `
<glbutton-stub
class="d-flex justify-content-center align-items-center js-no-auto-disable"
size="md"
type="submit"
variant="success"
>
......
import $ from 'jquery';
import { mount } from '@vue/test-utils';
import { first } from 'underscore';
import EvidenceBlock from '~/releases/list/components/evidence_block.vue';
......@@ -43,6 +44,7 @@ describe('Release block', () => {
const editButton = () => wrapper.find('.js-edit-button');
beforeEach(() => {
jest.spyOn($.fn, 'renderGFM');
releaseClone = JSON.parse(JSON.stringify(release));
});
......@@ -66,6 +68,11 @@ describe('Release block', () => {
expect(wrapper.text()).toContain(release.name);
});
it('renders release description', () => {
expect(wrapper.vm.$refs['gfm-content']).toBeDefined();
expect($.fn.renderGFM).toHaveBeenCalledTimes(1);
});
it('renders release date', () => {
expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at));
});
......
......@@ -17,6 +17,8 @@ exports[`self monitor component When the self monitor project has not been creat
<gl-button-stub
class="js-settings-toggle"
size="md"
variant="secondary"
>
Expand
</gl-button-stub>
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Expand button on click when short text is provided renders button after text 1`] = `
"<span><button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-prepend text-expander btn-blank btn-secondary\\" style=\\"display: none;\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\"><use xlink:href=\\"#ellipsis_h\\"></use></svg></button> <!----> <span><p>Expanded!</p></span> <button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-append text-expander btn-blank btn-secondary\\" style=\\"\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\">
<use xlink:href=\\"#ellipsis_h\\"></use>
</svg></button></span>"
<span>
<button
aria-label="Click to expand text"
class="btn js-text-expander-prepend text-expander btn-blank btn-secondary btn-md"
style="display: none;"
type="button"
>
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
>
<use
xlink:href="#ellipsis_h"
/>
</svg>
</button>
<!---->
<span>
<p>
Expanded!
</p>
</span>
<button
aria-label="Click to expand text"
class="btn js-text-expander-append text-expander btn-blank btn-secondary btn-md"
style=""
type="button"
>
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
>
<use
xlink:href="#ellipsis_h"
/>
</svg>
</button>
</span>
`;
exports[`Expand button when short text is provided renders button before text 1`] = `
"<span><button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-prepend text-expander btn-blank btn-secondary\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\"><use xlink:href=\\"#ellipsis_h\\"></use></svg></button> <span><p>Short</p></span>
<!----> <button aria-label=\\"Click to expand text\\" type=\\"button\\" class=\\"btn js-text-expander-append text-expander btn-blank btn-secondary\\" style=\\"display: none;\\"><svg aria-hidden=\\"true\\" class=\\"s12 ic-ellipsis_h\\">
<use xlink:href=\\"#ellipsis_h\\"></use>
</svg></button></span>"
<span>
<button
aria-label="Click to expand text"
class="btn js-text-expander-prepend text-expander btn-blank btn-secondary btn-md"
type="button"
>
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
>
<use
xlink:href="#ellipsis_h"
/>
</svg>
</button>
<span>
<p>
Short
</p>
</span>
<!---->
<button
aria-label="Click to expand text"
class="btn js-text-expander-append text-expander btn-blank btn-secondary btn-md"
style="display: none;"
type="button"
>
<svg
aria-hidden="true"
class="s12 ic-ellipsis_h"
>
<use
xlink:href="#ellipsis_h"
/>
</svg>
</button>
</span>
`;
......@@ -71,7 +71,7 @@ describe('Expand button', () => {
it('renders button before text', () => {
expect(expanderPrependEl().isVisible()).toBe(true);
expect(expanderAppendEl().isVisible()).toBe(false);
expect(wrapper.find(ExpandButton).html()).toMatchSnapshot();
expect(wrapper.find(ExpandButton).element).toMatchSnapshot();
});
});
......@@ -119,7 +119,7 @@ describe('Expand button', () => {
it('renders button after text', () => {
expect(expanderPrependEl().isVisible()).toBe(false);
expect(expanderAppendEl().isVisible()).toBe(true);
expect(wrapper.find(ExpandButton).html()).toMatchSnapshot();
expect(wrapper.find(ExpandButton).element).toMatchSnapshot();
});
});
});
......
# 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
......@@ -5,10 +5,10 @@ require 'spec_helper'
describe GitlabSchema.types['Snippet'] do
it 'has the correct fields' do
expected_fields = [:id, :title, :project, :author,
:file_name, :content, :description,
:file_name, :description,
:visibility_level, :created_at, :updated_at,
:web_url, :raw_url, :notes, :discussions,
:user_permissions, :description_html]
:user_permissions, :description_html, :blob]
is_expected.to have_graphql_fields(*expected_fields)
end
......
# 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
# 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
# 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
......@@ -5,10 +5,10 @@ require 'spec_helper'
describe Gitlab::SidekiqConfig do
describe '.workers' do
it 'includes all workers' do
workers = described_class.workers
worker_classes = described_class.workers.map(&:klass)
expect(workers).to include(PostReceive)
expect(workers).to include(MergeWorker)
expect(worker_classes).to include(PostReceive)
expect(worker_classes).to include(MergeWorker)
end
end
......@@ -44,4 +44,40 @@ describe Gitlab::SidekiqConfig do
expect(queues).to include('unknown')
end
end
describe '.workers_for_all_queues_yml' do
it 'returns a tuple with FOSS workers first' do
expect(described_class.workers_for_all_queues_yml.first)
.to include(an_object_having_attributes(queue: 'post_receive'))
end
end
describe '.all_queues_yml_outdated?' do
before do
workers = [
PostReceive,
MergeWorker,
ProcessCommitWorker
].map { |worker| described_class::Worker.new(worker, ee: false) }
allow(described_class).to receive(:workers).and_return(workers)
allow(Gitlab).to receive(:ee?).and_return(false)
end
it 'returns true if the YAML file does not match the application code' do
allow(File).to receive(:read)
.with(described_class::FOSS_QUEUE_CONFIG_PATH)
.and_return(YAML.dump(%w[post_receive merge]))
expect(described_class.all_queues_yml_outdated?).to be(true)
end
it 'returns false if the YAML file matches the application code' do
allow(File).to receive(:read)
.with(described_class::FOSS_QUEUE_CONFIG_PATH)
.and_return(YAML.dump(%w[merge post_receive process_commit]))
expect(described_class.all_queues_yml_outdated?).to be(false)
end
end
end
......@@ -8,7 +8,7 @@ describe Sentry::Client::Issue do
let(:token) { 'test-token' }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
let(:client) { Sentry::Client.new(sentry_url, token) }
let(:issue_id) { 503504 }
let(:issue_id) { 11 }
describe '#list_issues' do
shared_examples 'issues have correct return type' do |klass|
......@@ -243,7 +243,7 @@ describe Sentry::Client::Issue do
end
it 'has a correct external URL' do
expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/503504')
expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/11')
end
it 'issue has a correct external base url' do
......
# 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
......@@ -67,7 +67,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(content)
expect(mutation_response['snippet']['blob']['highlightedData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name)
......@@ -92,7 +92,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(content)
expect(mutation_response['snippet']['blob']['highlightedData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name)
......
......@@ -56,7 +56,7 @@ describe 'Updating a Snippet' do
it 'returns the updated Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(updated_content)
expect(mutation_response['snippet']['blob']['highlightedData']).to match(updated_content)
expect(mutation_response['snippet']['title']).to eq(updated_title)
expect(mutation_response['snippet']['description']).to eq(updated_description)
expect(mutation_response['snippet']['fileName']).to eq(updated_file_name)
......@@ -77,7 +77,7 @@ describe 'Updating a Snippet' do
it 'returns the Snippet with its original values' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(original_content)
expect(mutation_response['snippet']['blob']['highlightedData']).to match(original_content)
expect(mutation_response['snippet']['title']).to eq(original_title)
expect(mutation_response['snippet']['description']).to eq(original_description)
expect(mutation_response['snippet']['fileName']).to eq(original_file_name)
......
......@@ -5,6 +5,49 @@ require 'spec_helper'
describe SubmitUsagePingService do
include StubRequests
let(:score_params) do
{
score: {
leader_issues: 10.2,
instance_issues: 3.2,
percentage_issues: 31.37,
leader_notes: 25.3,
instance_notes: 23.2,
leader_milestones: 16.2,
instance_milestones: 5.5,
leader_boards: 5.2,
instance_boards: 3.2,
leader_merge_requests: 5.2,
instance_merge_requests: 3.2,
leader_ci_pipelines: 25.1,
instance_ci_pipelines: 21.3,
leader_environments: 3.3,
instance_environments: 2.2,
leader_deployments: 41.3,
instance_deployments: 15.2,
leader_projects_prometheus_active: 0.31,
instance_projects_prometheus_active: 0.30,
leader_service_desk_issues: 15.8,
instance_service_desk_issues: 15.1,
non_existing_column: 'value'
}
}
end
let(:with_dev_ops_score_params) { { dev_ops_score: score_params[:score] } }
let(:with_conv_index_params) { { conv_index: score_params[:score] } }
let(:without_dev_ops_score_params) { { dev_ops_score: {} } }
context 'when usage ping is disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
......@@ -19,13 +62,25 @@ describe SubmitUsagePingService do
end
end
shared_examples 'saves DevOps score data from the response' do
it do
expect { subject.execute }
.to change { DevOpsScore::Metric.count }
.by(1)
expect(DevOpsScore::Metric.last.leader_issues).to eq 10.2
expect(DevOpsScore::Metric.last.instance_issues).to eq 3.2
expect(DevOpsScore::Metric.last.percentage_issues).to eq 31.37
end
end
context 'when usage ping is enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
end
it 'sends a POST request' do
response = stub_response(without_conv_index_params)
response = stub_response(without_dev_ops_score_params)
subject.execute
......@@ -33,7 +88,7 @@ describe SubmitUsagePingService do
end
it 'refreshes usage data statistics before submitting' do
stub_response(without_conv_index_params)
stub_response(without_dev_ops_score_params)
expect(Gitlab::UsageData).to receive(:to_json)
.with(force_refresh: true)
......@@ -42,62 +97,21 @@ describe SubmitUsagePingService do
subject.execute
end
it 'saves DevOps Score data from the response' do
stub_response(with_conv_index_params)
context 'when conv_index data is passed' do
before do
stub_response(with_conv_index_params)
end
expect { subject.execute }
.to change { DevOpsScore::Metric.count }
.by(1)
expect(DevOpsScore::Metric.last.leader_issues).to eq 10.2
expect(DevOpsScore::Metric.last.instance_issues).to eq 3.2
expect(DevOpsScore::Metric.last.percentage_issues).to eq 31.37
it_behaves_like 'saves DevOps score data from the response'
end
end
def without_conv_index_params
{
conv_index: {}
}
end
def with_conv_index_params
{
conv_index: {
leader_issues: 10.2,
instance_issues: 3.2,
percentage_issues: 31.37,
leader_notes: 25.3,
instance_notes: 23.2,
leader_milestones: 16.2,
instance_milestones: 5.5,
context 'when DevOps score data is passed' do
before do
stub_response(with_dev_ops_score_params)
end
leader_boards: 5.2,
instance_boards: 3.2,
leader_merge_requests: 5.2,
instance_merge_requests: 3.2,
leader_ci_pipelines: 25.1,
instance_ci_pipelines: 21.3,
leader_environments: 3.3,
instance_environments: 2.2,
leader_deployments: 41.3,
instance_deployments: 15.2,
leader_projects_prometheus_active: 0.31,
instance_projects_prometheus_active: 0.30,
leader_service_desk_issues: 15.8,
instance_service_desk_issues: 15.1,
non_existing_column: 'value'
}
}
it_behaves_like 'saves DevOps score data from the response'
end
end
def stub_response(body)
......
# 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:
#
......
# 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
# 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
......@@ -3,8 +3,12 @@
require 'spec_helper'
describe 'Every Sidekiq worker' do
let(:workers_without_defaults) do
Gitlab::SidekiqConfig.workers - Gitlab::SidekiqConfig::DEFAULT_WORKERS
end
it 'does not use the default queue' do
expect(Gitlab::SidekiqConfig.workers.map(&:queue)).not_to include('default')
expect(workers_without_defaults.map(&:queue)).not_to include('default')
end
it 'uses the cronjob queue when the worker runs as a cronjob' do
......@@ -45,7 +49,7 @@ describe 'Every Sidekiq worker' do
# or explicitly be excluded with the `feature_category_not_owned!` annotation.
# Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do
Gitlab::SidekiqConfig.workers.each do |worker|
workers_without_defaults.each do |worker|
expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
end
end
......@@ -54,7 +58,7 @@ describe 'Every Sidekiq worker' do
# The category should match a value in `config/feature_categories.yml`.
# Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
it 'has a feature_category that maps to a value in feature_categories.yml', :aggregate_failures do
workers_with_feature_categories = Gitlab::SidekiqConfig.workers
workers_with_feature_categories = workers_without_defaults
.select(&:get_feature_category)
.reject(&:feature_category_not_owned?)
......@@ -69,7 +73,7 @@ describe 'Every Sidekiq worker' do
# rather than scaling the hardware to meet the SLO. For this reason, memory-bound,
# latency-sensitive jobs are explicitly discouraged and disabled.
it 'is (exclusively) memory-bound or latency-sentitive, not both', :aggregate_failures do
latency_sensitive_workers = Gitlab::SidekiqConfig.workers
latency_sensitive_workers = workers_without_defaults
.select(&:latency_sensitive_worker?)
latency_sensitive_workers.each do |worker|
......@@ -86,7 +90,7 @@ describe 'Every Sidekiq worker' do
# Please see doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies for more
# details.
it 'has (exclusively) external dependencies or is latency-sentitive, not both', :aggregate_failures do
latency_sensitive_workers = Gitlab::SidekiqConfig.workers
latency_sensitive_workers = workers_without_defaults
.select(&:latency_sensitive_worker?)
latency_sensitive_workers.each do |worker|
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment