Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-12 00:10:11 +00:00
parent 3f45eb27e9
commit c6f0b221b7
65 changed files with 1589 additions and 79 deletions

View file

@ -230,7 +230,7 @@ gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
gem 'hipchat', '~> 1.5.0'
# Jira integration
gem 'jira-ruby', '~> 1.7'
gem 'jira-ruby', '~> 2.0.0'
gem 'atlassian-jwt', '~> 0.2.0'
# Flowdock integration

View file

@ -545,7 +545,7 @@ GEM
opentracing (~> 0.3)
thrift
jaro_winkler (1.5.4)
jira-ruby (1.7.1)
jira-ruby (2.0.0)
activesupport
atlassian-jwt
multipart-post
@ -1278,7 +1278,7 @@ DEPENDENCIES
icalendar
influxdb (~> 0.2)
invisible_captcha (~> 0.12.1)
jira-ruby (~> 1.7)
jira-ruby (~> 2.0.0)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
jwt (~> 2.1.0)

View file

@ -170,17 +170,17 @@ export default {
</span>
</template>
<span v-else>{{ __('A deleted user') }}</span>
<span class="note-headline-light note-headline-meta d-inline-flex align-items-center">
<span class="note-headline-light note-headline-meta d-sm-inline-flex align-items-center">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span ref="actionText" class="system-note-separator">
<span ref="actionText" class="system-note-separator ml-1">
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
v-if="noteTimestampLink"
ref="noteTimestampLink"
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
class="note-timestamp system-note-separator mr-1"
@click="updateTargetNoteHash"
>
<time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
@ -194,7 +194,7 @@ export default {
name="eye-slash"
:size="14"
:title="__('Private comments are accessible by internal staff only')"
class="ml-1 gl-text-gray-800"
class="mx-1 gl-text-gray-800"
/>
<slot name="extra-controls"></slot>
<i

View file

@ -264,7 +264,7 @@ export default {
>
<slot slot="note-header-info" name="note-header-info"></slot>
<span v-if="commit" v-html="actionText"></span>
<span v-else class="d-none d-sm-inline mr-1">&middot;</span>
<span v-else class="d-none d-sm-inline">&middot;</span>
</note-header>
<note-actions
:author-id="author.id"

View file

@ -107,7 +107,7 @@ export default {
<span v-html="actionTextHtml"></span>
<template v-if="canSeeDescriptionVersion" slot="extra-controls">
&middot;
<button type="button" class="btn-blank btn-link" @click="toggleDescriptionVersion">
<button type="button" class="btn-blank btn-link ml-1" @click="toggleDescriptionVersion">
{{ __('Compare with previous version') }}
<icon :name="descriptionVersionToggleIcon" :size="12" class="append-left-5" />
</button>

View file

@ -18,6 +18,7 @@ class ApplicationController < ActionController::Base
include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern
include InitializesCurrentUserMode
include Gitlab::Logging::CloudflareHelper
before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
@ -151,6 +152,8 @@ class ApplicationController < ActionController::Base
end
payload[:queue_duration_s] = request.env[::Gitlab::Middleware::RailsQueueDuration::GITLAB_RAILS_QUEUE_DURATION_KEY]
store_cloudflare_headers!(payload, request)
end
##

View file

@ -40,6 +40,18 @@ module Emails
mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason))
end
def note_design_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id)
design = @note.noteable
@target_url = ::Gitlab::Routing.url_helpers.designs_project_issue_url(
@note.resource_parent,
design.issue,
note_target_url_query_params.merge(vueroute: design.filename)
)
mail_answer_note_thread(design, @note, note_thread_options(recipient_id, reason))
end
private
def note_target_url_options

View file

@ -2,17 +2,19 @@
module AlertManagement
class UpdateAlertStatusService
include Gitlab::Utils::StrongMemoize
# @param alert [AlertManagement::Alert]
# @param status [Integer] Must match a value from AlertManagement::Alert::STATUSES
def initialize(alert, status)
@alert = alert
@status = status
end
def execute
return error('Invalid status') unless AlertManagement::Alert::STATUSES.key?(status.to_sym)
return error('Invalid status') unless status_key
alert.status_event = AlertManagement::Alert::STATUS_EVENTS[status.to_sym]
if alert.save
if alert.update(status_event: status_event)
success
else
error(alert.errors.full_messages.to_sentence)
@ -23,6 +25,16 @@ module AlertManagement
attr_reader :alert, :status
def status_key
strong_memoize(:status_key) do
AlertManagement::Alert::STATUSES.key(status)
end
end
def status_event
AlertManagement::Alert::STATUS_EVENTS[status_key]
end
def success
ServiceResponse.success(payload: { alert: alert })
end

View file

@ -56,7 +56,10 @@ module Projects
end
def exporters
[version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver, snippets_repo_saver]
[
version_saver, avatar_saver, project_tree_saver, uploads_saver,
repo_saver, wiki_repo_saver, lfs_saver, snippets_repo_saver, design_repo_saver
]
end
def version_saver
@ -95,6 +98,10 @@ module Projects
Gitlab::ImportExport::SnippetsRepoSaver.new(current_user: current_user, project: project, shared: shared)
end
def design_repo_saver
Gitlab::ImportExport::DesignRepoSaver.new(project: project, shared: shared)
end
def cleanup
FileUtils.rm_rf(shared.archive_path) if shared&.archive_path
end
@ -117,5 +124,3 @@ module Projects
end
end
end
Projects::ImportExport::ExportService.prepend_if_ee('EE::Projects::ImportExport::ExportService')

View file

@ -0,0 +1 @@
= render 'note_email'

View file

@ -0,0 +1 @@
<%= render 'note_email' %>

View file

@ -1011,6 +1011,13 @@
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: design_management_new_version
:feature_category: :design_management
:has_external_dependencies:
:urgency: :low
:resource_boundary: :memory
:weight: 1
:idempotent:
- :name: detect_repository_languages
:feature_category: :source_code_management
:has_external_dependencies:

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
module DesignManagement
class NewVersionWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :design_management
# Declare this worker as memory bound due to
# `GenerateImageVersionsService` resizing designs
worker_resource_boundary :memory
def perform(version_id)
version = DesignManagement::Version.find(version_id)
add_system_note(version)
generate_image_versions(version)
rescue ActiveRecord::RecordNotFound => e
Sidekiq.logger.warn(e)
end
private
def add_system_note(version)
SystemNoteService.design_version_added(version)
end
def generate_image_versions(version)
DesignManagement::GenerateImageVersionsService.new(version).execute
end
end
end

View file

@ -0,0 +1,6 @@
---
title: Fix 'not enough data' in Value Stream Analytics when low median values are
returned
merge_request: 31315
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix missing space on system notes
merge_request: 31598
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix API requests for branch names ending in .txt
merge_request: 31446
author: Daniel Stone
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Log Cloudflare request headers
merge_request: 31532
author:
type: added

View file

@ -99,25 +99,29 @@ Set up the Jenkins project youre going to run your build on.
1. Check the following checkboxes:
- **Accepted Merge Request Events**
- **Closed Merge Request Events**
1. If you created a **Freestyle** project, choose Publish build status to GitLab in the Post-build Actions section.
If you created a **Pipeline** project, you must use a pipeline script to update the status on
GitLab. The following is an example pipeline script:
1. Specify how build status is reported to GitLab:
- If you created a **Freestyle** project, in the **Post-build Actions** section, choose
**Publish build status to GitLab**.
- If you created a **Pipeline** project, you must use a Jenkins Pipeline script to update the status on
GitLab.
```plaintext
pipeline {
agent any
Example Jenkins Pipeline script:
stages {
stage('gitlab') {
steps {
echo 'Notify GitLab'
updateGitlabCommitStatus name: 'build', state: 'pending'
updateGitlabCommitStatus name: 'build', state: 'success'
```groovy
pipeline {
agent any
stages {
stage('gitlab') {
steps {
echo 'Notify GitLab'
updateGitlabCommitStatus name: 'build', state: 'pending'
updateGitlabCommitStatus name: 'build', state: 'success'
}
}
}
}
}
```
```
## Configure the GitLab project

