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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-12 03:10:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-12 03:10:11 +0300
commitc6f0b221b71133792f2c9e5a026f3744c16d5ef5 (patch)
tree864e5737806d454fbf23c681d5bced9b3e4a7d77
parent3f45eb27e9586ad87682c2d125770e119a7e9fe0 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue8
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/controllers/application_controller.rb3
-rw-r--r--app/mailers/emails/notes.rb12
-rw-r--r--app/services/alert_management/update_alert_status_service.rb20
-rw-r--r--app/services/projects/import_export/export_service.rb11
-rw-r--r--app/views/notify/note_design_email.html.haml1
-rw-r--r--app/views/notify/note_design_email.text.erb1
-rw-r--r--app/workers/all_queues.yml7
-rw-r--r--app/workers/design_management/new_version_worker.rb31
-rw-r--r--changelogs/unreleased/202143-fix-not-enough-data-in-vsa.yml6
-rw-r--r--changelogs/unreleased/217310.yml5
-rw-r--r--changelogs/unreleased/fix-branch-dot-txt.yml5
-rw-r--r--changelogs/unreleased/sh-log-cloudflare-headers.yml5
-rw-r--r--doc/integration/jenkins.md36
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/entities/design_management/design.rb16
-rw-r--r--lib/api/entities/todo.rb12
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb20
-rw-r--r--lib/banzai/reference_parser/design_parser.rb31
-rw-r--r--lib/gitlab/analytics/cycle_analytics/median.rb2
-rw-r--r--lib/gitlab/grape_logging/loggers/cloudflare_logger.rb18
-rw-r--r--lib/gitlab/import_export.rb4
-rw-r--r--lib/gitlab/import_export/design_repo_restorer.rb15
-rw-r--r--lib/gitlab/import_export/design_repo_saver.rb19
-rw-r--r--lib/gitlab/import_export/importer.rb14
-rw-r--r--lib/gitlab/import_export/project/import_export.yml20
-rw-r--r--lib/gitlab/import_export/project/object_builder.rb8
-rw-r--r--lib/gitlab/import_export/project/relation_factory.rb5
-rw-r--r--lib/gitlab/logging/cloudflare_helper.rb23
-rw-r--r--lib/gitlab/lograge/custom_options.rb6
-rw-r--r--lib/gitlab/uploads/migration_helper.rb3
-rw-r--r--lib/gitlab/url_builder.rb15
-rw-r--r--lib/gitlab/usage_data.rb3
-rw-r--r--lib/gitlab/usage_data_counters/designs_counter.rb42
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/fixtures/lib/gitlab/import_export/designs/project.json517
-rw-r--r--spec/graphql/mutations/alert_management/update_alert_status_spec.rb2
-rw-r--r--spec/lib/api/entities/design_management/design_spec.rb19
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb55
-rw-r--r--spec/lib/banzai/reference_parser/design_parser_spec.rb91
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb42
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/design_repo_restorer_spec.rb42
-rw-r--r--spec/lib/gitlab/import_export/design_repo_saver_spec.rb37
-rw-r--r--spec/lib/gitlab/import_export/import_test_coverage_spec.rb13
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb64
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb25
-rw-r--r--spec/lib/gitlab/logging/cloudflare_helper_spec.rb52
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb12
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb11
-rw-r--r--spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb14
-rw-r--r--spec/mailers/notify_spec.rb23
-rw-r--r--spec/requests/api/branches_spec.rb7
-rw-r--r--spec/requests/api/todos_spec.rb40
-rw-r--r--spec/services/alert_management/update_alert_status_service_spec.rb2
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb10
-rw-r--r--spec/support/helpers/query_recorder.rb4
-rw-r--r--spec/support/helpers/usage_data_helpers.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb2
-rw-r--r--spec/workers/design_management/new_version_worker_spec.rb94
65 files changed, 1591 insertions, 81 deletions
diff --git a/Gemfile b/Gemfile
index 95e9d0af35a..5d3709c8d64 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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
diff --git a/Gemfile.lock b/Gemfile.lock
index 4f0b40bc993..f3912d5a16e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 54b81a7f310..c6675bc0aef 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -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
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 765464d67e4..3b17ad2ada7 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -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"
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index ec7d7e94e5c..05e1293a80e 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -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>
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b5695322eb6..028dd3fd3af 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -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
##
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 6dd4ccb510a..4b56ff60f09 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -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
diff --git a/app/services/alert_management/update_alert_status_service.rb b/app/services/alert_management/update_alert_status_service.rb
index 8e0c3e38b8b..37960f510ef 100644
--- a/app/services/alert_management/update_alert_status_service.rb
+++ b/app/services/alert_management/update_alert_status_service.rb
@@ -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)
-
- alert.status_event = AlertManagement::Alert::STATUS_EVENTS[status.to_sym]
+ return error('Invalid status') unless status_key
- 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
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 3cb03aa16ad..ce2f9a13107 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -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')
diff --git a/app/views/notify/note_design_email.html.haml b/app/views/notify/note_design_email.html.haml
new file mode 100644
index 00000000000..5e69f01a486
--- /dev/null
+++ b/app/views/notify/note_design_email.html.haml
@@ -0,0 +1 @@
+= render 'note_email'
diff --git a/app/views/notify/note_design_email.text.erb b/app/views/notify/note_design_email.text.erb
new file mode 100644
index 00000000000..413d9e6e9ac
--- /dev/null
+++ b/app/views/notify/note_design_email.text.erb
@@ -0,0 +1 @@
+<%= render 'note_email' %>
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index ad023d4366e..434f584ab33 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -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:
diff --git a/app/workers/design_management/new_version_worker.rb b/app/workers/design_management/new_version_worker.rb
new file mode 100644
index 00000000000..3634dcbcebd
--- /dev/null
+++ b/app/workers/design_management/new_version_worker.rb
@@ -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
diff --git a/changelogs/unreleased/202143-fix-not-enough-data-in-vsa.yml b/changelogs/unreleased/202143-fix-not-enough-data-in-vsa.yml
new file mode 100644
index 00000000000..6045e244d1a
--- /dev/null
+++ b/changelogs/unreleased/202143-fix-not-enough-data-in-vsa.yml
@@ -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
diff --git a/changelogs/unreleased/217310.yml b/changelogs/unreleased/217310.yml
new file mode 100644
index 00000000000..bdae668659e
--- /dev/null
+++ b/changelogs/unreleased/217310.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing space on system notes
+merge_request: 31598
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-branch-dot-txt.yml b/changelogs/unreleased/fix-branch-dot-txt.yml
new file mode 100644
index 00000000000..c6860cb0ecb
--- /dev/null
+++ b/changelogs/unreleased/fix-branch-dot-txt.yml
@@ -0,0 +1,5 @@
+---
+title: Fix API requests for branch names ending in .txt
+merge_request: 31446
+author: Daniel Stone
+type: fixed
diff --git a/changelogs/unreleased/sh-log-cloudflare-headers.yml b/changelogs/unreleased/sh-log-cloudflare-headers.yml
new file mode 100644
index 00000000000..0186e30585f
--- /dev/null
+++ b/changelogs/unreleased/sh-log-cloudflare-headers.yml
@@ -0,0 +1,5 @@
+---
+title: Log Cloudflare request headers
+merge_request: 31532
+author:
+type: added
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index 22be1ae9b5c..ff9315a9f61 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -99,25 +99,29 @@ Set up the Jenkins project you’re 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:
-
- ```plaintext
- pipeline {
- agent any
-
- stages {
- stage('gitlab') {
- steps {
- echo 'Notify GitLab'
- updateGitlabCommitStatus name: 'build', state: 'pending'
- updateGitlabCommitStatus name: 'build', state: 'success'
+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.
+
+ Example Jenkins Pipeline script:
+
+ ```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
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 999bf1627c1..081e8ffe4f0 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -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
diff --git a/lib/api/entities/design_management/design.rb b/lib/api/entities/design_management/design.rb
new file mode 100644
index 00000000000..183fe06d8f1
--- /dev/null
+++ b/lib/api/entities/design_management/design.rb
@@ -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
diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb
index 8261a0b488e..0acbb4cb704 100644
--- a/lib/api/entities/todo.rb
+++ b/lib/api/entities/todo.rb
@@ -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
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 09a4d71b5f6..37e66387f2e 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -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')
diff --git a/lib/banzai/reference_parser/design_parser.rb b/lib/banzai/reference_parser/design_parser.rb
new file mode 100644
index 00000000000..04e878756d8
--- /dev/null
+++ b/lib/banzai/reference_parser/design_parser.rb
@@ -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
diff --git a/lib/gitlab/analytics/cycle_analytics/median.rb b/lib/gitlab/analytics/cycle_analytics/median.rb
index 692e06722d8..9fcaeadf351 100644
--- a/lib/gitlab/analytics/cycle_analytics/median.rb
+++ b/lib/gitlab/analytics/cycle_analytics/median.rb
@@ -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
diff --git a/lib/gitlab/grape_logging/loggers/cloudflare_logger.rb b/lib/gitlab/grape_logging/loggers/cloudflare_logger.rb
new file mode 100644
index 00000000000..3abb0100a86
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/cloudflare_logger.rb
@@ -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
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index cb0a24c8864..921072a4970 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -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
diff --git a/lib/gitlab/import_export/design_repo_restorer.rb b/lib/gitlab/import_export/design_repo_restorer.rb
new file mode 100644
index 00000000000..a702c58a7c2
--- /dev/null
+++ b/lib/gitlab/import_export/design_repo_restorer.rb
@@ -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
diff --git a/lib/gitlab/import_export/design_repo_saver.rb b/lib/gitlab/import_export/design_repo_saver.rb
new file mode 100644
index 00000000000..db9ebee6a13
--- /dev/null
+++ b/lib/gitlab/import_export/design_repo_saver.rb
@@ -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
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index e88e0128d57..b1219384732 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -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')
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 24a48dfb187..0b104148edb 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -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
diff --git a/lib/gitlab/import_export/project/object_builder.rb b/lib/gitlab/import_export/project/object_builder.rb
index c3637b1c115..831e38f3034 100644
--- a/lib/gitlab/import_export/project/object_builder.rb
+++ b/lib/gitlab/import_export/project/object_builder.rb
@@ -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')
diff --git a/lib/gitlab/import_export/project/relation_factory.rb b/lib/gitlab/import_export/project/relation_factory.rb
index c5d99c9fced..3ab9f2c4bfa 100644
--- a/lib/gitlab/import_export/project/relation_factory.rb
+++ b/lib/gitlab/import_export/project/relation_factory.rb
@@ -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
diff --git a/lib/gitlab/logging/cloudflare_helper.rb b/lib/gitlab/logging/cloudflare_helper.rb
new file mode 100644
index 00000000000..09a4cbc6146
--- /dev/null
+++ b/lib/gitlab/logging/cloudflare_helper.rb
@@ -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
diff --git a/lib/gitlab/lograge/custom_options.rb b/lib/gitlab/lograge/custom_options.rb
index 145d67d7101..68402ef2184 100644
--- a/lib/gitlab/lograge/custom_options.rb
+++ b/lib/gitlab/lograge/custom_options.rb
@@ -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]
diff --git a/lib/gitlab/uploads/migration_helper.rb b/lib/gitlab/uploads/migration_helper.rb
index 96ee6f0e8e6..9377ccfec1e 100644
--- a/lib/gitlab/uploads/migration_helper.rb
+++ b/lib/gitlab/uploads/migration_helper.rb
@@ -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')
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index d292b36b311..a2ebe9f6936 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -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
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 0704f94b87f..7ddc9ae969a 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -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
diff --git a/lib/gitlab/usage_data_counters/designs_counter.rb b/lib/gitlab/usage_data_counters/designs_counter.rb
new file mode 100644
index 00000000000..801fb8f3b3d
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/designs_counter.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4d83a93a111..d248941bd18 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/fixtures/lib/gitlab/import_export/designs/project.json b/spec/fixtures/lib/gitlab/import_export/designs/project.json
new file mode 100644
index 00000000000..a466529c09d
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/import_export/designs/project.json
@@ -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":[
+
+ ]
+}
diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
index 19fe9fc3900..126362d024a 100644
--- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
+++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
@@ -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) }
diff --git a/spec/lib/api/entities/design_management/design_spec.rb b/spec/lib/api/entities/design_management/design_spec.rb
new file mode 100644
index 00000000000..50ca3b43c6a
--- /dev/null
+++ b/spec/lib/api/entities/design_management/design_spec.rb
@@ -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
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 4a412da27a7..61c59162a30 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -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
diff --git a/spec/lib/banzai/reference_parser/design_parser_spec.rb b/spec/lib/banzai/reference_parser/design_parser_spec.rb
new file mode 100644
index 00000000000..76708acf887
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/design_parser_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
new file mode 100644
index 00000000000..92ecec350ae
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
new file mode 100644
index 00000000000..922a433d7ac
--- /dev/null
+++ b/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
new file mode 100644
index 00000000000..5662b8af280
--- /dev/null
+++ b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
new file mode 100644
index 00000000000..bff48e8b52a
--- /dev/null
+++ b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
index 335b0031147..038b95809b4 100644
--- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
+++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 14e643f86c0..60179146416 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -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)
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index ecb1ed08260..58589a7bbbe 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index 8adc360026d..b9bfe253f10 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/logging/cloudflare_helper_spec.rb b/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
new file mode 100644
index 00000000000..8585943be3a
--- /dev/null
+++ b/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
index 48d06283b7a..3c014ae618b 100644
--- a/spec/lib/gitlab/lograge/custom_options_spec.rb
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 71ffa4a00a1..66826bcb3b1 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -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 }
diff --git a/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
new file mode 100644
index 00000000000..deaf7ebc7f3
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
@@ -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
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index d21efe2e1fe..6e1dba6945d 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -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 }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 97f880dd3cd..5e8223ec3cc 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -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 }
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 1c380d51813..0bdc71a30e9 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -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
diff --git a/spec/services/alert_management/update_alert_status_service_spec.rb b/spec/services/alert_management/update_alert_status_service_spec.rb
index 5bdad7a8e19..44083128453 100644
--- a/spec/services/alert_management/update_alert_status_service_spec.rb
+++ b/spec/services/alert_management/update_alert_status_service_spec.rb
@@ -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)
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index 5beec51b370..32d0b52f096 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -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
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index fd200a1abf3..61634813a1c 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -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)
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index e25fd310fbd..a8f743ed7d7 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -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(
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
index 851ed9c65a3..14292f70228 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
@@ -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
diff --git a/spec/workers/design_management/new_version_worker_spec.rb b/spec/workers/design_management/new_version_worker_spec.rb
new file mode 100644
index 00000000000..76497dde464
--- /dev/null
+++ b/spec/workers/design_management/new_version_worker_spec.rb
@@ -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