summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml2
-rw-r--r--.gitlab/merge_request_templates/Security Release.md2
-rw-r--r--app/assets/javascripts/blob/components/blob_edit_header.vue35
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue4
-rw-r--r--app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql7
-rw-r--r--app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql7
-rw-r--r--app/assets/javascripts/sidebar/services/sidebar_service.js27
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js6
-rw-r--r--app/assets/javascripts/snippet/snippet_bundle.js2
-rw-r--r--app/assets/stylesheets/utilities.scss2
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/helpers/analytics_navbar_helper.rb6
-rw-r--r--app/helpers/issuables_helper.rb1
-rw-r--r--app/models/broadcast_message.rb10
-rw-r--r--app/models/internal_id.rb2
-rw-r--r--app/models/internal_id_enums.rb10
-rw-r--r--app/services/groups/import_export/export_service.rb14
-rw-r--r--app/services/groups/import_export/import_service.rb43
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb2
-rw-r--r--app/services/metrics/dashboard/clone_dashboard_service.rb2
-rw-r--r--app/services/metrics/dashboard/update_dashboard_service.rb2
-rw-r--r--app/services/projects/import_export/export_service.rb4
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml3
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/workers/all_queues.yml2
-rw-r--r--app/workers/create_evidence_worker.rb2
-rw-r--r--changelogs/unreleased/207464-prevent-unauthorized-user-to-lock-an-issue-when-the-sidebar-is-col.yml5
-rw-r--r--changelogs/unreleased/208455-remove-analytics-suffixes-from-analytics-sidebar-menu-items.yml5
-rw-r--r--changelogs/unreleased/208788-fix-avg_cycle_analytics-giving-an-uncaught-error.yml5
-rw-r--r--changelogs/unreleased/55487-backfill-lfs-objects-v2.yml5
-rw-r--r--changelogs/unreleased/create-approval-todos-on-update.yml5
-rw-r--r--changelogs/unreleased/fix-merge-to-ref-service-raise-command-error.yml5
-rw-r--r--changelogs/unreleased/jhyson-export_failures.yml5
-rw-r--r--changelogs/unreleased/mwaw-197871-improve-duplicated-dashboard-error-messages.yml5
-rw-r--r--changelogs/unreleased/requirements-model.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-broadcast-message-redis.yml5
-rw-r--r--config/feature_categories.yml17
-rw-r--r--db/migrate/20200226100614_create_requirements.rb28
-rw-r--r--db/migrate/20200226100624_requirements_add_project_fk.rb19
-rw-r--r--db/migrate/20200226100634_requirements_add_author_fk.rb19
-rw-r--r--db/post_migrate/20200217091401_reschedule_link_lfs_objects.rb29
-rw-r--r--db/schema.rb21
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md6
-rw-r--r--doc/development/testing_guide/review_apps.md3
-rw-r--r--lib/gitlab/background_migration/link_lfs_objects.rb58
-rw-r--r--lib/gitlab/cycle_analytics/usage_data.rb21
-rw-r--r--lib/gitlab/import_export/error.rb9
-rw-r--r--lib/gitlab/import_export/group/tree_restorer.rb6
-rw-r--r--lib/gitlab/import_export/project/base_task.rb41
-rw-r--r--lib/gitlab/import_export/project/export_task.rb43
-rw-r--r--lib/gitlab/import_export/project/import_task.rb110
-rw-r--r--lib/gitlab/import_export/shared.rb8
-rw-r--r--lib/gitlab/usage_data.rb6
-rw-r--r--lib/gitlab/utils/measuring.rb5
-rw-r--r--lib/tasks/gitlab/import_export/export.rake105
-rw-r--r--lib/tasks/gitlab/import_export/import.rake164
-rw-r--r--locale/gitlab.pot66
-rw-r--r--spec/features/groups/group_page_with_external_authorization_service_spec.rb2
-rw-r--r--spec/features/groups/navbar_spec.rb6
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb23
-rw-r--r--spec/features/projects/active_tabs_spec.rb6
-rw-r--r--spec/features/projects/navbar_spec.rb8
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb2
-rw-r--r--spec/fixtures/group_export_invalid_subrelations.tar.gzbin0 -> 3036 bytes
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap16
-rw-r--r--spec/frontend/blob/components/blob_edit_header_spec.js50
-rw-r--r--spec/frontend/sidebar/mock_data.js10
-rw-r--r--spec/frontend/snippet/snippet_bundle_spec.js4
-rw-r--r--spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js13
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js10
-rw-r--r--spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb76
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/error_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb69
-rw-r--r--spec/lib/gitlab/import_export/project/import_task_spec.rb (renamed from spec/tasks/gitlab/import_export/import_rake_spec.rb)39
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb18
-rw-r--r--spec/migrations/reschedule_link_lfs_objects_spec.rb86
-rw-r--r--spec/models/broadcast_message_spec.rb11
-rw-r--r--spec/services/groups/import_export/export_service_spec.rb25
-rw-r--r--spec/services/groups/import_export/import_service_spec.rb75
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb11
-rw-r--r--spec/services/metrics/dashboard/clone_dashboard_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/update_dashboard_service_spec.rb2
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb2
-rw-r--r--spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb2
-rw-r--r--spec/tasks/gitlab/import_export/export_rake_spec.rb37
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb4
89 files changed, 1050 insertions, 633 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 12c1507da62..130e9085f03 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -254,4 +254,4 @@ danger-review:
- git version
- node --version
- yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
- - danger --fail-on-errors=true --new-comment --remove-previous-comments --verbose
+ - danger --fail-on-errors=true --verbose
diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md
index 02cb4c59fd1..24fe44200d6 100644
--- a/.gitlab/merge_request_templates/Security Release.md
+++ b/.gitlab/merge_request_templates/Security Release.md
@@ -12,7 +12,7 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
## Developer checklist
-- [ ] **Make sure this merge request mentions the [GitLab Security] issue it belongs to (i.e. `Related to <issue_id>`).**
+- [ ] **On "Related issues" section, write down the [GitLab Security] issue it belongs to (i.e. `Related to <issue_id>`).**
- [ ] Merge request targets `master`, or `X-Y-stable` for backports.
- [ ] Milestone is set for the version this merge request applies to. A closed milestone can be assigned via [quick actions].
- [ ] Title of this merge request is the same as for all backports.
diff --git a/app/assets/javascripts/blob/components/blob_edit_header.vue b/app/assets/javascripts/blob/components/blob_edit_header.vue
new file mode 100644
index 00000000000..e9b5ceda479
--- /dev/null
+++ b/app/assets/javascripts/blob/components/blob_edit_header.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlFormInput } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlFormInput,
+ },
+ props: {
+ value: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ name: this.value,
+ };
+ },
+};
+</script>
+<template>
+ <div class="js-file-title file-title-flex-parent">
+ <gl-form-input
+ id="snippet_file_name"
+ v-model="name"
+ :placeholder="
+ s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
+ "
+ name="snippet_file_name"
+ class="form-control js-snippet-file-name qa-snippet-file-name"
+ type="text"
+ @change="$emit('input', name)"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index c7c5e0e20f1..012a4f4ad89 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -63,7 +63,9 @@ export default {
methods: {
toggleForm() {
- this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
+ if (this.isEditable) {
+ this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
+ }
},
updateLockedAttribute(locked) {
this.mediator.service
diff --git a/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql b/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql
new file mode 100644
index 00000000000..8cc68f6ea9a
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/sidebarDetails.query.graphql
@@ -0,0 +1,7 @@
+query ($fullPath: ID!, $iid: String!) {
+ project (fullPath: $fullPath) {
+ issue (iid: $iid) {
+ iid
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql b/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql
new file mode 100644
index 00000000000..8cc68f6ea9a
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql
@@ -0,0 +1,7 @@
+query ($fullPath: ID!, $iid: String!) {
+ project (fullPath: $fullPath) {
+ issue (iid: $iid) {
+ iid
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/services/sidebar_service.js b/app/assets/javascripts/sidebar/services/sidebar_service.js
index feb08e3acaf..59d4f6ed388 100644
--- a/app/assets/javascripts/sidebar/services/sidebar_service.js
+++ b/app/assets/javascripts/sidebar/services/sidebar_service.js
@@ -1,4 +1,14 @@
import axios from '~/lib/utils/axios_utils';
+import createGqClient, { fetchPolicies } from '~/lib/graphql';
+import sidebarDetailsQuery from 'ee_else_ce/sidebar/queries/sidebarDetails.query.graphql';
+import sidebarDetailsForHealthStatusFeatureFlagQuery from 'ee_else_ce/sidebar/queries/sidebarDetailsForHealthStatusFeatureFlag.query.graphql';
+
+export const gqClient = createGqClient(
+ {},
+ {
+ fetchPolicy: fetchPolicies.NO_CACHE,
+ },
+);
export default class SidebarService {
constructor(endpointMap) {
@@ -7,6 +17,8 @@ export default class SidebarService {
this.toggleSubscriptionEndpoint = endpointMap.toggleSubscriptionEndpoint;
this.moveIssueEndpoint = endpointMap.moveIssueEndpoint;
this.projectsAutocompleteEndpoint = endpointMap.projectsAutocompleteEndpoint;
+ this.fullPath = endpointMap.fullPath;
+ this.id = endpointMap.id;
SidebarService.singleton = this;
}
@@ -15,7 +27,20 @@ export default class SidebarService {
}
get() {
- return axios.get(this.endpoint);
+ const hasHealthStatusFeatureFlag = gon.features && gon.features.saveIssuableHealthStatus;
+
+ return Promise.all([
+ axios.get(this.endpoint),
+ gqClient.query({
+ query: hasHealthStatusFeatureFlag
+ ? sidebarDetailsForHealthStatusFeatureFlagQuery
+ : sidebarDetailsQuery,
+ variables: {
+ fullPath: this.fullPath,
+ iid: this.id.toString(),
+ },
+ }),
+ ]);
}
update(key, data) {
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index ce869a625bf..eb1cf977725 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -19,6 +19,8 @@ export default class SidebarMediator {
toggleSubscriptionEndpoint: options.toggleSubscriptionEndpoint,
moveIssueEndpoint: options.moveIssueEndpoint,
projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
+ fullPath: options.fullPath,
+ id: options.id,
});
SidebarMediator.singleton = this;
}
@@ -45,8 +47,8 @@ export default class SidebarMediator {
fetch() {
return this.service
.get()
- .then(({ data }) => {
- this.processFetchedData(data);
+ .then(([restResponse, graphQlResponse]) => {
+ this.processFetchedData(restResponse.data, graphQlResponse.data);
})
.catch(() => new Flash(__('Error occurred when fetching sidebar data')));
}
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index 8e952fe9358..42da65a941d 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -17,7 +17,7 @@ const initAce = () => {
const initMonaco = () => {
const editorEl = document.getElementById('editor');
const contentEl = document.querySelector('.snippet-file-content');
- const fileNameEl = document.querySelector('.snippet-file-name');
+ const fileNameEl = document.querySelector('.js-snippet-file-name');
const form = document.querySelector('.snippet-form-holder form');
editor = new Editor();
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 2a92b271ed0..882ce29c671 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -77,3 +77,5 @@
.gl-text-red-700 { @include gl-text-red-700; }
.gl-text-orange-700 { @include gl-text-orange-700; }
.gl-text-green-700 { @include gl-text-green-700; }
+
+.gl-align-items-center { @include gl-align-items-center; }
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b14a1179d46..5ddc60707d5 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -44,6 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
+ push_frontend_feature_flag(:save_issuable_health_status, project.group)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
diff --git a/app/helpers/analytics_navbar_helper.rb b/app/helpers/analytics_navbar_helper.rb
index 021b9bb10cd..eecb4090bcf 100644
--- a/app/helpers/analytics_navbar_helper.rb
+++ b/app/helpers/analytics_navbar_helper.rb
@@ -35,7 +35,7 @@ module AnalyticsNavbarHelper
return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item(
- title: _('Value Stream Analytics'),
+ title: _('Value Stream'),
path: 'cycle_analytics#show',
link: project_cycle_analytics_path(project),
link_to_options: { class: 'shortcuts-project-cycle-analytics' }
@@ -47,7 +47,7 @@ module AnalyticsNavbarHelper
return if project.empty_repo?
navbar_sub_item(
- title: _('Repository Analytics'),
+ title: _('Repository'),
path: 'graphs#charts',
link: charts_project_graph_path(project, current_ref),
link_to_options: { class: 'shortcuts-repository-charts' }
@@ -60,7 +60,7 @@ module AnalyticsNavbarHelper
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
navbar_sub_item(
- title: _('CI / CD Analytics'),
+ title: _('CI / CD'),
path: 'pipelines#charts',
link: charts_project_pipelines_path(project)
)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 8c75a4a13e8..aa05076c43e 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -463,6 +463,7 @@ module IssuablesHelper
currentUser: issuable[:current_user],
rootPath: root_path,
fullPath: issuable[:project_full_path],
+ id: issuable[:id],
timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours
}
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index b3d72ebdcf3..0a536a01f72 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -52,7 +52,9 @@ class BroadcastMessage < ApplicationRecord
end
def cache
- Gitlab::JsonCache.new(cache_key_with_version: false)
+ ::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
+ Gitlab::JsonCache.new(cache_key_with_version: false)
+ end
end
def cache_expires_in
@@ -68,9 +70,9 @@ class BroadcastMessage < ApplicationRecord
now_or_future = messages.select(&:now_or_future?)
- # If there are cached entries but none are to be displayed we'll purge the
- # cache so we don't keep running this code all the time.
- cache.expire(cache_key) if now_or_future.empty?
+ # If there are cached entries but they don't match the ones we are
+ # displaying we'll refresh the cache so we don't need to keep filtering.
+ cache.expire(cache_key) if now_or_future != messages
now_or_future.select(&:now?).select { |message| message.matches_current_path(current_path) }
end
diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb
index 3e8d0c6a778..b6882701e23 100644
--- a/app/models/internal_id.rb
+++ b/app/models/internal_id.rb
@@ -21,7 +21,7 @@ class InternalId < ApplicationRecord
belongs_to :project
belongs_to :namespace
- enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4, ci_pipelines: 5, operations_feature_flags: 6 }
+ enum usage: ::InternalIdEnums.usage_resources
validates :usage, presence: true
diff --git a/app/models/internal_id_enums.rb b/app/models/internal_id_enums.rb
new file mode 100644
index 00000000000..2f7d7aeff2f
--- /dev/null
+++ b/app/models/internal_id_enums.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module InternalIdEnums
+ def self.usage_resources
+ # when adding new resource, make sure it doesn't conflict with EE usage_resources
+ { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4, ci_pipelines: 5, operations_feature_flags: 6 }
+ end
+end
+
+InternalIdEnums.prepend_if_ee('EE::InternalIdEnums')
diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb
index aa484e7203c..0bf54844430 100644
--- a/app/services/groups/import_export/export_service.rb
+++ b/app/services/groups/import_export/export_service.rb
@@ -11,11 +11,7 @@ module Groups
end
def execute
- unless @current_user.can?(:admin_group, @group)
- raise ::Gitlab::ImportExport::Error.new(
- "User with ID: %s does not have permission to Group %s with ID: %s." %
- [@current_user.id, @group.name, @group.id])
- end
+ validate_user_permissions
save!
ensure
@@ -26,6 +22,14 @@ module Groups
attr_accessor :shared
+ def validate_user_permissions
+ unless @current_user.can?(:admin_group, @group)
+ @shared.error(::Gitlab::ImportExport::Error.permission_error(@current_user, @group))
+
+ notify_error!
+ end
+ end
+
def save!
if savers.all?(&:save)
notify_success
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index 57d2d9855d1..548a4a98dc1 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -12,15 +12,14 @@ module Groups
end
def execute
- validate_user_permissions
+ if valid_user_permissions? && import_file && restorer.restore
+ notify_success
- if import_file && restorer.restore
@group
else
- raise StandardError.new(@shared.errors.to_sentence)
+ notify_error!
end
- rescue => e
- raise StandardError.new(e.message)
+
ensure
remove_import_file
end
@@ -49,13 +48,37 @@ module Groups
upload.save!
end
- def validate_user_permissions
- unless current_user.can?(:admin_group, group)
- raise ::Gitlab::ImportExport::Error.new(
- "User with ID: %s does not have permission to Group %s with ID: %s." %
- [current_user.id, group.name, group.id])
+ def valid_user_permissions?
+ if current_user.can?(:admin_group, group)
+ true
+ else
+ @shared.error(::Gitlab::ImportExport::Error.permission_error(current_user, group))
+
+ false
end
end
+
+ def notify_success
+ @shared.logger.info(
+ group_id: @group.id,
+ group_name: @group.name,
+ message: 'Group Import/Export: Import succeeded'
+ )
+ end
+
+ def notify_error
+ @shared.logger.error(
+ group_id: @group.id,
+ group_name: @group.name,
+ message: "Group Import/Export: Errors occurred, see '#{Gitlab::ErrorTracking::Logger.file_name}' for details"
+ )
+ end
+
+ def notify_error!
+ notify_error
+
+ raise Gitlab::ImportExport::Error.new(@shared.errors.to_sentence)
+ end
end
end
end
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 37b5805ae7e..1876b1096fe 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -60,7 +60,7 @@ module MergeRequests
def commit
repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message, first_parent_ref)
- rescue Gitlab::Git::PreReceiveError => error
+ rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError => error
raise MergeError, error.message
end
end
diff --git a/app/services/metrics/dashboard/clone_dashboard_service.rb b/app/services/metrics/dashboard/clone_dashboard_service.rb
index 990dc462432..2720bfdafcd 100644
--- a/app/services/metrics/dashboard/clone_dashboard_service.rb
+++ b/app/services/metrics/dashboard/clone_dashboard_service.rb
@@ -24,7 +24,7 @@ module Metrics
def execute
catch(:error) do
- throw(:error, error(_(%q(You can't commit to this project)), :forbidden)) unless push_authorized?
+ throw(:error, error(_(%q(You are not allowed to push into this branch. Create another branch or open a merge request.)), :forbidden)) unless push_authorized?
result = ::Files::CreateService.new(project, current_user, dashboard_attrs).execute
throw(:error, wrap_error(result)) unless result[:status] == :success
diff --git a/app/services/metrics/dashboard/update_dashboard_service.rb b/app/services/metrics/dashboard/update_dashboard_service.rb
index 316563ecbe7..e29fc47678f 100644
--- a/app/services/metrics/dashboard/update_dashboard_service.rb
+++ b/app/services/metrics/dashboard/update_dashboard_service.rb
@@ -9,7 +9,7 @@ module Metrics
def execute
catch(:error) do
- throw(:error, error(_(%q(You can't commit to this project)), :forbidden)) unless push_authorized?
+ throw(:error, error(_(%q(You are not allowed to push into this branch. Create another branch or open a merge request.)), :forbidden)) unless push_authorized?
result = ::Files::UpdateService.new(project, current_user, dashboard_attrs).execute
throw(:error, result.merge(http_status: :bad_request)) unless result[:status] == :success
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 77fddc44085..5a3eb4c2156 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -5,9 +5,7 @@ module Projects
class ExportService < BaseService
def execute(after_export_strategy = nil, options = {})
unless project.template_source? || can?(current_user, :admin_project, project)
- raise ::Gitlab::ImportExport::Error.new(
- "User with ID: %s does not have permission to Project %s with ID: %s." %
- [current_user.id, project.name, project.id])
+ raise ::Gitlab::ImportExport::Error.permission_error(current_user, project)
end
@shared = project.import_export_shared
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index c00c48b623c..b2fa647b23e 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -48,9 +48,9 @@
- unless should_display_analytics_pages_in_sidebar
- if group_sidebar_link?(:contribution_analytics)
= nav_link(path: 'contribution_analytics#show') do
- = link_to group_contribution_analytics_path(@group), title: _('Contribution Analytics'), data: { placement: 'right', qa_selector: 'contribution_analytics_link' } do
+ = link_to group_contribution_analytics_path(@group), title: _('Contribution'), data: { placement: 'right', qa_selector: 'contribution_analytics_link' } do
%span
- = _('Contribution Analytics')
+ = _('Contribution')
= render_if_exists 'layouts/nav/group_insights_link'
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 160e2a7c952..b8b5eacfab1 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -42,8 +42,8 @@
- unless should_display_analytics_pages_in_sidebar
- if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do
- = link_to project_cycle_analytics_path(@project), title: _('Value Stream Analytics'), class: 'shortcuts-project-cycle-analytics' do
- %span= _('Value Stream Analytics')
+ = link_to project_cycle_analytics_path(@project), title: _('Value Stream'), class: 'shortcuts-project-cycle-analytics' do
+ %span= _('Value Stream')
= render_if_exists 'layouts/nav/project_insights_link'
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 2a853de12a4..1a1da6b3801 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -129,6 +129,9 @@
= render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar
+ - if Feature.enabled?(:save_issuable_health_status, @project.group) && issuable_sidebar[:type] == "issue"
+ .js-sidebar-status-entry-point
+
- if issuable_sidebar.has_key?(:confidential)
-# haml-lint:disable InlineJavaScript
%script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index b07992c6890..828015d29f5 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -26,7 +26,7 @@
= f.label :file_name, s_('Snippets|File')
.file-holder.snippet
.js-file-title.file-title-flex-parent
- = f.text_field :file_name, placeholder: s_("Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"), class: 'form-control snippet-file-name qa-snippet-file-name'
+ = f.text_field :file_name, placeholder: s_("Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby"), class: 'form-control js-snippet-file-name qa-snippet-file-name'
.file-content.code
%pre#editor{ data: { 'editor-loading': true } }= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 61e9f50c2dd..890f38aa26b 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -865,7 +865,7 @@
:weight: 2
:idempotent:
- :name: create_evidence
- :feature_category: :release_governance
+ :feature_category: :release_evidence
:has_external_dependencies:
:urgency: :default
:resource_boundary: :unknown
diff --git a/app/workers/create_evidence_worker.rb b/app/workers/create_evidence_worker.rb
index 46f211cf3e1..c2faba84cfc 100644
--- a/app/workers/create_evidence_worker.rb
+++ b/app/workers/create_evidence_worker.rb
@@ -3,7 +3,7 @@
class CreateEvidenceWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
- feature_category :release_governance
+ feature_category :release_evidence
weight 2
def perform(release_id)
diff --git a/changelogs/unreleased/207464-prevent-unauthorized-user-to-lock-an-issue-when-the-sidebar-is-col.yml b/changelogs/unreleased/207464-prevent-unauthorized-user-to-lock-an-issue-when-the-sidebar-is-col.yml
new file mode 100644
index 00000000000..895fd7f95de
--- /dev/null
+++ b/changelogs/unreleased/207464-prevent-unauthorized-user-to-lock-an-issue-when-the-sidebar-is-col.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent unauthorized users to lock an issue from the collapsed sidebar.
+merge_request: 26324
+author: Gilang Gumilar
+type: fixed
diff --git a/changelogs/unreleased/208455-remove-analytics-suffixes-from-analytics-sidebar-menu-items.yml b/changelogs/unreleased/208455-remove-analytics-suffixes-from-analytics-sidebar-menu-items.yml
new file mode 100644
index 00000000000..836eea0d23b
--- /dev/null
+++ b/changelogs/unreleased/208455-remove-analytics-suffixes-from-analytics-sidebar-menu-items.yml
@@ -0,0 +1,5 @@
+---
+title: Remove "Analytics" suffix from the sidebar menu items
+merge_request: 26415
+author:
+type: removed
diff --git a/changelogs/unreleased/208788-fix-avg_cycle_analytics-giving-an-uncaught-error.yml b/changelogs/unreleased/208788-fix-avg_cycle_analytics-giving-an-uncaught-error.yml
new file mode 100644
index 00000000000..60d88989880
--- /dev/null
+++ b/changelogs/unreleased/208788-fix-avg_cycle_analytics-giving-an-uncaught-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix avg_cycle_analytics uncaught error and optimize query
+merge_request: 26381
+author:
+type: fixed
diff --git a/changelogs/unreleased/55487-backfill-lfs-objects-v2.yml b/changelogs/unreleased/55487-backfill-lfs-objects-v2.yml
deleted file mode 100644
index b751955913e..00000000000
--- a/changelogs/unreleased/55487-backfill-lfs-objects-v2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Backfill LfsObjectsProject records of forks
-merge_request: 25343
-author:
-type: other
diff --git a/changelogs/unreleased/create-approval-todos-on-update.yml b/changelogs/unreleased/create-approval-todos-on-update.yml
new file mode 100644
index 00000000000..87ab5539886
--- /dev/null
+++ b/changelogs/unreleased/create-approval-todos-on-update.yml
@@ -0,0 +1,5 @@
+---
+title: Create approval todos on update
+merge_request: 26077
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-merge-to-ref-service-raise-command-error.yml b/changelogs/unreleased/fix-merge-to-ref-service-raise-command-error.yml
new file mode 100644
index 00000000000..73b09188a6e
--- /dev/null
+++ b/changelogs/unreleased/fix-merge-to-ref-service-raise-command-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix MergeToRefService raises Gitlab::Git::CommandError
+merge_request: 26465
+author:
+type: fixed
diff --git a/changelogs/unreleased/jhyson-export_failures.yml b/changelogs/unreleased/jhyson-export_failures.yml
new file mode 100644
index 00000000000..d536230e87e
--- /dev/null
+++ b/changelogs/unreleased/jhyson-export_failures.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure all errors are logged in Group Import
+merge_request: 25619
+author:
+type: changed
diff --git a/changelogs/unreleased/mwaw-197871-improve-duplicated-dashboard-error-messages.yml b/changelogs/unreleased/mwaw-197871-improve-duplicated-dashboard-error-messages.yml
new file mode 100644
index 00000000000..a5e1fa98049
--- /dev/null
+++ b/changelogs/unreleased/mwaw-197871-improve-duplicated-dashboard-error-messages.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error messages for dashboard clonning process.
+merge_request: 26290
+author:
+type: fixed
diff --git a/changelogs/unreleased/requirements-model.yml b/changelogs/unreleased/requirements-model.yml
new file mode 100644
index 00000000000..daf2c649db2
--- /dev/null
+++ b/changelogs/unreleased/requirements-model.yml
@@ -0,0 +1,5 @@
+---
+title: Add migration for Requirement model
+merge_request: 26097
+author:
+type: added
diff --git a/changelogs/unreleased/sh-optimize-broadcast-message-redis.yml b/changelogs/unreleased/sh-optimize-broadcast-message-redis.yml
new file mode 100644
index 00000000000..c69f3ded73d
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-broadcast-message-redis.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unnecessary Redis deletes for broadcast messages
+merge_request: 26541
+author:
+type: performance
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 924bdb58682..7e3746baec5 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -25,8 +25,7 @@
- code_quality
- code_review
- collection
-- compliance_controls
-- compliance_frameworks
+- compliance_management
- container_network_security
- container_registry
- container_scanning
@@ -37,7 +36,7 @@
- dependency_proxy
- dependency_scanning
- design_management
-- devops_score
+- devops_reports
- digital_experience_management
- disaster_recovery
- dynamic_application_security_testing
@@ -52,6 +51,7 @@
- gitaly
- gitlab_handbook
- gitter
+- global_search
- helm_chart_registry
- importers
- incident_management
@@ -61,6 +61,8 @@
- interactive_application_security_testing
- internationalization
- issue_tracking
+- jenkins_importer
+- jira_importer
- jupyter_notebooks
- kanban_boards
- kubernetes_management
@@ -70,13 +72,14 @@
- load_testing
- logging
- malware_scanning
-- merge_trains
- metrics
- omnibus_package
- package_registry
- pages
+- pki_management
+- planning_analytics
- quality_management
-- release_governance
+- release_evidence
- release_orchestration
- requirements_management
- responsible_disclosure
@@ -86,7 +89,6 @@
- runner
- runtime_application_self_protection
- sdk
-- search
- secret_detection
- secrets_management
- serverless
@@ -97,8 +99,6 @@
- static_site_editor
- status_page
- subgroups
-- system_testing
-- teams
- templates
- threat_detection
- time_tracking
@@ -113,4 +113,3 @@
- web_ide
- web_performance
- wiki
-- workspaces
diff --git a/db/migrate/20200226100614_create_requirements.rb b/db/migrate/20200226100614_create_requirements.rb
new file mode 100644
index 00000000000..4ebbf38b8d1
--- /dev/null
+++ b/db/migrate/20200226100614_create_requirements.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class CreateRequirements < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :requirements do |t|
+ t.timestamps_with_timezone null: false
+ t.integer :project_id, null: false
+ t.integer :author_id
+ t.integer :iid, null: false
+ t.integer :cached_markdown_version
+ t.integer :state, limit: 2, default: 1, null: false
+ t.string :title, limit: 255, null: false
+ t.text :title_html
+
+ t.index :project_id
+ t.index :author_id
+ t.index :title, name: "index_requirements_on_title_trigram", using: :gin, opclass: :gin_trgm_ops
+ t.index :state
+ t.index :created_at
+ t.index :updated_at
+ t.index %w(project_id iid), name: 'index_requirements_on_project_id_and_iid', where: 'project_id IS NOT NULL', unique: true, using: :btree
+ end
+ end
+end
diff --git a/db/migrate/20200226100624_requirements_add_project_fk.rb b/db/migrate/20200226100624_requirements_add_project_fk.rb
new file mode 100644
index 00000000000..7c133e820f3
--- /dev/null
+++ b/db/migrate/20200226100624_requirements_add_project_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RequirementsAddProjectFk < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_foreign_key(:requirements, :projects, column: :project_id, on_delete: :cascade) # rubocop: disable Migration/AddConcurrentForeignKey
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key(:requirements, column: :project_id)
+ end
+ end
+end
diff --git a/db/migrate/20200226100634_requirements_add_author_fk.rb b/db/migrate/20200226100634_requirements_add_author_fk.rb
new file mode 100644
index 00000000000..8e1a726bb76
--- /dev/null
+++ b/db/migrate/20200226100634_requirements_add_author_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RequirementsAddAuthorFk < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_foreign_key(:requirements, :users, column: :author_id, on_delete: :nullify) # rubocop: disable Migration/AddConcurrentForeignKey
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key(:requirements, column: :author_id)
+ end
+ end
+end
diff --git a/db/post_migrate/20200217091401_reschedule_link_lfs_objects.rb b/db/post_migrate/20200217091401_reschedule_link_lfs_objects.rb
deleted file mode 100644
index a7d1b42ef70..00000000000
--- a/db/post_migrate/20200217091401_reschedule_link_lfs_objects.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-class RescheduleLinkLfsObjects < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- MIGRATION = 'LinkLfsObjects'
- BATCH_SIZE = 1_000
-
- disable_ddl_transaction!
-
- def up
- forks = Gitlab::BackgroundMigration::LinkLfsObjects::Project.with_non_existing_lfs_objects
-
- queue_background_migration_jobs_by_range_at_intervals(
- forks,
- MIGRATION,
- BackgroundMigrationWorker.minimum_interval,
- batch_size: BATCH_SIZE
- )
- end
-
- def down
- # No-op. No need to make this reversible. In case the jobs enqueued runs and
- # fails at some point, some records will be created. When rescheduled, those
- # records won't be re-created. It's also hard to track which records to clean
- # up if ever.
- end
-end
diff --git a/db/schema.rb b/db/schema.rb
index 2360c0f4025..2e7e0d8ce7c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3723,6 +3723,25 @@ ActiveRecord::Schema.define(version: 2020_03_04_160823) do
t.index ["project_id", "programming_language_id"], name: "index_repository_languages_on_project_and_languages_id", unique: true
end
+ create_table "requirements", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "project_id", null: false
+ t.integer "author_id"
+ t.integer "iid", null: false
+ t.integer "cached_markdown_version"
+ t.integer "state", limit: 2, default: 1, null: false
+ t.string "title", limit: 255, null: false
+ t.text "title_html"
+ t.index ["author_id"], name: "index_requirements_on_author_id"
+ t.index ["created_at"], name: "index_requirements_on_created_at"
+ t.index ["project_id", "iid"], name: "index_requirements_on_project_id_and_iid", unique: true, where: "(project_id IS NOT NULL)"
+ t.index ["project_id"], name: "index_requirements_on_project_id"
+ t.index ["state"], name: "index_requirements_on_state"
+ t.index ["title"], name: "index_requirements_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["updated_at"], name: "index_requirements_on_updated_at"
+ end
+
create_table "resource_label_events", force: :cascade do |t|
t.integer "action", null: false
t.integer "issue_id"
@@ -5001,6 +5020,8 @@ ActiveRecord::Schema.define(version: 2020_03_04_160823) do
add_foreign_key "releases", "users", column: "author_id", name: "fk_8e4456f90f", on_delete: :nullify
add_foreign_key "remote_mirrors", "projects", name: "fk_43a9aa4ca8", on_delete: :cascade
add_foreign_key "repository_languages", "projects", on_delete: :cascade
+ add_foreign_key "requirements", "projects", on_delete: :cascade
+ add_foreign_key "requirements", "users", column: "author_id", on_delete: :nullify
add_foreign_key "resource_label_events", "epics", on_delete: :cascade
add_foreign_key "resource_label_events", "issues", on_delete: :cascade
add_foreign_key "resource_label_events", "labels", on_delete: :nullify
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index 7d0fc43f810..0ee8f26b97c 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -54,8 +54,10 @@ Add the following to your `sshd_config` file. This is usually located at
Omnibus Docker:
```plaintext
-AuthorizedKeysCommand /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
-AuthorizedKeysCommandUser git
+Match User git # Apply the AuthorizedKeysCommands to the git user only
+ AuthorizedKeysCommand /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
+ AuthorizedKeysCommandUser git
+Match all # End match, settings apply to all users again
```
Reload OpenSSH:
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index b8df3286da2..0f7780907f6 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -104,7 +104,8 @@ Review Apps are automatically stopped 2 days after the last deployment thanks to
the [Environment auto-stop](../../ci/environments.html#environments-auto-stop) feature.
If you need your Review App to stay up for a longer time, you can
-[pin its environment](../../ci/environments.html#auto-stop-example).
+[pin its environment](../../ci/environments.html#auto-stop-example) or retry the
+`review-deploy` job to update the "latest deployed at" time.
The `review-cleanup` job that automatically runs in scheduled
pipelines (and is manual in merge request) stops stale Review Apps after 5 days,
diff --git a/lib/gitlab/background_migration/link_lfs_objects.rb b/lib/gitlab/background_migration/link_lfs_objects.rb
index 3131b5d5125..69c03f617bf 100644
--- a/lib/gitlab/background_migration/link_lfs_objects.rb
+++ b/lib/gitlab/background_migration/link_lfs_objects.rb
@@ -6,8 +6,6 @@ module Gitlab
class LinkLfsObjects
# Model definition used for migration
class ForkNetworkMember < ActiveRecord::Base
- include EachBatch
-
self.table_name = 'fork_network_members'
def self.with_non_existing_lfs_objects
@@ -25,62 +23,8 @@ module Gitlab
end
end
- # Model definition used for migration
- class Project < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'projects'
-
- has_one :fork_network_member, class_name: 'LinkLfsObjects::ForkNetworkMember'
-
- def self.with_non_existing_lfs_objects
- fork_network_members =
- ForkNetworkMember.with_non_existing_lfs_objects
- .select(1)
- .where('fork_network_members.project_id = projects.id')
-
- where('EXISTS (?)', fork_network_members)
- end
- end
-
- # Model definition used for migration
- class LfsObjectsProject < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'lfs_objects_projects'
- end
-
- BATCH_SIZE = 1000
-
def perform(start_id, end_id)
- forks =
- Project
- .with_non_existing_lfs_objects
- .where(id: start_id..end_id)
-
- forks.includes(:fork_network_member).find_each do |project|
- LfsObjectsProject
- .select("lfs_objects_projects.lfs_object_id, #{project.id}, NOW(), NOW()")
- .where(project_id: project.fork_network_member.forked_from_project_id)
- .each_batch(of: BATCH_SIZE) do |batch|
- execute <<~SQL
- INSERT INTO lfs_objects_projects (lfs_object_id, project_id, created_at, updated_at)
- #{batch.to_sql}
- SQL
- end
- end
-
- logger.info(message: "LinkLfsObjects: created missing LfsObjectsProject for Projects #{forks.map(&:id).join(', ')}")
- end
-
- private
-
- def execute(sql)
- ::ActiveRecord::Base.connection.execute(sql)
- end
-
- def logger
- @logger ||= Gitlab::BackgroundMigration::Logger.build
+ # no-op as some queries times out
end
end
end
diff --git a/lib/gitlab/cycle_analytics/usage_data.rb b/lib/gitlab/cycle_analytics/usage_data.rb
index acfb641aeec..e58def57e69 100644
--- a/lib/gitlab/cycle_analytics/usage_data.rb
+++ b/lib/gitlab/cycle_analytics/usage_data.rb
@@ -3,15 +3,32 @@
module Gitlab
module CycleAnalytics
class UsageData
+ include Gitlab::Utils::StrongMemoize
PROJECTS_LIMIT = 10
- attr_reader :projects, :options
+ attr_reader :options
def initialize
- @projects = Project.sorted_by_activity.limit(PROJECTS_LIMIT)
@options = { from: 7.days.ago }
end
+ def projects
+ strong_memoize(:projects) do
+ projects = Project.where.not(last_activity_at: nil).order(last_activity_at: :desc).limit(10) +
+ Project.where.not(last_repository_updated_at: nil).order(last_repository_updated_at: :desc).limit(10)
+
+ projects = projects.uniq.sort_by do |project|
+ [project.last_activity_at, project.last_repository_updated_at].min
+ end
+
+ if projects.size < 10
+ projects.concat(Project.where(last_activity_at: nil, last_repository_updated_at: nil).limit(10))
+ end
+
+ projects.uniq.first(10)
+ end
+ end
+
def to_json(*)
total = 0
diff --git a/lib/gitlab/import_export/error.rb b/lib/gitlab/import_export/error.rb
index 454dc778b6b..f11b7a0a298 100644
--- a/lib/gitlab/import_export/error.rb
+++ b/lib/gitlab/import_export/error.rb
@@ -2,6 +2,13 @@
module Gitlab
module ImportExport
- Error = Class.new(StandardError)
+ class Error < StandardError
+ def self.permission_error(user, importable)
+ self.new(
+ "User with ID: %s does not have required permissions for %s: %s with ID: %s" %
+ [user.id, importable.class.name, importable.name, importable.id]
+ )
+ end
+ end
end
end
diff --git a/lib/gitlab/import_export/group/tree_restorer.rb b/lib/gitlab/import_export/group/tree_restorer.rb
index e6f49dcac7a..cbaa6929efa 100644
--- a/lib/gitlab/import_export/group/tree_restorer.rb
+++ b/lib/gitlab/import_export/group/tree_restorer.rb
@@ -49,11 +49,7 @@ module Gitlab
json = IO.read(@path)
ActiveSupport::JSON.decode(json)
rescue => e
- @shared.logger.error(
- group_id: @group.id,
- group_name: @group.name,
- message: "Import/Export error: #{e.message}"
- )
+ @shared.error(e)
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
end
diff --git a/lib/gitlab/import_export/project/base_task.rb b/lib/gitlab/import_export/project/base_task.rb
new file mode 100644
index 00000000000..6a7b24421c9
--- /dev/null
+++ b/lib/gitlab/import_export/project/base_task.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ module Project
+ class BaseTask
+ include Gitlab::WithRequestStore
+
+ def initialize(opts, logger: Logger.new($stdout))
+ @project_path = opts.fetch(:project_path)
+ @file_path = opts.fetch(:file_path)
+ @namespace = Namespace.find_by_full_path(opts.fetch(:namespace_path))
+ @current_user = User.find_by_username(opts.fetch(:username))
+ @measurement_enabled = opts.fetch(:measurement_enabled)
+ @measurement = Gitlab::Utils::Measuring.new(logger: logger) if @measurement_enabled
+ @logger = logger
+ end
+
+ private
+
+ attr_reader :measurement, :project, :namespace, :current_user, :file_path, :project_path, :logger
+
+ def measurement_enabled?
+ @measurement_enabled
+ end
+
+ def success(message)
+ logger.info(message)
+
+ true
+ end
+
+ def error(message)
+ logger.error(message)
+
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/project/export_task.rb b/lib/gitlab/import_export/project/export_task.rb
new file mode 100644
index 00000000000..ec287380c48
--- /dev/null
+++ b/lib/gitlab/import_export/project/export_task.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ module Project
+ class ExportTask < BaseTask
+ def initialize(*)
+ super
+
+ @project = namespace.projects.find_by_path(@project_path)
+ end
+
+ def export
+ return error("Project with path: #{project_path} was not found. Please provide correct project path") unless project
+ return error("Invalid file path: #{file_path}. Please provide correct file path") unless file_path_exists?
+
+ with_export do
+ ::Projects::ImportExport::ExportService.new(project, current_user)
+ .execute(Gitlab::ImportExport::AfterExportStrategies::MoveFileStrategy.new(archive_path: file_path))
+ end
+
+ success('Done!')
+ end
+
+ private
+
+ def file_path_exists?
+ directory = File.dirname(file_path)
+
+ Dir.exist?(directory)
+ end
+
+ def with_export
+ with_request_store do
+ ::Gitlab::GitalyClient.allow_n_plus_1_calls do
+ measurement_enabled? ? measurement.with_measuring { yield } : yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/project/import_task.rb b/lib/gitlab/import_export/project/import_task.rb
new file mode 100644
index 00000000000..ae654ddbeaf
--- /dev/null
+++ b/lib/gitlab/import_export/project/import_task.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ module Project
+ class ImportTask < BaseTask
+ def import
+ show_import_start_message
+
+ run_isolated_sidekiq_job
+
+ show_import_failures_count
+
+ return error(project.import_state.last_error) if project.import_state&.last_error
+ return error(project.errors.full_messages.to_sentence) if project.errors.any?
+
+ success('Done!')
+ end
+
+ private
+
+ # We want to ensure that all Sidekiq jobs are executed
+ # synchronously as part of that process.
+ # This ensures that all expensive operations do not escape
+ # to general Sidekiq clusters/nodes.
+ def with_isolated_sidekiq_job
+ Sidekiq::Testing.fake! do
+ with_request_store do
+ # If you are attempting to import a large project into a development environment,
+ # you may see Gitaly throw an error about too many calls or invocations.
+ # This is due to a n+1 calls limit being set for development setups (not enforced in production)
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24475#note_283090635
+ # For development setups, this code-path will be excluded from n+1 detection.
+ ::Gitlab::GitalyClient.allow_n_plus_1_calls do
+ measurement_enabled? ? measurement.with_measuring { yield } : yield
+ end
+ end
+
+ true
+ end
+ end
+
+ def run_isolated_sidekiq_job
+ with_isolated_sidekiq_job do
+ @project = create_project
+
+ execute_sidekiq_job
+ end
+ end
+
+ def create_project
+ # We are disabling ObjectStorage for `import`
+ # as it is too slow to handle big archives:
+ # 1. DB transaction timeouts on upload
+ # 2. Download of archive before unpacking
+ disable_upload_object_storage do
+ service = Projects::GitlabProjectsImportService.new(
+ current_user,
+ {
+ namespace_id: namespace.id,
+ path: project_path,
+ file: File.open(file_path)
+ }
+ )
+
+ service.execute
+ end
+ end
+
+ def execute_sidekiq_job
+ Sidekiq::Worker.drain_all
+ end
+
+ def disable_upload_object_storage
+ overwrite_uploads_setting('background_upload', false) do
+ overwrite_uploads_setting('direct_upload', false) do
+ yield
+ end
+ end
+ end
+
+ def overwrite_uploads_setting(key, value)
+ old_value = Settings.uploads.object_store[key]
+ Settings.uploads.object_store[key] = value
+
+ yield
+
+ ensure
+ Settings.uploads.object_store[key] = old_value
+ end
+
+ def full_path
+ "#{namespace.full_path}/#{project_path}"
+ end
+
+ def show_import_start_message
+ logger.info "Importing GitLab export: #{file_path} into GitLab" \
+ " #{full_path}" \
+ " as #{current_user.name}"
+ end
+
+ def show_import_failures_count
+ return unless project.import_failures.exists?
+
+ logger.info "Total number of not imported relations: #{project.import_failures.count}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 8d81b2af065..09ed4eb568d 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -94,14 +94,6 @@ module Gitlab
end
end
- def log_error(details)
- @logger.error(log_base_data.merge(details))
- end
-
- def log_debug(details)
- @logger.debug(log_base_data.merge(details))
- end
-
def log_base_data
log = {
importer: 'Import/Export',
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 6e29a3e4cc4..09ea1c49c22 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -122,6 +122,8 @@ module Gitlab
def cycle_analytics_usage_data
Gitlab::CycleAnalytics::UsageData.new.to_json
+ rescue ActiveRecord::StatementInvalid
+ { avg_cycle_analytics: {} }
end
def features_usage_data
@@ -232,7 +234,7 @@ module Gitlab
end
def count(relation, column = nil, fallback: -1, batch: true)
- if batch && Feature.enabled?(:usage_ping_batch_counter)
+ if batch && Feature.enabled?(:usage_ping_batch_counter, default_enabled: true)
Gitlab::Database::BatchCount.batch_count(relation, column)
else
relation.count
@@ -242,7 +244,7 @@ module Gitlab
end
def distinct_count(relation, column = nil, fallback: -1, batch: true)
- if batch && Feature.enabled?(:usage_ping_batch_counter)
+ if batch && Feature.enabled?(:usage_ping_batch_counter, default_enabled: true)
Gitlab::Database::BatchCount.batch_distinct_count(relation, column)
else
relation.distinct_count_by(column)
diff --git a/lib/gitlab/utils/measuring.rb b/lib/gitlab/utils/measuring.rb
index 20c57e777d8..c9e6cb9c039 100644
--- a/lib/gitlab/utils/measuring.rb
+++ b/lib/gitlab/utils/measuring.rb
@@ -59,14 +59,15 @@ module Gitlab
end
def duration_in_numbers(duration_in_seconds)
+ milliseconds = duration_in_seconds.in_milliseconds % 1.second.in_milliseconds
seconds = duration_in_seconds % 1.minute
minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute)
hours = duration_in_seconds / 1.hour
if hours == 0
- "%02d:%02d" % [minutes, seconds]
+ "%02d:%02d:%03d" % [minutes, seconds, milliseconds]
else
- "%02d:%02d:%02d" % [hours, minutes, seconds]
+ "%02d:%02d:%02d:%03d" % [hours, minutes, seconds, milliseconds]
end
end
end
diff --git a/lib/tasks/gitlab/import_export/export.rake b/lib/tasks/gitlab/import_export/export.rake
index 6cedf4e8cf1..ae54636fdd3 100644
--- a/lib/tasks/gitlab/import_export/export.rake
+++ b/lib/tasks/gitlab/import_export/export.rake
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require 'gitlab/with_request_store'
-
# Export project to archive
#
# @example
@@ -14,81 +12,36 @@ namespace :gitlab do
# Load it here to avoid polluting Rake tasks with Sidekiq test warnings
require 'sidekiq/testing'
- warn_user_is_not_gitlab
-
- if ENV['IMPORT_DEBUG'].present?
- ActiveRecord::Base.logger = Logger.new(STDOUT)
- Gitlab::Metrics::Exporter::SidekiqExporter.instance.start
- end
-
- GitlabProjectExport.new(
- namespace_path: args.namespace_path,
- project_path: args.project_path,
- username: args.username,
- file_path: args.archive_path,
- measurement_enabled: Gitlab::Utils.to_boolean(args.measurement_enabled)
- ).export
- end
- end
-end
-
-class GitlabProjectExport
- include Gitlab::WithRequestStore
-
- def initialize(opts)
- @project_path = opts.fetch(:project_path)
- @file_path = opts.fetch(:file_path)
- @current_user = User.find_by_username(opts.fetch(:username))
- namespace = Namespace.find_by_full_path(opts.fetch(:namespace_path))
- @project = namespace.projects.find_by_path(@project_path)
- @measurement_enabled = opts.fetch(:measurement_enabled)
- @measurable = Gitlab::Utils::Measuring.new if @measurement_enabled
- end
-
- def export
- validate_project
- validate_file_path
-
- with_export do
- ::Projects::ImportExport::ExportService.new(project, current_user)
- .execute(Gitlab::ImportExport::AfterExportStrategies::MoveFileStrategy.new(archive_path: file_path))
- end
-
- puts 'Done!'
- rescue StandardError => e
- puts "Exception: #{e.message}"
- puts e.backtrace
- exit 1
- end
-
- private
-
- attr_reader :measurable, :project, :current_user, :file_path, :project_path
-
- def validate_project
- unless project
- puts "Error: Project with path: #{project_path} was not found. Please provide correct project path"
- exit 1
- end
- end
-
- def validate_file_path
- directory = File.dirname(file_path)
- unless Dir.exist?(directory)
- puts "Error: Invalid file path: #{file_path}. Please provide correct file path"
- exit 1
- end
- end
-
- def with_export
- with_request_store do
- ::Gitlab::GitalyClient.allow_n_plus_1_calls do
- measurement_enabled? ? measurable.with_measuring { yield } : yield
+ logger = Logger.new($stdout)
+
+ begin
+ warn_user_is_not_gitlab
+
+ if ENV['EXPORT_DEBUG'].present?
+ ActiveRecord::Base.logger = logger
+ Gitlab::Metrics::Exporter::SidekiqExporter.instance.start
+ logger.level = Logger::DEBUG
+ else
+ logger.level = Logger::INFO
+ end
+
+ task = Gitlab::ImportExport::Project::ExportTask.new(
+ namespace_path: args.namespace_path,
+ project_path: args.project_path,
+ username: args.username,
+ file_path: args.archive_path,
+ measurement_enabled: Gitlab::Utils.to_boolean(args.measurement_enabled),
+ logger: logger
+ )
+
+ success = task.export
+
+ exit(success)
+ rescue StandardError => e
+ logger.error "Exception: #{e.message}"
+ logger.debug e.backtrace
+ exit 1
end
end
end
-
- def measurement_enabled?
- @measurement_enabled
- end
end
diff --git a/lib/tasks/gitlab/import_export/import.rake b/lib/tasks/gitlab/import_export/import.rake
index 4ed724a5c82..6e2d0e75da8 100644
--- a/lib/tasks/gitlab/import_export/import.rake
+++ b/lib/tasks/gitlab/import_export/import.rake
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require 'gitlab/with_request_store'
-
# Import large project archives
#
# This task:
@@ -18,148 +16,36 @@ namespace :gitlab do
# Load it here to avoid polluting Rake tasks with Sidekiq test warnings
require 'sidekiq/testing'
- warn_user_is_not_gitlab
-
- if ENV['IMPORT_DEBUG'].present?
- ActiveRecord::Base.logger = Logger.new(STDOUT)
- end
-
- GitlabProjectImport.new(
- namespace_path: args.namespace_path,
- project_path: args.project_path,
- username: args.username,
- file_path: args.archive_path,
- measurement_enabled: Gitlab::Utils.to_boolean(args.measurement_enabled)
- ).import
- end
- end
-end
-
-class GitlabProjectImport
- include Gitlab::WithRequestStore
-
- def initialize(opts)
- @project_path = opts.fetch(:project_path)
- @file_path = opts.fetch(:file_path)
- @namespace = Namespace.find_by_full_path(opts.fetch(:namespace_path))
- @current_user = User.find_by_username(opts.fetch(:username))
- @measurement_enabled = opts.fetch(:measurement_enabled)
- @measurement = Gitlab::Utils::Measuring.new if @measurement_enabled
- end
-
- def import
- show_import_start_message
+ logger = Logger.new($stdout)
- run_isolated_sidekiq_job
+ begin
+ warn_user_is_not_gitlab
- show_import_failures_count
-
- if project&.import_state&.last_error
- puts "ERROR: #{project.import_state.last_error}"
- exit 1
- elsif project.errors.any?
- puts "ERROR: #{project.errors.full_messages.join(', ')}"
- exit 1
- else
- puts 'Done!'
- end
- rescue StandardError => e
- puts "Exception: #{e.message}"
- puts e.backtrace
- exit 1
- end
-
- private
-
- attr_reader :measurement, :project, :namespace, :current_user, :file_path, :project_path
-
- def measurement_enabled?
- @measurement_enabled
- end
-
- # We want to ensure that all Sidekiq jobs are executed
- # synchronously as part of that process.
- # This ensures that all expensive operations do not escape
- # to general Sidekiq clusters/nodes.
- def with_isolated_sidekiq_job
- Sidekiq::Testing.fake! do
- with_request_store do
- # If you are attempting to import a large project into a development environment,
- # you may see Gitaly throw an error about too many calls or invocations.
- # This is due to a n+1 calls limit being set for development setups (not enforced in production)
- # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24475#note_283090635
- # For development setups, this code-path will be excluded from n+1 detection.
- ::Gitlab::GitalyClient.allow_n_plus_1_calls do
- measurement_enabled? ? measurement.with_measuring { yield } : yield
+ if ENV['IMPORT_DEBUG'].present?
+ ActiveRecord::Base.logger = logger
+ Gitlab::Metrics::Exporter::SidekiqExporter.instance.start
+ logger.level = Logger::DEBUG
+ else
+ logger.level = Logger::INFO
end
- end
-
- true
- end
- end
-
- def run_isolated_sidekiq_job
- with_isolated_sidekiq_job do
- @project = create_project
-
- execute_sidekiq_job
- end
- end
-
- def create_project
- # We are disabling ObjectStorage for `import`
- # as it is too slow to handle big archives:
- # 1. DB transaction timeouts on upload
- # 2. Download of archive before unpacking
- disable_upload_object_storage do
- service = Projects::GitlabProjectsImportService.new(
- current_user,
- {
- namespace_id: namespace.id,
- path: project_path,
- file: File.open(file_path)
- }
- )
-
- service.execute
- end
- end
-
- def execute_sidekiq_job
- Sidekiq::Worker.drain_all
- end
- def disable_upload_object_storage
- overwrite_uploads_setting('background_upload', false) do
- overwrite_uploads_setting('direct_upload', false) do
- yield
+ task = Gitlab::ImportExport::Project::ImportTask.new(
+ namespace_path: args.namespace_path,
+ project_path: args.project_path,
+ username: args.username,
+ file_path: args.archive_path,
+ measurement_enabled: Gitlab::Utils.to_boolean(args.measurement_enabled),
+ logger: logger
+ )
+
+ success = task.import
+
+ exit(success)
+ rescue StandardError => e
+ logger.error "Exception: #{e.message}"
+ logger.debug e.backtrace
+ exit 1
end
end
end
-
- def overwrite_uploads_setting(key, value)
- old_value = Settings.uploads.object_store[key]
- Settings.uploads.object_store[key] = value
-
- yield
-
- ensure
- Settings.uploads.object_store[key] = old_value
- end
-
- def full_path
- "#{namespace.full_path}/#{project_path}"
- end
-
- def show_import_start_message
- puts "Importing GitLab export: #{file_path} into GitLab" \
- " #{full_path}" \
- " as #{current_user.name}"
- end
-
- def show_import_failures_count
- return unless project.import_failures.exists?
-
- puts "Total number of not imported relations: #{project.import_failures.count}"
- end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7c63ec93374..057f6a41980 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1142,6 +1142,9 @@ msgstr ""
msgid "Add email address"
msgstr ""
+msgid "Add environment"
+msgstr ""
+
msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
msgstr ""
@@ -2453,6 +2456,9 @@ msgstr ""
msgid "At least one of group_id or project_id must be specified"
msgstr ""
+msgid "At risk"
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -3133,9 +3139,6 @@ msgstr ""
msgid "CI / CD"
msgstr ""
-msgid "CI / CD Analytics"
-msgstr ""
-
msgid "CI / CD Charts"
msgstr ""
@@ -5612,6 +5615,9 @@ msgstr ""
msgid "Create"
msgstr ""
+msgid "Create %{environment}"
+msgstr ""
+
msgid "Create %{type} token"
msgstr ""
@@ -7432,6 +7438,9 @@ msgstr ""
msgid "Enter a number"
msgstr ""
+msgid "Enter a whole number between 0 and 100"
+msgstr ""
+
msgid "Enter at least three characters to search"
msgstr ""
@@ -7456,6 +7465,9 @@ msgstr ""
msgid "Enter number of issues"
msgstr ""
+msgid "Enter one or more user ID separated by commas"
+msgstr ""
+
msgid "Enter the issue description"
msgstr ""
@@ -8554,6 +8566,18 @@ msgstr ""
msgid "FeatureFlags|User IDs"
msgstr ""
+msgid "FeatureFlag|Delete strategy"
+msgstr ""
+
+msgid "FeatureFlag|Percentage"
+msgstr ""
+
+msgid "FeatureFlag|Type"
+msgstr ""
+
+msgid "FeatureFlag|User IDs"
+msgstr ""
+
msgid "Feb"
msgstr ""
@@ -12790,6 +12814,9 @@ msgstr ""
msgid "Need help?"
msgstr ""
+msgid "Needs attention"
+msgstr ""
+
msgid "Network"
msgstr ""
@@ -13398,6 +13425,9 @@ msgstr ""
msgid "Omnibus Protected Paths throttle is active. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
msgstr ""
+msgid "On track"
+msgstr ""
+
msgid "Onboarding"
msgstr ""
@@ -13901,6 +13931,9 @@ msgstr ""
msgid "People without permission will never get a notification."
msgstr ""
+msgid "Percent rollout (logged in users)"
+msgstr ""
+
msgid "Percentage"
msgstr ""
@@ -14552,6 +14585,9 @@ msgstr ""
msgid "Proceed"
msgstr ""
+msgid "Productivity"
+msgstr ""
+
msgid "Productivity Analytics"
msgstr ""
@@ -16431,9 +16467,6 @@ msgstr ""
msgid "Repository"
msgstr ""
-msgid "Repository Analytics"
-msgstr ""
-
msgid "Repository Graph"
msgstr ""
@@ -17425,6 +17458,9 @@ msgstr ""
msgid "Select source branch"
msgstr ""
+msgid "Select strategy activation method"
+msgstr ""
+
msgid "Select target branch"
msgstr ""
@@ -17913,6 +17949,9 @@ msgstr ""
msgid "Sidebar|Only numeral characters allowed"
msgstr ""
+msgid "Sidebar|Status"
+msgstr ""
+
msgid "Sidebar|Weight"
msgstr ""
@@ -21383,6 +21422,9 @@ msgstr ""
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User IDs"
+msgstr ""
+
msgid "User OAuth applications"
msgstr ""
@@ -21740,6 +21782,9 @@ msgstr ""
msgid "Value"
msgstr ""
+msgid "Value Stream"
+msgstr ""
+
msgid "Value Stream Analytics"
msgstr ""
@@ -22449,6 +22494,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
+msgstr ""
+
msgid "You are not allowed to unlink your primary login account"
msgstr ""
@@ -22569,9 +22617,6 @@ msgstr ""
msgid "You can try again using %{begin_link}basic search%{end_link}"
msgstr ""
-msgid "You can't commit to this project"
-msgstr ""
-
msgid "You cannot access the raw file. Please wait a minute."
msgstr ""
@@ -23512,6 +23557,9 @@ msgstr ""
msgid "is not an email you own"
msgstr ""
+msgid "is not in the group enforcing Group Managed Account"
+msgstr ""
+
msgid "is too long (%{current_value}). The maximum size is %{max_size}."
msgstr ""
diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb
index 823c8cc8fad..a71b930d35f 100644
--- a/spec/features/groups/group_page_with_external_authorization_service_spec.rb
+++ b/spec/features/groups/group_page_with_external_authorization_service_spec.rb
@@ -47,7 +47,7 @@ describe 'The group page' do
expect(page).to have_link('Group overview')
expect(page).to have_link('Details')
expect(page).not_to have_link('Activity')
- expect(page).not_to have_link('Contribution Analytics')
+ expect(page).not_to have_link('Contribution')
expect(page).not_to have_link('Issues')
expect(page).not_to have_link('Merge Requests')
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index 4d45bcf639d..0c457c11fce 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -10,7 +10,7 @@ describe 'Group navbar' do
{
nav_item: _('Analytics'),
nav_sub_items: [
- _('Contribution Analytics')
+ _('Contribution')
]
}
end
@@ -63,7 +63,7 @@ describe 'Group navbar' do
before do
stub_licensed_features(productivity_analytics: true)
- analytics_nav_item[:nav_sub_items] << _('Productivity Analytics')
+ analytics_nav_item[:nav_sub_items] << _('Productivity')
group.add_maintainer(user)
sign_in(user)
@@ -78,7 +78,7 @@ describe 'Group navbar' do
before do
stub_licensed_features(cycle_analytics_for_groups: true)
- analytics_nav_item[:nav_sub_items] << _('Value Stream Analytics')
+ analytics_nav_item[:nav_sub_items] << _('Value Stream')
group.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 9baba80bf06..e7c675bf6bf 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -225,6 +225,29 @@ describe 'Issue Sidebar' do
it 'does not have a option to edit labels' do
expect(page).not_to have_selector('.block.labels .edit-link')
end
+
+ context 'interacting with collapsed sidebar', :js do
+ collapsed_sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
+ expanded_sidebar_selector = 'aside.right-sidebar.right-sidebar-expanded'
+ lock_sidebar_block = '.block.lock'
+ lock_button = '.block.lock .btn-close'
+ collapsed_sidebar_block_icon = '.sidebar-collapsed-icon'
+
+ before do
+ resize_screen_sm
+ end
+
+ it 'expands then does not show the lock dialog form' do
+ expect(page).to have_css(collapsed_sidebar_selector)
+
+ page.within(lock_sidebar_block) do
+ find(collapsed_sidebar_block_icon).click
+ end
+
+ expect(page).to have_css(expanded_sidebar_selector)
+ expect(page).not_to have_selector(lock_button)
+ end
+ end
end
def visit_issue(project, issue)
diff --git a/spec/features/projects/active_tabs_spec.rb b/spec/features/projects/active_tabs_spec.rb
index d7060c63d6d..69681da6b2a 100644
--- a/spec/features/projects/active_tabs_spec.rb
+++ b/spec/features/projects/active_tabs_spec.rb
@@ -136,16 +136,16 @@ describe 'Project active tab' do
context 'on project Analytics/Repository Analytics' do
it_behaves_like 'page has active tab', _('Analytics')
- it_behaves_like 'page has active sub tab', _('Repository Analytics')
+ it_behaves_like 'page has active sub tab', _('Repository')
end
context 'on project Analytics/Cycle Analytics' do
before do
- click_tab(_('CI / CD Analytics'))
+ click_tab(_('CI / CD'))
end
it_behaves_like 'page has active tab', _('Analytics')
- it_behaves_like 'page has active sub tab', _('CI / CD Analytics')
+ it_behaves_like 'page has active sub tab', _('CI / CD')
end
end
end
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 95a8f974261..dabb2b2dbf2 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -10,10 +10,10 @@ describe 'Project navbar' do
{
nav_item: _('Analytics'),
nav_sub_items: [
- _('CI / CD Analytics'),
+ _('CI / CD'),
(_('Code Review') if Gitlab.ee?),
- _('Repository Analytics'),
- _('Value Stream Analytics')
+ _('Repository'),
+ _('Value Stream')
]
}
end
@@ -114,7 +114,7 @@ describe 'Project navbar' do
before do
stub_licensed_features(issues_analytics: true)
- analytics_nav_item[:nav_sub_items] << _('Issues Analytics')
+ analytics_nav_item[:nav_sub_items] << _('Issues')
analytics_nav_item[:nav_sub_items].sort!
visit project_path(project)
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index beed1c07e51..33c2e65633e 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -222,7 +222,7 @@ describe 'User uses shortcuts', :js do
find('body').native.send_key('d')
expect(page).to have_active_navigation(_('Analytics'))
- expect(page).to have_active_sub_navigation(_('Repository Analytics'))
+ expect(page).to have_active_sub_navigation(_('Repository'))
end
end
end
diff --git a/spec/fixtures/group_export_invalid_subrelations.tar.gz b/spec/fixtures/group_export_invalid_subrelations.tar.gz
new file mode 100644
index 00000000000..6a8f1453517
--- /dev/null
+++ b/spec/fixtures/group_export_invalid_subrelations.tar.gz
Binary files differ
diff --git a/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap
new file mode 100644
index 00000000000..e47a7dcfa2a
--- /dev/null
+++ b/spec/frontend/blob/components/__snapshots__/blob_edit_header_spec.js.snap
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Blob Header Editing rendering matches the snapshot 1`] = `
+<div
+ class="js-file-title file-title-flex-parent"
+>
+ <gl-form-input-stub
+ class="form-control js-snippet-file-name qa-snippet-file-name"
+ id="snippet_file_name"
+ name="snippet_file_name"
+ placeholder="Give your file a name to add code highlighting, e.g. example.rb for Ruby"
+ type="text"
+ value="foo.md"
+ />
+</div>
+`;
diff --git a/spec/frontend/blob/components/blob_edit_header_spec.js b/spec/frontend/blob/components/blob_edit_header_spec.js
new file mode 100644
index 00000000000..db7d7d7d48d
--- /dev/null
+++ b/spec/frontend/blob/components/blob_edit_header_spec.js
@@ -0,0 +1,50 @@
+import { shallowMount } from '@vue/test-utils';
+import BlobEditHeader from '~/blob/components/blob_edit_header.vue';
+import { GlFormInput } from '@gitlab/ui';
+
+describe('Blob Header Editing', () => {
+ let wrapper;
+ const value = 'foo.md';
+
+ function createComponent() {
+ wrapper = shallowMount(BlobEditHeader, {
+ propsData: {
+ value,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('rendering', () => {
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('contains a form input field', () => {
+ expect(wrapper.contains(GlFormInput)).toBe(true);
+ });
+ });
+
+ describe('functionality', () => {
+ it('emits input event when the blob name is changed', () => {
+ const inputComponent = wrapper.find(GlFormInput);
+ const newValue = 'bar.txt';
+
+ wrapper.setData({
+ name: newValue,
+ });
+ inputComponent.vm.$emit('change');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted().input[0]).toEqual([newValue]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js
index 3ee97b978fd..c7e52eae9bd 100644
--- a/spec/frontend/sidebar/mock_data.js
+++ b/spec/frontend/sidebar/mock_data.js
@@ -178,8 +178,17 @@ const RESPONSE_MAP = {
},
};
+const graphQlResponseData = {
+ project: {
+ issue: {
+ healthStatus: 'onTrack',
+ },
+ },
+};
+
const mockData = {
responseMap: RESPONSE_MAP,
+ graphQlResponseData,
mediator: {
endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras',
toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
@@ -195,6 +204,7 @@ const mockData = {
},
rootPath: '/',
fullPath: '/gitlab-org/gitlab-shell',
+ id: 1,
},
time: {
time_estimate: 3600,
diff --git a/spec/frontend/snippet/snippet_bundle_spec.js b/spec/frontend/snippet/snippet_bundle_spec.js
index af98e8d665d..12d20d5cd85 100644
--- a/spec/frontend/snippet/snippet_bundle_spec.js
+++ b/spec/frontend/snippet/snippet_bundle_spec.js
@@ -27,7 +27,7 @@ describe('Snippet editor', () => {
setHTMLFixture(`
<div class="snippet-form-holder">
<form>
- <input class="snippet-file-name" type="text" value="${name}">
+ <input class="js-snippet-file-name" type="text" value="${name}">
<input class="snippet-file-content" type="hidden" value="${content}">
<pre id="editor"></pre>
</form>
@@ -39,7 +39,7 @@ describe('Snippet editor', () => {
setUpFixture(name, content);
editorEl = document.getElementById('editor');
contentEl = document.querySelector('.snippet-file-content');
- fileNameEl = document.querySelector('.snippet-file-name');
+ fileNameEl = document.querySelector('.js-snippet-file-name');
form = document.querySelector('.snippet-form-holder form');
initEditor();
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index 0a3f0d6901f..5296908afe2 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -83,4 +83,17 @@ describe('LockIssueSidebar', () => {
done();
});
});
+
+ it('does not display the edit form when opened from collapsed state if not editable', done => {
+ expect(vm2.isLockDialogOpen).toBe(false);
+
+ vm2.$el.querySelector('.sidebar-collapsed-icon').click();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm2.isLockDialogOpen).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index b0412105e3f..2aa30fd1cc6 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
-import SidebarService from '~/sidebar/services/sidebar_service';
+import SidebarService, { gqClient } from '~/sidebar/services/sidebar_service';
import Mock from './mock_data';
const { mediator: mediatorMockData } = Mock;
@@ -44,12 +44,18 @@ describe('Sidebar mediator', function() {
it('fetches the data', done => {
const mockData = Mock.responseMap.GET[mediatorMockData.endpoint];
mock.onGet(mediatorMockData.endpoint).reply(200, mockData);
+
+ const mockGraphQlData = Mock.graphQlResponseData;
+ spyOn(gqClient, 'query').and.returnValue({
+ data: mockGraphQlData,
+ });
+
spyOn(this.mediator, 'processFetchedData').and.callThrough();
this.mediator
.fetch()
.then(() => {
- expect(this.mediator.processFetchedData).toHaveBeenCalledWith(mockData);
+ expect(this.mediator.processFetchedData).toHaveBeenCalledWith(mockData, mockGraphQlData);
})
.then(done)
.catch(done.fail);
diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb
deleted file mode 100644
index aba903e2f26..00000000000
--- a/spec/lib/gitlab/background_migration/link_lfs_objects_spec.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::LinkLfsObjects, :migration, schema: 2020_02_10_062432 do
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:fork_networks) { table(:fork_networks) }
- let(:fork_network_members) { table(:fork_network_members) }
- let(:lfs_objects) { table(:lfs_objects) }
- let(:lfs_objects_projects) { table(:lfs_objects_projects) }
-
- let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
-
- let!(:source_project) { projects.create(namespace_id: namespace.id) }
- let!(:another_source_project) { projects.create(namespace_id: namespace.id) }
- let!(:project) { projects.create(namespace_id: namespace.id) }
- let!(:another_project) { projects.create(namespace_id: namespace.id) }
- let!(:other_project) { projects.create(namespace_id: namespace.id) }
- let!(:linked_project) { projects.create(namespace_id: namespace.id) }
-
- let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
- let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
-
- let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
- let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
-
- before do
- stub_const("#{described_class}::BATCH_SIZE", 2)
-
- # Create links between projects
- fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
-
- [project, another_project, linked_project].each do |p|
- fork_network_members.create(
- fork_network_id: fork_network.id,
- project_id: p.id,
- forked_from_project_id: fork_network.root_project_id
- )
- end
-
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: other_project.id, forked_from_project_id: another_fork_network.root_project_id)
-
- # Links LFS objects to some projects
- [source_project, another_source_project, linked_project].each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
- end
- end
-
- it 'creates LfsObjectsProject records for forks within the specified range of project IDs' do
- expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger|
- expect(logger).to receive(:info).twice
- end
-
- expect { subject.perform(project.id, other_project.id) }.to change { lfs_objects_projects.count }.by(6)
-
- expect(lfs_object_ids_for(project)).to match_array(lfs_object_ids_for(source_project))
- expect(lfs_object_ids_for(another_project)).to match_array(lfs_object_ids_for(source_project))
- expect(lfs_object_ids_for(other_project)).to match_array(lfs_object_ids_for(another_source_project))
-
- expect { subject.perform(project.id, other_project.id) }.not_to change { lfs_objects_projects.count }
- end
-
- context 'when it is not necessary to create LfsObjectProject records' do
- it 'does not create LfsObjectProject records' do
- expect { subject.perform(linked_project.id, linked_project.id) }
- .not_to change { lfs_objects_projects.count }
- end
- end
-
- def lfs_object_ids_for(project)
- lfs_objects_projects.where(project_id: project.id).pluck(:lfs_object_id)
- end
-end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 78e80576aef..bbb61b4c356 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -467,6 +467,7 @@ project:
- resource_groups
- autoclose_referenced_issues
- status_page_setting
+- requirements
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/error_spec.rb b/spec/lib/gitlab/import_export/error_spec.rb
new file mode 100644
index 00000000000..067f7049097
--- /dev/null
+++ b/spec/lib/gitlab/import_export/error_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::Error do
+ describe '.permission_error' do
+ subject(:error) do
+ described_class.permission_error(user, importable)
+ end
+
+ let(:user) { build(:user, id: 1) }
+
+ context 'when supplied a project' do
+ let(:importable) { build(:project, id: 1, name: 'project1') }
+
+ it 'returns an error with the correct message' do
+ expect(error.message)
+ .to eq 'User with ID: 1 does not have required permissions for Project: project1 with ID: 1'
+ end
+ end
+
+ context 'when supplied a group' do
+ let(:importable) { build(:group, id: 1, name: 'group1') }
+
+ it 'returns an error with the correct message' do
+ expect(error.message)
+ .to eq 'User with ID: 1 does not have required permissions for Group: group1 with ID: 1'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
new file mode 100644
index 00000000000..cf11a1df33c
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe Gitlab::ImportExport::Project::ExportTask do
+ let(:username) { 'root' }
+ let(:namespace_path) { username }
+ let!(:user) { create(:user, username: username) }
+ let(:measurement_enabled) { false }
+ let(:file_path) { 'spec/fixtures/gitlab/import_export/test_project_export.tar.gz' }
+ let(:project) { create(:project, creator: user, namespace: user.namespace) }
+ let(:project_name) { project.name }
+
+ let(:task_params) do
+ {
+ username: username,
+ namespace_path: namespace_path,
+ project_path: project_name,
+ file_path: file_path,
+ measurement_enabled: measurement_enabled
+ }
+ end
+
+ subject { described_class.new(task_params).export }
+
+ context 'when project is found' do
+ let(:project) { create(:project, creator: user, namespace: user.namespace) }
+
+ around do |example|
+ example.run
+ ensure
+ File.delete(file_path)
+ end
+
+ it 'performs project export successfully' do
+ expect { subject }.to output(/Done!/).to_stdout
+
+ expect(subject).to eq(true)
+
+ expect(File).to exist(file_path)
+ end
+
+ it_behaves_like 'measurable'
+ end
+
+ context 'when project is not found' do
+ let(:project_name) { 'invalid project name' }
+
+ it 'logs an error' do
+ expect { subject }.to output(/Project with path: #{project_name} was not found. Please provide correct project path/).to_stdout
+ end
+
+ it 'returns false' do
+ expect(subject).to eq(false)
+ end
+ end
+
+ context 'when file path is invalid' do
+ let(:file_path) { '/invalid_file_path/test_project_export.tar.gz' }
+
+ it 'logs an error' do
+ expect { subject }.to output(/Invalid file path: #{file_path}. Please provide correct file path/ ).to_stdout
+ end
+
+ it 'returns false' do
+ expect(subject).to eq(false)
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/import_export/import_rake_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb
index 3e0bf2d0832..f7b9cbaa095 100644
--- a/spec/tasks/gitlab/import_export/import_rake_spec.rb
+++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb
@@ -2,19 +2,25 @@
require 'rake_helper'
-describe 'gitlab:import_export:import rake task' do
+describe Gitlab::ImportExport::Project::ImportTask do
let(:username) { 'root' }
let(:namespace_path) { username }
let!(:user) { create(:user, username: username) }
let(:measurement_enabled) { false }
- let(:task_params) { [username, namespace_path, project_name, archive_path, measurement_enabled] }
let(:project) { Project.find_by_full_path("#{namespace_path}/#{project_name}") }
+ let(:import_task) { described_class.new(task_params) }
+ let(:task_params) do
+ {
+ username: username,
+ namespace_path: namespace_path,
+ project_path: project_name,
+ file_path: file_path,
+ measurement_enabled: measurement_enabled
+ }
+ end
before do
- Rake.application.rake_require('tasks/gitlab/import_export/import')
allow(Settings.uploads.object_store).to receive(:[]=).and_call_original
- allow_any_instance_of(GitlabProjectImport).to receive(:exit)
- .and_raise(RuntimeError, 'exit not handled')
end
around do |example|
@@ -30,15 +36,16 @@ describe 'gitlab:import_export:import rake task' do
Settings.uploads.object_store['background_upload'] = old_background_upload_setting
end
- subject { run_rake_task('gitlab:import_export:import', task_params) }
+ subject { import_task.import }
context 'when project import is valid' do
let(:project_name) { 'import_rake_test_project' }
- let(:archive_path) { 'spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz' }
+ let(:file_path) { 'spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz' }
it 'performs project import successfully' do
expect { subject }.to output(/Done!/).to_stdout
expect { subject }.not_to raise_error
+ expect(subject).to eq(true)
expect(project.merge_requests.count).to be > 0
expect(project.issues.count).to be > 0
@@ -56,15 +63,13 @@ describe 'gitlab:import_export:import rake task' do
end
end
- expect_next_instance_of(GitlabProjectImport) do |importer|
- expect(importer).to receive(:execute_sidekiq_job).and_wrap_original do |m|
- expect(Settings.uploads.object_store['background_upload']).to eq(true)
- expect(Settings.uploads.object_store['direct_upload']).to eq(true)
- expect(Settings.uploads.object_store).not_to receive(:[]=).with('backgroud_upload', false)
- expect(Settings.uploads.object_store).not_to receive(:[]=).with('direct_upload', false)
+ expect(import_task).to receive(:execute_sidekiq_job).and_wrap_original do |m|
+ expect(Settings.uploads.object_store['background_upload']).to eq(true)
+ expect(Settings.uploads.object_store['direct_upload']).to eq(true)
+ expect(Settings.uploads.object_store).not_to receive(:[]=).with('backgroud_upload', false)
+ expect(Settings.uploads.object_store).not_to receive(:[]=).with('direct_upload', false)
- m.call
- end
+ m.call
end
subject
@@ -75,13 +80,13 @@ describe 'gitlab:import_export:import rake task' do
context 'when project import is invalid' do
let(:project_name) { 'import_rake_invalid_test_project' }
- let(:archive_path) { 'spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz' }
+ let(:file_path) { 'spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz' }
let(:not_imported_message) { /Total number of not imported relations: 1/ }
- let(:error) { /Validation failed: Notes is invalid/ }
it 'performs project import successfully' do
expect { subject }.to output(not_imported_message).to_stdout
expect { subject }.not_to raise_error
+ expect(subject).to eq(true)
expect(project.merge_requests).to be_empty
expect(project.import_state.last_error).to be_nil
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b50481a85cd..7bc9e1a9a32 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -324,6 +324,24 @@ describe Gitlab::UsageData do
end
end
+ describe '#cycle_analytics_usage_data' do
+ subject { described_class.cycle_analytics_usage_data }
+
+ it 'works when queries time out in new' do
+ allow(Gitlab::CycleAnalytics::UsageData)
+ .to receive(:new).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect { subject }.not_to raise_error
+ end
+
+ it 'works when queries time out in to_json' do
+ allow_any_instance_of(Gitlab::CycleAnalytics::UsageData)
+ .to receive(:to_json).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect { subject }.not_to raise_error
+ end
+ end
+
describe '#ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
diff --git a/spec/migrations/reschedule_link_lfs_objects_spec.rb b/spec/migrations/reschedule_link_lfs_objects_spec.rb
deleted file mode 100644
index 6ce6e77f514..00000000000
--- a/spec/migrations/reschedule_link_lfs_objects_spec.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20200217091401_reschedule_link_lfs_objects.rb')
-
-describe RescheduleLinkLfsObjects, :migration, :sidekiq do
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:fork_networks) { table(:fork_networks) }
- let(:fork_network_members) { table(:fork_network_members) }
- let(:lfs_objects) { table(:lfs_objects) }
- let(:lfs_objects_projects) { table(:lfs_objects_projects) }
-
- let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
-
- let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
- let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
-
- let!(:source_project) { projects.create(namespace_id: namespace.id) }
- let!(:another_source_project) { projects.create(namespace_id: namespace.id) }
- let!(:project) { projects.create(namespace_id: namespace.id) }
- let!(:another_project) { projects.create(namespace_id: namespace.id) }
- let!(:other_project) { projects.create(namespace_id: namespace.id) }
- let!(:linked_project) { projects.create(namespace_id: namespace.id) }
-
- let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
- let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
-
- before do
- # Create links between projects
- fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
-
- [project, another_project, linked_project].each do |p|
- fork_network_members.create(
- fork_network_id: fork_network.id,
- project_id: p.id,
- forked_from_project_id: fork_network.root_project_id
- )
- end
-
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
- fork_network_members.create(fork_network_id: another_fork_network.id, project_id: other_project.id, forked_from_project_id: another_fork_network.root_project_id)
- end
-
- context 'when there are forks to be backfilled' do
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 2)
-
- # Links LFS objects to some projects
- [source_project, another_source_project, linked_project].each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
- end
- end
-
- it 'schedules background migration to link LFS objects' do
- Sidekiq::Testing.fake! do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq(2)
- expect(described_class::MIGRATION)
- .to be_scheduled_delayed_migration(2.minutes, project.id, another_project.id)
- expect(described_class::MIGRATION)
- .to be_scheduled_delayed_migration(4.minutes, other_project.id, other_project.id)
- end
- end
- end
-
- context 'when there are no forks to be backfilled' do
- before do
- # Links LFS objects to all projects
- projects.all.each do |p|
- lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
- lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
- end
- end
-
- it 'does not schedule any job' do
- Sidekiq::Testing.fake! do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq(0)
- end
- end
- end
-end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 67d8284bebe..6cef81d6e44 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -65,6 +65,17 @@ describe BroadcastMessage do
end
end
+ it 'expires the value if a broadcast message has ended', :request_store do
+ message = create(:broadcast_message, broadcast_type: broadcast_type, ends_at: Time.now.utc + 1.day)
+
+ expect(subject.call).to match_array([message])
+ expect(described_class.cache).to receive(:expire).and_call_original
+
+ Timecop.travel(1.week) do
+ 2.times { expect(subject.call).to be_empty }
+ end
+ end
+
it 'does not create new records' do
create(:broadcast_message, broadcast_type: broadcast_type)
diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb
index 5eebf08892a..0d7fa98e16b 100644
--- a/spec/services/groups/import_export/export_service_spec.rb
+++ b/spec/services/groups/import_export/export_service_spec.rb
@@ -38,12 +38,31 @@ describe Groups::ImportExport::ExportService do
let!(:another_user) { create(:user) }
let(:service) { described_class.new(group: group, user: another_user, params: { shared: shared }) }
+ let(:expected_message) do
+ "User with ID: %s does not have required permissions for Group: %s with ID: %s" %
+ [another_user.id, group.name, group.id]
+ end
+
it 'fails' do
- expected_message =
- "User with ID: %s does not have permission to Group %s with ID: %s." %
- [another_user.id, group.name, group.id]
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error).with_message(expected_message)
end
+
+ it 'logs the error' do
+ expect(shared.logger).to receive(:error).with(
+ group_id: group.id,
+ group_name: group.name,
+ error: expected_message,
+ message: 'Group Import/Export: Export failed'
+ )
+
+ expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
+ end
+
+ it 'tracks the error' do
+ expect(shared).to receive(:error) { |param| expect(param.message).to eq expected_message }
+
+ expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
+ end
end
context 'when export fails' do
diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb
index bac266d08da..49c786ef67f 100644
--- a/spec/services/groups/import_export/import_service_spec.rb
+++ b/spec/services/groups/import_export/import_service_spec.rb
@@ -9,6 +9,8 @@ describe Groups::ImportExport::ImportService do
let(:service) { described_class.new(group: group, user: user) }
let(:import_file) { fixture_file_upload('spec/fixtures/group_export.tar.gz') }
+ let(:import_logger) { instance_double(Gitlab::Import::Logger) }
+
subject { service.execute }
before do
@@ -25,13 +27,82 @@ describe Groups::ImportExport::ImportService do
expect(group.import_export_upload.import_file.file).to be_nil
end
+
+ it 'logs the import success' do
+ allow(Gitlab::Import::Logger).to receive(:build).and_return(import_logger)
+
+ expect(import_logger).to receive(:info).with(
+ group_id: group.id,
+ group_name: group.name,
+ message: 'Group Import/Export: Import succeeded'
+ )
+
+ subject
+ end
end
context 'when user does not have correct permissions' do
let(:user) { create(:user) }
- it 'raises exception' do
- expect { subject }.to raise_error(StandardError)
+ it 'logs the error and raises an exception' do
+ allow(Gitlab::Import::Logger).to receive(:build).and_return(import_logger)
+
+ expect(import_logger).to receive(:error).with(
+ group_id: group.id,
+ group_name: group.name,
+ message: a_string_including('Errors occurred')
+ )
+
+ expect { subject }.to raise_error(Gitlab::ImportExport::Error)
+ end
+
+ it 'tracks the error' do
+ shared = Gitlab::ImportExport::Shared.new(group)
+ allow(Gitlab::ImportExport::Shared).to receive(:new).and_return(shared)
+
+ expect(shared).to receive(:error) do |param|
+ expect(param.message).to include 'does not have required permissions for'
+ end
+
+ expect { subject }.to raise_error(Gitlab::ImportExport::Error)
+ end
+ end
+
+ context 'when there are errors with the import file' do
+ let(:import_file) { fixture_file_upload('spec/fixtures/symlink_export.tar.gz') }
+
+ before do
+ allow(Gitlab::Import::Logger).to receive(:build).and_return(import_logger)
+ end
+
+ it 'logs the error and raises an exception' do
+ expect(import_logger).to receive(:error).with(
+ group_id: group.id,
+ group_name: group.name,
+ message: a_string_including('Errors occurred')
+ )
+
+ expect { subject }.to raise_error(Gitlab::ImportExport::Error)
+ end
+ end
+
+ context 'when there are errors with the sub-relations' do
+ let(:import_file) { fixture_file_upload('spec/fixtures/group_export_invalid_subrelations.tar.gz') }
+
+ it 'successfully imports the group' do
+ expect(subject).to be_truthy
+ end
+
+ it 'logs the import success' do
+ allow(Gitlab::Import::Logger).to receive(:build).and_return(import_logger)
+
+ expect(import_logger).to receive(:info).with(
+ group_id: group.id,
+ group_name: group.name,
+ message: 'Group Import/Export: Import succeeded'
+ )
+
+ subject
end
end
end
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 5c26e32bb22..596d46f3c43 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -91,6 +91,17 @@ describe MergeRequests::MergeToRefService do
it_behaves_like 'successfully evaluates pre-condition checks'
+ it 'returns an error when Gitlab::Git::CommandError is raised during merge' do
+ allow(project.repository).to receive(:merge_to_ref) do
+ raise Gitlab::Git::CommandError.new('Failed to create merge commit')
+ end
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Failed to create merge commit')
+ end
+
context 'commit history comparison with regular MergeService' do
before do
# The merge service needs an authorized user while merge-to-ref
diff --git a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
index 5f7279ee550..431b0db392a 100644
--- a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
@@ -29,7 +29,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
end
context 'user does not have push right to repository' do
- it_behaves_like 'misconfigured dashboard service response', :forbidden, %q(You can't commit to this project)
+ it_behaves_like 'misconfigured dashboard service response', :forbidden, %q(You are not allowed to push into this branch. Create another branch or open a merge request.)
end
context 'with rights to push to the repository' do
diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
index 2bb08579fb9..66622524e9c 100644
--- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
@@ -27,7 +27,7 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto
end
context 'user does not have push right to repository' do
- it_behaves_like 'misconfigured dashboard service response', :forbidden, "You can't commit to this project"
+ it_behaves_like 'misconfigured dashboard service response', :forbidden, "You are not allowed to push into this branch. Create another branch or open a merge request."
end
context 'with rights to push to the repository' do
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index ec1771e64c2..4d51deaa404 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -164,7 +164,7 @@ describe Projects::ImportExport::ExportService do
it 'fails' do
expected_message =
- "User with ID: %s does not have permission to Project %s with ID: %s." %
+ "User with ID: %s does not have required permissions for Project: %s with ID: %s" %
[another_user.id, project.name, project.id]
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error).with_message(expected_message)
end
diff --git a/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb b/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb
index 4df81478639..45b2c5eac3a 100644
--- a/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb
+++ b/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb
@@ -18,7 +18,7 @@ RSpec.shared_examples 'measurable' do
end
context 'when measurement is not provided' do
- let(:task_params) { [username, namespace_path, project_name, archive_path] }
+ let(:measurement_enabled) { nil }
it 'does not output measurement results' do
expect { subject }.not_to output(/Measuring enabled.../).to_stdout
diff --git a/spec/tasks/gitlab/import_export/export_rake_spec.rb b/spec/tasks/gitlab/import_export/export_rake_spec.rb
deleted file mode 100644
index b665b46fe1c..00000000000
--- a/spec/tasks/gitlab/import_export/export_rake_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require 'rake_helper'
-
-describe 'gitlab:import_export:export rake task' do
- let(:username) { 'root' }
- let(:namespace_path) { username }
- let!(:user) { create(:user, username: username) }
- let(:measurement_enabled) { false }
- let(:task_params) { [username, namespace_path, project_name, archive_path, measurement_enabled] }
-
- before do
- Rake.application.rake_require('tasks/gitlab/import_export/export')
- end
-
- subject { run_rake_task('gitlab:import_export:export', task_params) }
-
- context 'when project is found' do
- let(:project) { create(:project, creator: user, namespace: user.namespace) }
- let(:project_name) { project.name }
- let(:archive_path) { 'spec/fixtures/gitlab/import_export/test_project_export.tar.gz' }
-
- around do |example|
- example.run
- ensure
- File.delete(archive_path)
- end
-
- it 'performs project export successfully' do
- expect { subject }.to output(/Done!/).to_stdout
-
- expect(File).to exist(archive_path)
- end
-
- it_behaves_like 'measurable'
- end
-end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 6ca8fa2bc5c..3437839765d 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -173,7 +173,7 @@ describe 'layouts/nav/sidebar/_project' do
it 'shows the value stream analytics entry' do
render
- expect(rendered).to have_link('Value Stream Analytics', href: project_cycle_analytics_path(project))
+ expect(rendered).to have_link('Value Stream', href: project_cycle_analytics_path(project))
end
end
@@ -183,7 +183,7 @@ describe 'layouts/nav/sidebar/_project' do
it 'does not show the value stream analytics entry' do
render
- expect(rendered).not_to have_link('Value Stream Analytics', href: project_cycle_analytics_path(project))
+ expect(rendered).not_to have_link('Value Stream', href: project_cycle_analytics_path(project))
end
end
end