View file

@ -8,6 +8,8 @@ module API
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
after_validation { content_type "application/json" }
before do
require_repository_enabled!
authorize! :download_code, user_project

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
module API
module Entities
module DesignManagement
class Design < Grape::Entity
expose :id
expose :project_id
expose :filename
expose :image_url do |design|
::Gitlab::UrlBuilder.build(design)
end
end
end
end
end

View file

@ -31,6 +31,8 @@ module API
end
def todo_target_url(todo)
return design_todo_target_url(todo) if todo.for_design?
target_type = todo.target_type.underscore
target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url"
@ -42,6 +44,16 @@ module API
def todo_target_anchor(todo)
"note_#{todo.note_id}" if todo.note_id?
end
def design_todo_target_url(todo)
design = todo.target
path_options = {
anchor: todo_target_anchor(todo),
vueroute: design.filename
}
::Gitlab::Routing.url_helpers.designs_project_issue_url(design.project, design.issue, path_options)
end
end
end
end

View file

@ -28,8 +28,24 @@ module Banzai
def parent_records(parent, ids)
parent.issues.where(iid: ids.to_a)
end
def object_link_text_extras(issue, matches)
super + design_link_extras(issue, matches.named_captures['path'])
end
private
def design_link_extras(issue, path)
if path == '/designs' && read_designs?(issue)
['designs']
else
[]
end
end
def read_designs?(issue)
Ability.allowed?(current_user, :read_design, issue)
end
end
end
end
Banzai::Filter::IssueReferenceFilter.prepend_if_ee('EE::Banzai::Filter::IssueReferenceFilter')

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Banzai
module ReferenceParser
class DesignParser < BaseParser
self.reference_type = :design
def references_relation
DesignManagement::Design
end
def nodes_visible_to_user(user, nodes)
issues = issues_for_nodes(nodes)
issue_attr = 'data-issue'
nodes.select do |node|
if node.has_attribute?(issue_attr)
can?(user, :read_design, issues[node])
else
true
end
end
end
def issues_for_nodes(nodes)
relation = Issue.includes(project: [:project_feature])
grouped_objects_for_nodes(nodes, relation, 'data-issue')
end
end
end
end

View file

@ -15,7 +15,7 @@ module Gitlab
@query = @query.select(median_duration_in_seconds.as('median'))
result = execute_query(@query).first || {}
result['median'] ? result['median'].to_i : nil
result['median'] || nil
end
def days

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Gitlab
module GrapeLogging
module Loggers
class CloudflareLogger < ::GrapeLogging::Loggers::Base
include ::Gitlab::Logging::CloudflareHelper
def parameters(request, _response)
data = {}
store_cloudflare_headers!(data, request)
data
end
end
end
end
end

View file

@ -42,6 +42,10 @@ module Gitlab
"project.wiki.bundle"
end
def design_repo_bundle_filename
'project.design.bundle'
end
def snippet_repo_bundle_dir
'snippets'
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Gitlab
module ImportExport
class DesignRepoRestorer < RepoRestorer
def initialize(project:, shared:, path_to_bundle:)
super(project: project, shared: shared, path_to_bundle: path_to_bundle)
@repository = project.design_repository
end
# `restore` method is handled in super class
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Gitlab
module ImportExport
class DesignRepoSaver < RepoSaver
def save
@repository = project.design_repository
super
end
private
def bundle_full_path
File.join(shared.export_path, ::Gitlab::ImportExport.design_repo_bundle_filename)
end
end
end
end

View file

@ -34,7 +34,7 @@ module Gitlab
attr_accessor :archive_file, :current_user, :project, :shared
def restorers
[repo_restorer, wiki_restorer, project_tree, avatar_restorer,
[repo_restorer, wiki_restorer, project_tree, avatar_restorer, design_repo_restorer,
uploads_restorer, lfs_restorer, statistics_restorer, snippets_repo_restorer]
end
@ -71,6 +71,12 @@ module Gitlab
wiki_enabled: project.wiki_enabled?)
end
def design_repo_restorer
Gitlab::ImportExport::DesignRepoRestorer.new(path_to_bundle: design_repo_path,
shared: shared,
project: project)
end
def uploads_restorer
Gitlab::ImportExport::UploadsRestorer.new(project: project, shared: shared)
end
@ -101,6 +107,10 @@ module Gitlab
File.join(shared.export_path, Gitlab::ImportExport.wiki_repo_bundle_filename)
end
def design_repo_path
File.join(shared.export_path, Gitlab::ImportExport.design_repo_bundle_filename)
end
def remove_import_file
upload = project.import_export_upload
@ -141,5 +151,3 @@ module Gitlab
end
end
end
Gitlab::ImportExport::Importer.prepend_if_ee('EE::Gitlab::ImportExport::Importer')

View file

@ -29,6 +29,14 @@ tree:
- resource_label_events:
- label:
- :priorities
- designs:
- notes:
- :author
- events:
- :push_event_payload
- design_versions:
- actions:
- :design # Duplicate export of issues.designs in order to link the record to both Issue and Action
- :issue_assignees
- :zoom_meetings
- :sentry_issue
@ -287,6 +295,7 @@ excluded_attributes:
actions:
- :design_id
- :version_id
- image_v432x230
links:
- :release_id
project_members:
@ -379,14 +388,6 @@ ee:
tree:
project:
- issues:
- designs:
- notes:
- :author
- events:
- :push_event_payload
- design_versions:
- actions:
- :design # Duplicate export of issues.designs in order to link the record to both Issue and Action
- epic_issue:
- :epic
- protected_branches:
@ -394,6 +395,3 @@ ee:
- protected_environments:
- :deploy_access_levels
- :service_desk_setting
excluded_attributes:
actions:
- image_v432x230

View file

@ -57,6 +57,8 @@ module Gitlab
# Returns Arel clause for a particular model or `nil`.
def where_clause_for_klass
return attrs_to_arel(attributes.slice('filename')).and(table[:issue_id].eq(nil)) if design?
attrs_to_arel(attributes.slice('iid')) if merge_request?
end
@ -95,6 +97,10 @@ module Gitlab
klass == Epic
end
def design?
klass == DesignManagement::Design
end
# If an existing group milestone used the IID
# claim the IID back and set the group milestone to use one available
# This is necessary to fix situations like the following:
@ -115,5 +121,3 @@ module Gitlab
end
end
end
Gitlab::ImportExport::Project::ObjectBuilder.prepend_if_ee('EE::Gitlab::ImportExport::Project::ObjectBuilder')

View file

@ -17,6 +17,10 @@ module Gitlab
merge_access_levels: 'ProtectedBranch::MergeAccessLevel',
push_access_levels: 'ProtectedBranch::PushAccessLevel',
create_access_levels: 'ProtectedTag::CreateAccessLevel',
design: 'DesignManagement::Design',
designs: 'DesignManagement::Design',
design_versions: 'DesignManagement::Version',
actions: 'DesignManagement::Action',
labels: :project_labels,
priorities: :label_priorities,
auto_devops: :project_auto_devops,
@ -51,6 +55,7 @@ module Gitlab
container_expiration_policy
external_pull_request
external_pull_requests
DesignManagement::Design
].freeze
def create

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Gitlab
module Logging
module CloudflareHelper
CLOUDFLARE_CUSTOM_HEADERS = { 'Cf-Ray' => :cf_ray, 'Cf-Request-Id' => :cf_request_id }.freeze
def store_cloudflare_headers!(payload, request)
CLOUDFLARE_CUSTOM_HEADERS.each do |header, value|
payload[value] = request.headers[header] if valid_cloudflare_header?(request.headers[header])
end
end
def valid_cloudflare_header?(value)
return false unless value.present?
return false if value.length > 64
return false if value.index(/[^[:alnum:]]/)
true
end
end
end
end

View file

@ -3,6 +3,8 @@
module Gitlab
module Lograge
module CustomOptions
include ::Gitlab::Logging::CloudflareHelper
LIMITED_ARRAY_SENTINEL = { key: 'truncated', value: '...' }.freeze
IGNORE_PARAMS = Set.new(%w(controller action format)).freeze
@ -31,6 +33,10 @@ module Gitlab
payload[:cpu_s] = cpu_s.round(2)
end
CLOUDFLARE_CUSTOM_HEADERS.each do |_, value|
payload[value] = event.payload[value] if event.payload[value]
end
# https://github.com/roidrage/lograge#logging-errors--exceptions
exception = event.payload[:exception_object]

View file

@ -15,6 +15,7 @@ module Gitlab
%w(FileUploader Project),
%w(PersonalFileUploader Snippet),
%w(NamespaceFileUploader Snippet),
%w(DesignManagement::DesignV432x230Uploader DesignManagement::Action :image_v432x230),
%w(FileUploader MergeRequest)].freeze
def initialize(args, logger)
@ -74,5 +75,3 @@ module Gitlab
end
end
end
Gitlab::Uploads::MigrationHelper.prepend_if_ee('EE::Gitlab::Uploads::MigrationHelper')

View file

@ -11,6 +11,7 @@ module Gitlab
class << self
include ActionView::RecordIdentifier
# rubocop:disable Metrics/CyclomaticComplexity
def build(object, **options)
# Objects are sometimes wrapped in a BatchLoader instance
case object.itself
@ -38,10 +39,13 @@ module Gitlab
wiki_url(object, **options)
when WikiPage
instance.project_wiki_url(object.wiki.project, object.slug, **options)
when ::DesignManagement::Design
design_url(object, **options)
else
raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
end
end
# rubocop:enable Metrics/CyclomaticComplexity
def commit_url(commit, **options)
return '' unless commit.project
@ -78,6 +82,17 @@ module Gitlab
raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
end
end
def design_url(design, **options)
size, ref = options.values_at(:size, :ref)
options.except!(:size, :ref)
if size
instance.project_design_management_designs_resized_image_url(design.project, design, ref, size, **options)
else
instance.project_design_management_designs_raw_image_url(design.project, design, ref, **options)
end
end
end
end
end

View file

@ -223,7 +223,8 @@ module Gitlab
Gitlab::UsageDataCounters::CycleAnalyticsCounter,
Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
Gitlab::UsageDataCounters::SourceCodeCounter,
Gitlab::UsageDataCounters::MergeRequestCounter
Gitlab::UsageDataCounters::MergeRequestCounter,
Gitlab::UsageDataCounters::DesignsCounter
]
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
module Gitlab::UsageDataCounters
class DesignsCounter
extend Gitlab::UsageDataCounters::RedisCounter
KNOWN_EVENTS = %w[create update delete].map(&:freeze).freeze
UnknownEvent = Class.new(StandardError)
class << self
# Each event gets a unique Redis key
def redis_key(event)
raise UnknownEvent, event unless KNOWN_EVENTS.include?(event.to_s)
"USAGE_DESIGN_MANAGEMENT_DESIGNS_#{event}".upcase
end
def count(event)
increment(redis_key(event))
end
def read(event)
total_count(redis_key(event))
end
def totals
KNOWN_EVENTS.map { |event| [counter_key(event), read(event)] }.to_h
end
def fallback_totals
KNOWN_EVENTS.map { |event| [counter_key(event), -1] }.to_h
end
private
def counter_key(event)
"design_management_designs_#{event}".to_sym
end
end
end
end

View file

@ -16804,12 +16804,18 @@ msgstr ""
msgid "Promotions|This feature is locked."
msgstr ""
msgid "Promotions|Track activity with Contribution Analytics."
msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
msgid "Promotions|Upgrade your plan"
msgstr ""
msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
msgstr ""
msgid "Promotions|Weight"
msgstr ""
@ -16819,6 +16825,9 @@ msgstr ""
msgid "Promotions|When you have a lot of issues, it can be hard to get an overview. By adding a weight to your issues, you can get a better idea of the effort, cost, required time, or value of each, and so better manage them."
msgstr ""
msgid "Promotions|With Contribution Analytics you can have an overview for the activity of issues, merge requests, and push events of your organization and its members."
msgstr ""
msgid "Prompt users to upload SSH keys"
msgstr ""
@ -22284,9 +22293,6 @@ msgstr ""
msgid "Tracing"
msgstr ""
msgid "Track activity with Contribution Analytics."
msgstr ""
msgid "Track groups of issues that share a theme, across projects and milestones"
msgstr ""
@ -22791,9 +22797,6 @@ msgstr ""
msgid "Upgrade your plan to activate Audit Events."
msgstr ""
msgid "Upgrade your plan to activate Contribution Analytics."
msgstr ""
msgid "Upgrade your plan to activate Group Webhooks."
msgstr ""
@ -24039,9 +24042,6 @@ msgstr ""
msgid "Will deploy to"
msgstr ""
msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
msgstr ""
msgid "Withdraw Access Request"
msgstr ""

View file

@ -0,0 +1,517 @@
{
"description":"",
"visibility_level":0,
"archived":false,
"merge_requests_template":null,
"merge_requests_rebase_enabled":false,
"approvals_before_merge":0,
"reset_approvals_on_push":true,
"merge_requests_ff_only_enabled":false,
"issues_template":null,
"shared_runners_enabled":true,
"build_coverage_regex":null,
"build_allow_git_fetch":true,
"build_timeout":3600,
"pending_delete":false,
"public_builds":true,
"last_repository_check_failed":null,
"container_registry_enabled":true,
"only_allow_merge_if_pipeline_succeeds":false,
"has_external_issue_tracker":false,
"request_access_enabled":false,
"has_external_wiki":false,
"ci_config_path":null,
"only_allow_merge_if_all_discussions_are_resolved":false,
"repository_size_limit":null,
"printing_merge_request_link_enabled":true,
"auto_cancel_pending_pipelines":"enabled",
"service_desk_enabled":null,
"delete_error":null,
"disable_overriding_approvers_per_merge_request":null,
"resolve_outdated_diff_discussions":false,
"jobs_cache_index":null,
"external_authorization_classification_label":null,
"pages_https_only":false,
"external_webhook_token":null,
"merge_requests_author_approval":null,
"merge_requests_disable_committers_approval":null,
"require_password_to_approve":null,
"labels":[
],
"milestones":[
],
"issues":[
{
"id":469,
"title":"issue 1",
"author_id":1,
"project_id":30,
"created_at":"2019-08-07T03:57:55.007Z",
"updated_at":"2019-08-07T03:57:55.007Z",
"description":"",
"state":"opened",
"iid":1,
"updated_by_id":null,
"weight":null,
"confidential":false,
"due_date":null,
"moved_to_id":null,
"lock_version":0,
"time_estimate":0,
"relative_position":1073742323,
"service_desk_reply_to":null,
"last_edited_at":null,
"last_edited_by_id":null,
"discussion_locked":null,
"closed_at":null,
"closed_by_id":null,
"state_id":1,
"events":[
{
"id":1775,
"project_id":30,
"author_id":1,
"target_id":469,
"created_at":"2019-08-07T03:57:55.158Z",
"updated_at":"2019-08-07T03:57:55.158Z",
"target_type":"Issue",
"action":1
}
],
"timelogs":[
],
"notes":[
],
"label_links":[
],
"resource_label_events":[
],
"issue_assignees":[
],
"designs":[
{
"id":38,
"project_id":30,
"issue_id":469,
"filename":"chirrido3.jpg",
"notes":[
]
},
{
"id":39,
"project_id":30,
"issue_id":469,
"filename":"jonathan_richman.jpg",
"notes":[
]
},
{
"id":40,
"project_id":30,
"issue_id":469,
"filename":"mariavontrap.jpeg",
"notes":[
]
}
],
"design_versions":[
{
"id":24,
"sha":"9358d1bac8ff300d3d2597adaa2572a20f7f8703",
"issue_id":469,
"author_id":1,
"actions":[
{
"design_id":38,
"version_id":24,
"event":0,
"design":{
"id":38,
"project_id":30,
"issue_id":469,
"filename":"chirrido3.jpg"
}
}
]
},
{
"id":25,
"sha":"e1a4a501bcb42f291f84e5d04c8f927821542fb6",
"issue_id":469,
"author_id":2,
"actions":[
{
"design_id":38,
"version_id":25,
"event":1,
"design":{
"id":38,
"project_id":30,
"issue_id":469,
"filename":"chirrido3.jpg"
}
},
{
"design_id":39,
"version_id":25,
"event":0,
"design":{
"id":39,
"project_id":30,
"issue_id":469,
"filename":"jonathan_richman.jpg"
}
}
]
},
{
"id":26,
"sha":"27702d08f5ee021ae938737f84e8fe7c38599e85",
"issue_id":469,
"author_id":1,
"actions":[
{
"design_id":38,
"version_id":26,
"event":1,
"design":{
"id":38,
"project_id":30,
"issue_id":469,
"filename":"chirrido3.jpg"
}
},
{
"design_id":39,
"version_id":26,
"event":2,
"design":{
"id":39,
"project_id":30,
"issue_id":469,
"filename":"jonathan_richman.jpg"
}
},
{
"design_id":40,
"version_id":26,
"event":0,
"design":{
"id":40,
"project_id":30,
"issue_id":469,
"filename":"mariavontrap.jpeg"
}
}
]
}
]
},
{
"id":470,
"title":"issue 2",
"author_id":1,
"project_id":30,
"created_at":"2019-08-07T04:15:57.607Z",
"updated_at":"2019-08-07T04:15:57.607Z",
"description":"",
"state":"opened",
"iid":2,
"updated_by_id":null,
"weight":null,
"confidential":false,
"due_date":null,
"moved_to_id":null,
"lock_version":0,
"time_estimate":0,
"relative_position":1073742823,
"service_desk_reply_to":null,
"last_edited_at":null,
"last_edited_by_id":null,
"discussion_locked":null,
"closed_at":null,
"closed_by_id":null,
"state_id":1,
"events":[
{
"id":1776,
"project_id":30,
"author_id":1,
"target_id":470,
"created_at":"2019-08-07T04:15:57.789Z",
"updated_at":"2019-08-07T04:15:57.789Z",
"target_type":"Issue",
"action":1
}
],
"timelogs":[
],
"notes":[
],
"label_links":[
],
"resource_label_events":[
],
"issue_assignees":[
],
"designs":[
{
"id":42,
"project_id":30,
"issue_id":470,
"filename":"1 (1).jpeg",
"notes":[
]
},
{
"id":43,
"project_id":30,
"issue_id":470,
"filename":"2099743.jpg",
"notes":[
]
},
{
"id":44,
"project_id":30,
"issue_id":470,
"filename":"a screenshot (1).jpg",
"notes":[
]
},
{
"id":41,
"project_id":30,
"issue_id":470,
"filename":"chirrido3.jpg",
"notes":[
]
}
],
"design_versions":[
{
"id":27,
"sha":"8587e78ab6bda3bc820a9f014c3be4a21ad4fcc8",
"issue_id":470,
"author_id":1,
"actions":[
{
"design_id":41,
"version_id":27,
"event":0,
"design":{
"id":41,
"project_id":30,
"issue_id":470,
"filename":"chirrido3.jpg"
}
}
]
},
{
"id":28,
"sha":"73f871b4c8c1d65c62c460635e023179fb53abc4",
"issue_id":470,
"author_id":2,
"actions":[
{
"design_id":42,
"version_id":28,
"event":0,
"design":{
"id":42,
"project_id":30,
"issue_id":470,
"filename":"1 (1).jpeg"
}
},
{
"design_id":43,
"version_id":28,
"event":0,
"design":{
"id":43,
"project_id":30,
"issue_id":470,
"filename":"2099743.jpg"
}
}
]
},
{
"id":29,
"sha":"c9b5f067f3e892122a4b12b0a25a8089192f3ac8",
"issue_id":470,
"author_id":2,
"actions":[
{
"design_id":42,
"version_id":29,
"event":1,
"design":{
"id":42,
"project_id":30,
"issue_id":470,
"filename":"1 (1).jpeg"
}
},
{
"design_id":44,
"version_id":29,
"event":0,
"design":{
"id":44,
"project_id":30,
"issue_id":470,
"filename":"a screenshot (1).jpg"
}
}
]
}
]
}
],
"snippets":[
],
"releases":[
],
"project_members":[
{
"id":95,
"access_level":40,
"source_id":30,
"source_type":"Project",
"user_id":1,
"notification_level":3,
"created_at":"2019-08-07T03:57:32.825Z",
"updated_at":"2019-08-07T03:57:32.825Z",
"created_by_id":1,
"invite_email":null,
"invite_token":null,
"invite_accepted_at":null,
"requested_at":null,
"expires_at":null,
"ldap":false,
"override":false,
"user":{
"id":1,
"email":"admin@example.com",
"username":"root"
}
},
{
"id":96,
"access_level":40,
"source_id":30,
"source_type":"Project",
"user_id":2,
"notification_level":3,
"created_at":"2019-08-07T03:57:32.825Z",
"updated_at":"2019-08-07T03:57:32.825Z",
"created_by_id":null,
"invite_email":null,
"invite_token":null,
"invite_accepted_at":null,
"requested_at":null,
"expires_at":null,
"ldap":false,
"override":false,
"user":{
"id":2,
"email":"user_2@gitlabexample.com",
"username":"user_2"
}
}
],
"merge_requests":[
],
"ci_pipelines":[
],
"triggers":[
],
"pipeline_schedules":[
],
"services":[
],
"protected_branches":[
],
"protected_environments": [
{
"id": 14,
"created_at": "2017-10-19T15:36:23.466Z",
"updated_at": "2017-10-19T15:36:23.466Z",
"name": "production",
"deploy_access_levels": [
{
"id": 21,
"created_at": "2017-10-19T15:36:23.466Z",
"updated_at": "2017-10-19T15:36:23.466Z",
"access_level": 40,
"user_id": 1,
"group_id": null
}
]
}
],
"protected_tags":[
],
"project_feature":{
"id":30,
"project_id":30,
"merge_requests_access_level":20,
"issues_access_level":20,
"wiki_access_level":20,
"snippets_access_level":20,
"builds_access_level":20,
"created_at":"2019-08-07T03:57:32.485Z",
"updated_at":"2019-08-07T03:57:32.485Z",
"repository_access_level":20,
"pages_access_level":10
},
"custom_attributes":[
],
"prometheus_metrics":[
],
"project_badges":[
],
"ci_cd_settings":{
"group_runners_enabled":true
},
"boards":[
],
"pipelines":[
]
}

View file

@ -6,7 +6,7 @@ describe Mutations::AlertManagement::UpdateAlertStatus do
let_it_be(:current_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, status: 'triggered') }
let_it_be(:project) { alert.project }
let(:new_status) { 'acknowledged' }
let(:new_status) { Types::AlertManagement::StatusEnum.values['ACKNOWLEDGED'].value }
let(:args) { { status: new_status, project_path: project.full_path, iid: alert.iid } }
specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'spec_helper'
describe API::Entities::DesignManagement::Design do
let_it_be(:design) { create(:design) }
let(:entity) { described_class.new(design, request: double) }
subject { entity.as_json }
it 'has the correct attributes' do
expect(subject).to eq({
id: design.id,
project_id: design.project_id,
filename: design.filename,
image_url: ::Gitlab::UrlBuilder.build(design)
})
end
end

View file

@ -4,6 +4,7 @@ require 'spec_helper'
describe Banzai::Filter::IssueReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
def helper
IssuesHelper
@ -358,6 +359,23 @@ describe Banzai::Filter::IssueReferenceFilter do
end
end
context 'when processing a link to the designs tab' do
let(:designs_tab_url) { url_for_designs(issue) }
let(:input_text) { "See #{designs_tab_url}" }
subject(:link) { reference_filter(input_text).css('a').first }
before do
enable_design_management
end
it 'includes the word "designs" after the reference in the text content', :aggregate_failures do
expect(link.attr('title')).to eq(issue.title)
expect(link.attr('href')).to eq(designs_tab_url)
expect(link.text).to eq("#{issue.to_reference} (designs)")
end
end
context 'group context' do
let(:group) { create(:group) }
let(:context) { { project: nil, group: group } }
@ -467,4 +485,41 @@ describe Banzai::Filter::IssueReferenceFilter do
end.not_to yield_control
end
end
describe '#object_link_text_extras' do
before do
enable_design_management(enabled)
end
let(:current_user) { project.owner }
let(:enabled) { true }
let(:matches) { Issue.link_reference_pattern.match(input_text) }
let(:extras) { subject.object_link_text_extras(issue, matches) }
subject { filter_instance }
context 'the link does not go to the designs tab' do
let(:input_text) { Gitlab::Routing.url_helpers.project_issue_url(issue.project, issue) }
it 'does not include designs' do
expect(extras).not_to include('designs')
end
end
context 'the link goes to the designs tab' do
let(:input_text) { url_for_designs(issue) }
it 'includes designs' do
expect(extras).to include('designs')
end
context 'design management is disabled' do
let(:enabled) { false }
it 'does not include designs in the extras' do
expect(extras).not_to include('designs')
end
end
end
end
end

View file

@ -0,0 +1,91 @@
# frozen_string_literal: true
require 'spec_helper'
describe Banzai::ReferenceParser::DesignParser do
include ReferenceParserHelpers
include DesignManagementTestHelpers
let_it_be(:issue) { create(:issue) }
let_it_be(:design) { create(:design, :with_versions, issue: issue) }
let_it_be(:user) { create(:user, developer_projects: [issue.project]) }
subject(:instance) { described_class.new(Banzai::RenderContext.new(issue.project, user)) }
let(:link) { design_link(design) }
before do
enable_design_management
end
describe '#nodes_visible_to_user' do
it_behaves_like 'referenced feature visibility', 'issues' do
let(:project) { issue.project }
end
describe 'specific states' do
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:other_project_link) do
design_link(create(:design, :with_versions))
end
let_it_be(:public_link) do
design_link(create(:design, :with_versions, issue: create(:issue, project: public_project)))
end
let_it_be(:public_but_confidential_link) do
design_link(create(:design, :with_versions, issue: create(:issue, :confidential, project: public_project)))
end
subject(:visible_nodes) do
nodes = [link,
other_project_link,
public_link,
public_but_confidential_link]
instance.nodes_visible_to_user(user, nodes)
end
it 'redacts links we should not have access to' do
expect(visible_nodes).to contain_exactly(link, public_link)
end
context 'design management is not available' do
before do
enable_design_management(false)
end
it 'redacts all nodes' do
expect(visible_nodes).to be_empty
end
end
end
end
describe '#process' do
it 'returns the correct designs' do
frag = document([design, create(:design, :with_versions)])
expect(subject.process([frag])[:visible]).to contain_exactly(design)
end
end
def design_link(design)
node = empty_html_link
node['class'] = 'gfm'
node['data-reference-type'] = 'design'
node['data-project'] = design.project.id.to_s
node['data-issue'] = design.issue.id.to_s
node['data-design'] = design.id.to_s
node
end
def document(designs)
frag = Nokogiri::HTML.fragment('')
designs.each do |design|
frag.add_child(design_link(design))
end
frag
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::Median do
let_it_be(:project) { create(:project, :repository) }
let(:query) { Project.joins(merge_requests: :metrics) }
let(:stage) do
build(
:cycle_analytics_project_stage,
start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated.identifier,
end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged.identifier,
project: project
)
end
subject { described_class.new(stage: stage, query: query).seconds }
around do |example|
Timecop.freeze { example.run }
end
it 'retruns nil when no results' do
expect(subject).to eq(nil)
end
it 'returns median duration seconds as float' do
merge_request1 = create(:merge_request, source_branch: '1', target_project: project, source_project: project)
merge_request2 = create(:merge_request, source_branch: '2', target_project: project, source_project: project)
Timecop.travel(5.minutes.from_now) do
merge_request1.metrics.update!(merged_at: Time.zone.now)
end
Timecop.travel(10.minutes.from_now) do
merge_request2.metrics.update!(merged_at: Time.zone.now)
end
expect(subject).to be_within(0.5).of(7.5.minutes.seconds)
end
end

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::GrapeLogging::Loggers::CloudflareLogger do
subject { described_class.new }
describe "#parameters" do
let(:mock_request) { ActionDispatch::Request.new({}) }
let(:start_time) { Time.new(2018, 01, 01) }
describe 'with no Cloudflare headers' do
it 'returns an empty hash' do
expect(subject.parameters(mock_request, nil)).to eq({})
end
end
describe 'with Cloudflare headers' do
before do
mock_request.headers['Cf-Ray'] = SecureRandom.hex
mock_request.headers['Cf-Request-Id'] = SecureRandom.hex
end
it 'returns the correct duration in seconds' do
data = subject.parameters(mock_request, nil)
expect(data.keys).to contain_exactly(:cf_ray, :cf_request_id)
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::ImportExport::DesignRepoRestorer do
include GitHelpers
describe 'bundle a design Git repo' do
let(:user) { create(:user) }
let!(:project_with_design_repo) { create(:project, :design_repo) }
let!(:project) { create(:project) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:bundler) { Gitlab::ImportExport::DesignRepoSaver.new(project: project_with_design_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.design_repo_bundle_filename) }
let(:restorer) do
described_class.new(path_to_bundle: bundle_path,
shared: shared,
project: project)
end
before do
allow_next_instance_of(Gitlab::ImportExport) do |instance|
allow(instance).to receive(:storage_path).and_return(export_path)
end
bundler.save
end
after do
FileUtils.rm_rf(export_path)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
FileUtils.rm_rf(project_with_design_repo.design_repository.path_to_repo)
FileUtils.rm_rf(project.design_repository.path_to_repo)
end
end
it 'restores the repo successfully' do
expect(restorer.restore).to eq(true)
end
end
end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::ImportExport::DesignRepoSaver do
describe 'bundle a design Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:design) { create(:design, :with_file, versions_count: 1) }
let!(:project) { create(:project, :design_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:design_bundler) { described_class.new(project: project, shared: shared) }
before do
project.add_maintainer(user)
allow_next_instance_of(Gitlab::ImportExport) do |instance|
allow(instance).to receive(:storage_path).and_return(export_path)
end
end
after do
FileUtils.rm_rf(export_path)
end
it 'bundles the repo successfully' do
expect(design_bundler.save).to be true
end
context 'when the repo is empty' do
let!(:project) { create(:project) }
it 'bundles the repo successfully' do
expect(design_bundler.save).to be true
end
end
end
end

View file

@ -53,22 +53,15 @@ describe 'Test coverage of the Project Import' do
].freeze
# A list of JSON fixture files we use to test Import.
# Note that we use separate fixture to test ee-only features.
# Most of the relations are present in `complex/project.json`
# which is our main fixture.
PROJECT_JSON_FIXTURES_EE =
if Gitlab.ee?
['ee/spec/fixtures/lib/gitlab/import_export/designs/project.json'].freeze
else
[]
end
PROJECT_JSON_FIXTURES = [
'spec/fixtures/lib/gitlab/import_export/complex/project.json',
'spec/fixtures/lib/gitlab/import_export/group/project.json',
'spec/fixtures/lib/gitlab/import_export/light/project.json',
'spec/fixtures/lib/gitlab/import_export/milestone-iid/project.json'
].freeze + PROJECT_JSON_FIXTURES_EE
'spec/fixtures/lib/gitlab/import_export/milestone-iid/project.json',
'spec/fixtures/lib/gitlab/import_export/designs/project.json'
].freeze
it 'ensures that all imported/exported relations are present in test JSONs' do
not_tested_relations = (relations_from_config - tested_relations) - MUTED_RELATIONS

View file

@ -51,7 +51,8 @@ describe Gitlab::ImportExport::Importer do
Gitlab::ImportExport::UploadsRestorer,
Gitlab::ImportExport::LfsRestorer,
Gitlab::ImportExport::StatisticsRestorer,
Gitlab::ImportExport::SnippetsRepoRestorer
Gitlab::ImportExport::SnippetsRepoRestorer,
Gitlab::ImportExport::DesignRepoRestorer
].each do |restorer|
it "calls the #{restorer}" do
fake_restorer = double(restorer.to_s)

View file

@ -8,6 +8,7 @@ end
describe Gitlab::ImportExport::Project::TreeRestorer do
include ImportExport::CommonUtil
using RSpec::Parameterized::TableSyntax
let(:shared) { project.import_export_shared }
@ -987,6 +988,69 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
end
end
end
context 'JSON with design management data' do
let_it_be(:user) { create(:admin, email: 'user_1@gitlabexample.com') }
let_it_be(:second_user) { create(:user, email: 'user_2@gitlabexample.com') }
let_it_be(:project) do
create(:project, :builds_disabled, :issues_disabled,
{ name: 'project', path: 'project' })
end
let(:shared) { project.import_export_shared }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
subject(:restored_project_json) { project_tree_restorer.restore }
before do
setup_import_export_config('designs')
restored_project_json
end
it_behaves_like 'restores project successfully', issues: 2
it 'restores project associations correctly' do
expect(project.designs.size).to eq(7)
end
describe 'restores issue associations correctly' do
let(:issue) { project.issues.offset(index).first }
where(:index, :design_filenames, :version_shas, :events, :author_emails) do
0 | %w[chirrido3.jpg jonathan_richman.jpg mariavontrap.jpeg] | %w[27702d08f5ee021ae938737f84e8fe7c38599e85 9358d1bac8ff300d3d2597adaa2572a20f7f8703 e1a4a501bcb42f291f84e5d04c8f927821542fb6] | %w[creation creation creation modification modification deletion] | %w[user_1@gitlabexample.com user_1@gitlabexample.com user_2@gitlabexample.com]
1 | ['1 (1).jpeg', '2099743.jpg', 'a screenshot (1).jpg', 'chirrido3.jpg'] | %w[73f871b4c8c1d65c62c460635e023179fb53abc4 8587e78ab6bda3bc820a9f014c3be4a21ad4fcc8 c9b5f067f3e892122a4b12b0a25a8089192f3ac8] | %w[creation creation creation creation modification] | %w[user_1@gitlabexample.com user_2@gitlabexample.com user_2@gitlabexample.com]
end
with_them do
it do
expect(issue.designs.pluck(:filename)).to contain_exactly(*design_filenames)
expect(issue.design_versions.pluck(:sha)).to contain_exactly(*version_shas)
expect(issue.design_versions.flat_map(&:actions).map(&:event)).to contain_exactly(*events)
expect(issue.design_versions.map(&:author).map(&:email)).to contain_exactly(*author_emails)
end
end
end
describe 'restores design version associations correctly' do
let(:project_designs) { project.designs.reorder(:filename, :issue_id) }
let(:design) { project_designs.offset(index).first }
where(:index, :version_shas) do
0 | %w[73f871b4c8c1d65c62c460635e023179fb53abc4 c9b5f067f3e892122a4b12b0a25a8089192f3ac8]
1 | %w[73f871b4c8c1d65c62c460635e023179fb53abc4]
2 | %w[c9b5f067f3e892122a4b12b0a25a8089192f3ac8]
3 | %w[27702d08f5ee021ae938737f84e8fe7c38599e85 9358d1bac8ff300d3d2597adaa2572a20f7f8703 e1a4a501bcb42f291f84e5d04c8f927821542fb6]
4 | %w[8587e78ab6bda3bc820a9f014c3be4a21ad4fcc8]
5 | %w[27702d08f5ee021ae938737f84e8fe7c38599e85 e1a4a501bcb42f291f84e5d04c8f927821542fb6]
6 | %w[27702d08f5ee021ae938737f84e8fe7c38599e85]
end
with_them do
it do
expect(design.versions.pluck(:sha)).to contain_exactly(*version_shas)
end
end
end
end
end
context 'enable ndjson import' do

View file

@ -168,6 +168,28 @@ describe Gitlab::ImportExport::Project::TreeSaver do
it 'has issue resource label events' do
expect(subject.first['resource_label_events']).not_to be_empty
end
it 'saves the issue designs correctly' do
expect(subject.first['designs'].size).to eq(1)
end
it 'saves the issue design notes correctly' do
expect(subject.first['designs'].first['notes']).not_to be_empty
end
it 'saves the issue design versions correctly' do
issue_json = subject.first
actions = issue_json['design_versions'].flat_map { |v| v['actions'] }
expect(issue_json['design_versions'].size).to eq(2)
issue_json['design_versions'].each do |version|
expect(version['author_id']).to be_kind_of(Integer)
end
expect(actions.size).to eq(2)
actions.each do |action|
expect(action['design']).to be_present
end
end
end
context 'with ci_pipelines' do
@ -442,6 +464,9 @@ describe Gitlab::ImportExport::Project::TreeSaver do
board = create(:board, project: project, name: 'TestBoard')
create(:list, board: board, position: 0, label: project_label)
design = create(:design, :with_file, versions_count: 2, issue: issue)
create(:diff_note_on_design, noteable: design, project: project, author: user)
project
end
end

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Logging::CloudflareHelper do
let(:helper) do
Class.new do
include Gitlab::Logging::CloudflareHelper
end.new
end
describe '#store_cloudflare_headers!' do
let(:payload) { {} }
let(:env) { {} }
let(:request) { ActionDispatch::Request.new(env) }
before do
request.headers.merge!(headers)
end
context 'with normal headers' do
let(:headers) { { 'Cf-Ray' => SecureRandom.hex, 'Cf-Request-Id' => SecureRandom.hex } }
it 'adds Cf-Ray-Id and Cf-Request-Id' do
helper.store_cloudflare_headers!(payload, request)
expect(payload[:cf_ray]).to eq(headers['Cf-Ray'])
expect(payload[:cf_request_id]).to eq(headers['Cf-Request-Id'])
end
end
context 'with header values with long strings' do
let(:headers) { { 'Cf-Ray' => SecureRandom.hex(33), 'Cf-Request-Id' => SecureRandom.hex(33) } }
it 'filters invalid header values' do
helper.store_cloudflare_headers!(payload, request)
expect(payload.keys).not_to include(:cf_ray, :cf_request_id)
end
end
context 'with header values with non-alphanumeric characters' do
let(:headers) { { 'Cf-Ray' => "Bad\u0000ray", 'Cf-Request-Id' => "Bad\u0000req" } }
it 'filters invalid header values' do
helper.store_cloudflare_headers!(payload, request)
expect(payload.keys).not_to include(:cf_ray, :cf_request_id)
end
end
end
end

View file

@ -19,7 +19,12 @@ describe Gitlab::Lograge::CustomOptions do
1,
2,
'transaction_id',
{ params: params, user_id: 'test' }
{
params: params,
user_id: 'test',
cf_ray: SecureRandom.hex,
cf_request_id: SecureRandom.hex
}
)
end
@ -46,5 +51,10 @@ describe Gitlab::Lograge::CustomOptions do
it 'adds the user id' do
expect(subject[:user_id]).to eq('test')
end
it 'adds Cloudflare headers' do
expect(subject[:cf_ray]).to eq(event.payload[:cf_ray])
expect(subject[:cf_request_id]).to eq(event.payload[:cf_request_id])
end
end
end

View file

@ -25,6 +25,7 @@ describe Gitlab::UrlBuilder do
:project_snippet | ->(snippet) { "/#{snippet.project.full_path}/snippets/#{snippet.id}" }
:project_wiki | ->(wiki) { "/#{wiki.container.full_path}/-/wikis/home" }
:ci_build | ->(build) { "/#{build.project.full_path}/-/jobs/#{build.id}" }
:design | ->(design) { "/#{design.project.full_path}/-/design_management/designs/#{design.id}/raw_image" }
:group | ->(group) { "/groups/#{group.full_path}" }
:group_milestone | ->(milestone) { "/groups/#{milestone.group.full_path}/-/milestones/#{milestone.iid}" }
@ -95,6 +96,16 @@ describe Gitlab::UrlBuilder do
end
end
context 'when passing a DesignManagement::Design' do
let(:design) { build_stubbed(:design) }
it 'uses the given ref and size in the URL' do
url = subject.build(design, ref: 'feature', size: 'small')
expect(url).to eq "#{Settings.gitlab['url']}/#{design.project.full_path}/-/design_management/designs/#{design.id}/feature/resized_image/small"
end
end
context 'when passing an unsupported class' do
let(:object) { Object.new }

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::UsageDataCounters::DesignsCounter do
it_behaves_like 'a redis usage counter', 'Designs', :create
it_behaves_like 'a redis usage counter', 'Designs', :update
it_behaves_like 'a redis usage counter', 'Designs', :delete
it_behaves_like 'a redis usage counter with totals', :design_management_designs,
create: 5,
update: 3,
delete: 2
end

View file

@ -712,6 +712,29 @@ describe Notify do
end
end
describe 'for design notes' do
let_it_be(:design) { create(:design, :with_file) }
let_it_be(:recipient) { create(:user) }
let_it_be(:note) do
create(:diff_note_on_design,
noteable: design,
note: "Hello #{recipient.to_reference}")
end
let(:header_name) { 'X-Gitlab-DesignManagement-Design-ID' }
let(:refer_to_design) do
have_attributes(subject: a_string_including(design.filename))
end
subject { described_class.note_design_email(recipient.id, note.id) }
it { is_expected.to have_header(header_name, design.id.to_s) }
it { is_expected.to have_body_text(design.filename) }
it { is_expected.to refer_to_design }
end
describe 'project was moved' do
let(:recipient) { user }

View file

@ -16,6 +16,7 @@ describe API::Branches do
before do
project.add_maintainer(user)
project.repository.add_branch(user, 'ends-with.txt', branch_sha)
end
describe "GET /projects/:id/repository/branches" do
@ -240,6 +241,12 @@ describe API::Branches do
it_behaves_like 'repository branch'
end
context 'when branch contains dot txt' do
let(:branch_name) { project.repository.find_branch('ends-with.txt').name }
it_behaves_like 'repository branch'
end
context 'when branch contains a slash' do
let(:branch_name) { branch_with_slash.name }

View file

@ -159,6 +159,46 @@ describe API::Todos do
expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control)
expect(response).to have_gitlab_http_status(:ok)
end
context 'when there is a Design Todo' do
let!(:design_todo) { create_todo_for_mentioned_in_design }
def create_todo_for_mentioned_in_design
issue = create(:issue, project: project_1)
create(:todo, :mentioned,
user: john_doe,
project: project_1,
target: create(:design, issue: issue),
author: create(:user),
note: create(:note, project: project_1, note: "I am note, hear me roar"))
end
def api_request
get api('/todos', john_doe)
end
before do
api_request
end
specify do
expect(response).to have_gitlab_http_status(:ok)
end
it 'avoids N+1 queries', :request_store do
control = ActiveRecord::QueryRecorder.new { api_request }
create_todo_for_mentioned_in_design
expect { api_request }.not_to exceed_query_limit(control)
end
it 'includes the Design Todo in the response' do
expect(json_response).to include(
a_hash_including('id' => design_todo.id)
)
end
end
end
describe 'POST /todos/:id/mark_as_done' do

View file

@ -8,7 +8,7 @@ describe AlertManagement::UpdateAlertStatusService do
describe '#execute' do
subject(:execute) { described_class.new(alert, new_status).execute }
let(:new_status) { 'acknowledged' }
let(:new_status) { Types::AlertManagement::StatusEnum.values['ACKNOWLEDGED'].value }
it 'updates the status' do
expect { execute }.to change { alert.acknowledged? }.to(true)

View file

@ -46,8 +46,8 @@ describe Projects::ImportExport::ExportService do
# in the corresponding EE spec.
skip if Gitlab.ee?
# once for the normal repo, once for the wiki
expect(Gitlab::ImportExport::RepoSaver).to receive(:new).twice.and_call_original
# once for the normal repo, once for the wiki repo, and once for the design repo
expect(Gitlab::ImportExport::RepoSaver).to receive(:new).exactly(3).times.and_call_original
service.execute
end
@ -58,6 +58,12 @@ describe Projects::ImportExport::ExportService do
service.execute
end
it 'saves the design repo' do
expect(Gitlab::ImportExport::DesignRepoSaver).to receive(:new).and_call_original
service.execute
end
it 'saves the lfs objects' do
expect(Gitlab::ImportExport::LfsSaver).to receive(:new).and_call_original

View file

@ -19,7 +19,9 @@ module ActiveRecord
def show_backtrace(values)
Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}")
Gitlab::BacktraceCleaner.clean_backtrace(caller).each { |line| Rails.logger.debug(" --> #{line}") }
Gitlab::BacktraceCleaner.clean_backtrace(caller).each do |line|
Rails.logger.debug("QueryRecorder backtrace: --> #{line}")
end
end
def get_sql_source(sql)

View file

@ -19,6 +19,9 @@ module UsageDataHelpers
cycle_analytics_views
productivity_analytics_views
source_code_pushes
design_management_designs_create
design_management_designs_update
design_management_designs_delete
).freeze
COUNTS_KEYS = %i(

View file

@ -63,7 +63,7 @@ shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend exampl
context 'provides the same results as the old implementation' do
it 'for the median' do
expect(data_collector.median.seconds).to eq(ISSUES_MEDIAN)
expect(data_collector.median.seconds).to be_within(0.5).of(ISSUES_MEDIAN)
end
it 'for the list of event records' do

View file

@ -0,0 +1,94 @@
# frozen_string_literal: true
require 'spec_helper'
describe DesignManagement::NewVersionWorker do
# TODO a number of these tests are being temporarily skipped unless run in EE,
# as we are in the process of moving Design Management to FOSS in 13.0
# in steps. In the current step the services have not yet been moved, and
# certain services are called within these tests:
# - `SystemNoteService`
# - `DesignManagement::GenerateImageVersionsService`
#
# See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283.
describe '#perform' do
let(:worker) { described_class.new }
context 'the id is wrong or out-of-date' do
let(:version_id) { -1 }
it 'does not create system notes' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect(SystemNoteService).not_to receive(:design_version_added)
worker.perform(version_id)
end
it 'does not invoke GenerateImageVersionsService' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect(DesignManagement::GenerateImageVersionsService).not_to receive(:new)
worker.perform(version_id)
end
it 'logs the reason for this failure' do
expect(Sidekiq.logger).to receive(:warn)
.with(an_instance_of(ActiveRecord::RecordNotFound))
worker.perform(version_id)
end
end
context 'the version id is valid' do
let_it_be(:version) { create(:design_version, :with_lfs_file, designs_count: 2) }
it 'creates a system note' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect { worker.perform(version.id) }.to change { Note.system.count }.by(1)
end
it 'invokes GenerateImageVersionsService' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect_next_instance_of(DesignManagement::GenerateImageVersionsService) do |service|
expect(service).to receive(:execute)
end
worker.perform(version.id)
end
it 'does not log anything' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect(Sidekiq.logger).not_to receive(:warn)
worker.perform(version.id)
end
end
context 'the version includes multiple types of action' do
let_it_be(:version) do
create(:design_version, :with_lfs_file,
created_designs: create_list(:design, 1, :with_lfs_file),
modified_designs: create_list(:design, 1))
end
it 'creates two system notes' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect { worker.perform(version.id) }.to change { Note.system.count }.by(2)
end
it 'calls design_version_added' do
skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
expect(SystemNoteService).to receive(:design_version_added).with(version)
worker.perform(version.id)
end
end
end
end