-
diff --git a/app/assets/javascripts/linked_resources/constants.js b/app/assets/javascripts/linked_resources/constants.js
deleted file mode 100644
index 1b11cfc5f88..00000000000
--- a/app/assets/javascripts/linked_resources/constants.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { s__ } from '~/locale';
-
-export const resourceLinksI18n = Object.freeze({
- headerText: s__('LinkedResources|Linked resources'),
- helpText: s__('LinkedResources|Read more about linked resources'),
- addButtonText: s__('LinkedResources|Add a resource link'),
-});
-
-export const resourceLinksFormI18n = Object.freeze({
- linkTextLabel: s__('LinkedResources|Text (Optional)'),
- linkValueLabel: s__('LinkedResources|Link'),
- submitButtonText: s__('LinkedResources|Add'),
- cancelButtonText: s__('LinkedResources|Cancel'),
-});
diff --git a/app/assets/javascripts/linked_resources/index.js b/app/assets/javascripts/linked_resources/index.js
index 4ac9ca31a84..244adca86c9 100644
--- a/app/assets/javascripts/linked_resources/index.js
+++ b/app/assets/javascripts/linked_resources/index.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
+import ResourceLinksBlock from 'ee_component/linked_resources/components/resource_links_block.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
-import ResourceLinksBlock from './components/resource_links_block.vue';
export default function initLinkedResources() {
const linkedResourcesRootElement = document.querySelector('.js-linked-resources-root');
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue
index 56da8e88b7a..bfa99c01c3f 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue
@@ -1,4 +1,5 @@
+
+
+
+
+
+
+
+
+ {{ s__('WorkItem|Remove') }}
+
+
+
+
diff --git a/app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql b/app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql
new file mode 100644
index 00000000000..dc5286174d8
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/change_work_item_parent_link.mutation.graphql
@@ -0,0 +1,13 @@
+mutation changeWorkItemParentLink($id: WorkItemID!, $parentId: WorkItemID) {
+ workItemUpdate(input: { id: $id, hierarchyWidget: { parentId: $parentId } }) {
+ workItem {
+ id
+ workItemType {
+ id
+ }
+ title
+ state
+ }
+ errors
+ }
+}
diff --git a/app/controllers/projects/pipelines/tests_controller.rb b/app/controllers/projects/pipelines/tests_controller.rb
index e42cb9b8422..8ac370b1bd4 100644
--- a/app/controllers/projects/pipelines/tests_controller.rb
+++ b/app/controllers/projects/pipelines/tests_controller.rb
@@ -51,7 +51,7 @@ module Projects
def test_suite
suite = builds.sum do |build|
- build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new)
end
Gitlab::Ci::Reports::TestFailureHistory.new(suite.failed.values, project).load!
diff --git a/app/graphql/resolvers/ci/test_suite_resolver.rb b/app/graphql/resolvers/ci/test_suite_resolver.rb
index 5d61d9e986b..f758e217b47 100644
--- a/app/graphql/resolvers/ci/test_suite_resolver.rb
+++ b/app/graphql/resolvers/ci/test_suite_resolver.rb
@@ -28,7 +28,7 @@ module Resolvers
def load_test_suite_data(builds)
suite = builds.sum do |build|
- build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new)
end
Gitlab::Ci::Reports::TestFailureHistory.new(suite.failed.values, pipeline.project).load!
diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb
index 87c8bf5cb28..3dd6b3f4a80 100644
--- a/app/helpers/users/callouts_helper.rb
+++ b/app/helpers/users/callouts_helper.rb
@@ -11,6 +11,7 @@ module Users
UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout'
SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout'
REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS = [/^root/, /^dashboard\S*/, /^admin\S*/].freeze
+ WEB_HOOK_DISABLED = 'web_hook_disabled'
def show_gke_cluster_integration_callout?(project)
active_nav_link?(controller: sidebar_operations_paths) &&
@@ -60,12 +61,31 @@ module Users
!user_dismissed?(SECURITY_NEWSLETTER_CALLOUT)
end
+ def web_hook_disabled_dismissed?(project)
+ return false unless project
+
+ last_failure = Gitlab::Redis::SharedState.with do |redis|
+ key = "web_hooks:last_failure:project-#{project.id}"
+ redis.get(key)
+ end
+
+ last_failure = DateTime.parse(last_failure) if last_failure
+
+ user_dismissed?(WEB_HOOK_DISABLED, last_failure, namespace: project.namespace)
+ end
+
private
- def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
+ def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, namespace: nil)
return false unless current_user
- current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
+ query = { feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than }
+
+ if namespace
+ current_user.dismissed_callout_for_namespace?(namespace: namespace, **query)
+ else
+ current_user.dismissed_callout?(**query)
+ end
end
end
end
diff --git a/app/helpers/web_hooks/web_hooks_helper.rb b/app/helpers/web_hooks/web_hooks_helper.rb
new file mode 100644
index 00000000000..95122750c2f
--- /dev/null
+++ b/app/helpers/web_hooks/web_hooks_helper.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module WebHooks
+ module WebHooksHelper
+ EXPIRY_TTL = 1.hour
+
+ def show_project_hook_failed_callout?(project:)
+ return false unless current_user
+ return false unless Feature.enabled?(:webhooks_failed_callout, project)
+ return false unless Feature.enabled?(:web_hooks_disable_failed, project)
+ return false unless Ability.allowed?(current_user, :read_web_hooks, project)
+
+ # Assumes include of Users::CalloutsHelper
+ return false if web_hook_disabled_dismissed?(project)
+
+ any_project_hook_failed?(project) # Most expensive query last
+ end
+
+ private
+
+ def any_project_hook_failed?(project)
+ Rails.cache.fetch("any_web_hook_failed:#{project.id}", expires_in: EXPIRY_TTL) do
+ ProjectHook.for_projects(project).disabled.exists?
+ end
+ end
+ end
+end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 791bae17271..78b55680b5e 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1090,7 +1090,7 @@ module Ci
end
def test_reports
- Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |test_reports|
latest_test_report_builds.find_each do |build|
build.collect_test_reports!(test_reports)
end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index b7ace34141e..bcbf43ee38b 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -27,6 +27,8 @@ class ProjectHook < WebHook
belongs_to :project
validates :project, presence: true
+ scope :for_projects, ->(project) { where(project: project) }
+
def pluralized_name
_('Webhooks')
end
@@ -41,6 +43,19 @@ class ProjectHook < WebHook
project
end
+ override :update_last_failure
+ def update_last_failure
+ return if executable?
+
+ key = "web_hooks:last_failure:project-#{project_id}"
+ time = Time.current.utc.iso8601
+
+ Gitlab::Redis::SharedState.with do |redis|
+ prev = redis.get(key)
+ redis.set(key, time) if !prev || prev < time
+ end
+ end
+
private
override :web_hooks_disable_failed?
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 71e7206718e..f428d07cd7f 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -48,6 +48,11 @@ class WebHook < ApplicationRecord
where('recent_failures <= ? AND (disabled_until IS NULL OR disabled_until < ?)', FAILURE_THRESHOLD, Time.current)
end
+ # Inverse of executable
+ scope :disabled, -> do
+ where('recent_failures > ? OR disabled_until >= ?', FAILURE_THRESHOLD, Time.current)
+ end
+
def executable?
!temporarily_disabled? && !permanently_disabled?
end
@@ -181,6 +186,10 @@ class WebHook < ApplicationRecord
raise InterpolationError, "Invalid URL template. Missing key #{e.key}"
end
+ def update_last_failure
+ # Overridden in child classes.
+ end
+
private
def web_hooks_disable_failed?
diff --git a/app/models/note.rb b/app/models/note.rb
index f2ddf0efe47..986a85acac6 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -111,6 +111,7 @@ class Note < ApplicationRecord
end
validate :does_not_exceed_notes_limit?, on: :create, unless: [:system?, :importing?]
+ validate :validate_created_after
# @deprecated attachments are handled by the Upload model.
#
@@ -748,6 +749,13 @@ class Note < ApplicationRecord
errors.add(:base, _('Maximum number of comments exceeded')) if noteable.notes.count >= Noteable::MAX_NOTES_LIMIT
end
+ def validate_created_after
+ return unless created_at
+ return if created_at >= '1970-01-01'
+
+ errors.add(:created_at, s_('Note|The created date provided is too far in the past.'))
+ end
+
def noteable_label_url_method
for_merge_request? ? :project_merge_requests_url : :project_issues_url
end
diff --git a/app/models/user.rb b/app/models/user.rb
index dc2f36a9ddb..12f434db631 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -222,6 +222,7 @@ class User < ApplicationRecord
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'Users::Callout'
has_many :group_callouts, class_name: 'Users::GroupCallout'
+ has_many :namespace_callouts, class_name: 'Users::NamespaceCallout'
has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
@@ -2085,6 +2086,13 @@ class User < ApplicationRecord
callout_dismissed?(callout, ignore_dismissal_earlier_than)
end
+ def dismissed_callout_for_namespace?(feature_name:, namespace:, ignore_dismissal_earlier_than: nil)
+ source_feature_name = "#{feature_name}_#{namespace.id}"
+ callout = namespace_callouts_by_feature_name[source_feature_name]
+
+ callout_dismissed?(callout, ignore_dismissal_earlier_than)
+ end
+
# Load the current highest access by looking directly at the user's memberships
def current_highest_access_level
members.non_request.maximum(:access_level)
@@ -2111,6 +2119,11 @@ class User < ApplicationRecord
.find_or_initialize_by(feature_name: ::Users::GroupCallout.feature_names[feature_name], group_id: group_id)
end
+ def find_or_initialize_namespace_callout(feature_name, namespace_id)
+ namespace_callouts
+ .find_or_initialize_by(feature_name: ::Users::NamespaceCallout.feature_names[feature_name], namespace_id: namespace_id)
+ end
+
def can_trigger_notifications?
confirmed? && !blocked? && !ghost?
end
@@ -2228,6 +2241,10 @@ class User < ApplicationRecord
@group_callouts_by_feature_name ||= group_callouts.index_by(&:source_feature_name)
end
+ def namespace_callouts_by_feature_name
+ @namespace_callouts_by_feature_name ||= namespace_callouts.index_by(&:source_feature_name)
+ end
+
def authorized_groups_without_shared_membership
Group.from_union([
groups.select(*Namespace.cached_column_list),
diff --git a/app/models/users/namespace_callout.rb b/app/models/users/namespace_callout.rb
new file mode 100644
index 00000000000..a20a196a4ef
--- /dev/null
+++ b/app/models/users/namespace_callout.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Users
+ class NamespaceCallout < ApplicationRecord
+ include Users::Calloutable
+
+ self.table_name = 'user_namespace_callouts'
+
+ belongs_to :namespace
+
+ enum feature_name: {
+ invite_members_banner: 1,
+ approaching_seat_count_threshold: 2, # EE-only
+ storage_enforcement_banner_first_enforcement_threshold: 3,
+ storage_enforcement_banner_second_enforcement_threshold: 4,
+ storage_enforcement_banner_third_enforcement_threshold: 5,
+ storage_enforcement_banner_fourth_enforcement_threshold: 6,
+ preview_user_over_limit_free_plan_alert: 7, # EE-only
+ user_reached_limit_free_plan_alert: 8, # EE-only
+ web_hook_disabled: 9
+ }
+
+ validates :namespace, presence: true
+ validates :feature_name,
+ presence: true,
+ uniqueness: { scope: [:user_id, :namespace_id] },
+ inclusion: { in: NamespaceCallout.feature_names.keys }
+
+ def source_feature_name
+ "#{feature_name}_#{namespace_id}"
+ end
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index c54dbefc1ae..850f25a6089 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -492,6 +492,7 @@ class ProjectPolicy < BasePolicy
enable :update_runners_registration_token
enable :admin_project_google_cloud
enable :admin_secure_files
+ enable :read_web_hooks
end
rule { public_project & metrics_dashboard_allowed }.policy do
diff --git a/app/services/ci/build_report_result_service.rb b/app/services/ci/build_report_result_service.rb
index 8bdb51320f9..f9146b3677a 100644
--- a/app/services/ci/build_report_result_service.rb
+++ b/app/services/ci/build_report_result_service.rb
@@ -22,7 +22,7 @@ module Ci
private
def generate_test_suite_report(build)
- build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new)
end
def tests_params(test_suite)
diff --git a/app/services/ci/test_failure_history_service.rb b/app/services/ci/test_failure_history_service.rb
index 7323ad417ea..2214a6a2729 100644
--- a/app/services/ci/test_failure_history_service.rb
+++ b/app/services/ci/test_failure_history_service.rb
@@ -81,7 +81,7 @@ module Ci
def generate_test_suite!(build)
# Returns an instance of Gitlab::Ci::Reports::TestSuite
- build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new)
end
def ci_unit_test_attrs(batch)
diff --git a/app/services/web_hooks/log_execution_service.rb b/app/services/web_hooks/log_execution_service.rb
index 0ee7c41469f..17dcf615830 100644
--- a/app/services/web_hooks/log_execution_service.rb
+++ b/app/services/web_hooks/log_execution_service.rb
@@ -44,6 +44,7 @@ module WebHooks
end
log_state_change
+ hook.update_last_failure
end
rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
raise if raise_lock_error?
diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml
index e114e4609f8..3090c823677 100644
--- a/app/views/errors/omniauth_error.html.haml
+++ b/app/views/errors/omniauth_error.html.haml
@@ -2,14 +2,18 @@
.container
= render partial: "shared/errors/graphic_422", formats: :svg
- %h3 Sign-in using #{@provider} auth failed
+ %h3
+ = _('Sign-in using %{provider} auth failed') % { provider: @provider }
- %p.light.subtitle Sign-in failed because #{@error}.
+ %p.light.subtitle
+ = _('Sign-in failed because %{error}.') % { error: @error }
- %p Try logging in using your username or email. If you have forgotten your password, try recovering it
+ %p
+ = _('Try logging in using your username or email. If you have forgotten your password, try recovering it')
- = link_to "Sign in", new_session_path(:user), class: 'gl-button btn primary'
- = link_to "Recover password", new_password_path(:user), class: 'gl-button btn secondary'
+ = link_to _('Sign in'), new_session_path(:user), class: 'gl-button btn primary'
+ = link_to _('Recover password'), new_password_path(:user), class: 'gl-button btn secondary'
%hr
- %p.light If none of the options work, try contacting a GitLab administrator.
+ %p.light
+ = _('If none of the options work, try contacting a GitLab administrator.')
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 54435f675a7..07e299d71ea 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -6,9 +6,9 @@
= preserve(markdown(commit.description, pipeline: :single_line))
.info-well
- .well-segment.pipeline-info
- .icon-container.gl-vertical-align-text-bottom
- = sprite_icon('clock')
+ .well-segment.pipeline-info{ class: "gl-align-items-baseline!" }
+ .icon-container
+ = sprite_icon('clock', css_class: 'gl-top-0!')
= pluralize @pipeline.total_size, "job"
= @pipeline.ref_text
- if @pipeline.duration
@@ -20,7 +20,7 @@
- if has_pipeline_badges?(@pipeline)
.well-segment.qa-pipeline-badges
.icon-container
- = sprite_icon('flag')
+ = sprite_icon('flag', css_class: 'gl-top-0!')
- if @pipeline.child?
- text = sprintf(s_('Pipelines|Child pipeline (%{link_start}parent%{link_end})'), { link_start: "", link_end: ""}).html_safe
= gl_badge_tag text, { variant: :info, size: :sm }, { class: 'js-pipeline-child has-tooltip', title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
@@ -44,13 +44,13 @@
.well-segment.branch-info
.icon-container.commit-icon
- = custom_icon("icon_commit")
+ = sprite_icon('commit', css_class: 'gl-top-0!')
= link_to commit.short_id, project_commit_path(@project, @pipeline.sha), class: "commit-sha"
= clipboard_button(text: @pipeline.sha, title: _("Copy commit SHA"))
.well-segment.related-merge-request-info
.icon-container
- = sprite_icon("git-merge")
+ = sprite_icon("git-merge", css_class: 'gl-top-0!')
%span.related-merge-requests
%span.js-truncated-mr-list
= @pipeline.all_related_merge_request_text(limit: 1)
diff --git a/config/feature_flags/development/webhooks_failed_callout.yml b/config/feature_flags/development/webhooks_failed_callout.yml
new file mode 100644
index 00000000000..11de5a793f6
--- /dev/null
+++ b/config/feature_flags/development/webhooks_failed_callout.yml
@@ -0,0 +1,8 @@
+---
+name: webhooks_failed_callout
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91092
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365535
+milestone: '15.2'
+type: development
+group: group::integrations
+default_enabled: false
diff --git a/db/docs/user_namespace_callouts.yml b/db/docs/user_namespace_callouts.yml
new file mode 100644
index 00000000000..5038ecce3bc
--- /dev/null
+++ b/db/docs/user_namespace_callouts.yml
@@ -0,0 +1,10 @@
+
+---
+table_name: user_namespace_callouts
+classes:
+- Users::NamespaceCallout
+feature_categories:
+- navigation
+description: Contains records of which users have dismissed a callout, grouped by namespace.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91092
+milestone: '15.2'
diff --git a/db/migrate/20220627122229_create_user_namespace_callouts.rb b/db/migrate/20220627122229_create_user_namespace_callouts.rb
new file mode 100644
index 00000000000..fc85c02d2db
--- /dev/null
+++ b/db/migrate/20220627122229_create_user_namespace_callouts.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateUserNamespaceCallouts < Gitlab::Database::Migration[2.0]
+ def up
+ create_table :user_namespace_callouts do |t|
+ t.bigint :user_id, null: false
+ t.bigint :namespace_id, null: false, index: true
+ t.datetime_with_timezone :dismissed_at
+ t.integer :feature_name, limit: 2, null: false
+ end
+ end
+
+ def down
+ drop_table :user_namespace_callouts
+ end
+end
diff --git a/db/migrate/20220627122230_add_foreign_keys_to_user_namespace_callouts.rb b/db/migrate/20220627122230_add_foreign_keys_to_user_namespace_callouts.rb
new file mode 100644
index 00000000000..f78eb978a9b
--- /dev/null
+++ b/db/migrate/20220627122230_add_foreign_keys_to_user_namespace_callouts.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class AddForeignKeysToUserNamespaceCallouts < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :user_namespace_callouts, :users,
+ column: :user_id,
+ on_delete: :cascade
+
+ add_concurrent_foreign_key :user_namespace_callouts, :namespaces,
+ column: :namespace_id,
+ on_delete: :cascade
+
+ add_concurrent_index :user_namespace_callouts, [:user_id, :feature_name, :namespace_id],
+ unique: true,
+ name: 'index_ns_user_callouts_feature'
+ end
+
+ def down
+ remove_concurrent_index_by_name :user_namespace_callouts, 'index_ns_user_callouts_feature'
+
+ with_lock_retries do
+ remove_foreign_key :user_namespace_callouts, column: :user_id
+ remove_foreign_key :user_namespace_callouts, column: :namespace_id
+ end
+ end
+end
diff --git a/db/post_migrate/20220628111752_drop_token_index_from_ci_builds.rb b/db/post_migrate/20220628111752_drop_token_index_from_ci_builds.rb
new file mode 100644
index 00000000000..d551eeebeb6
--- /dev/null
+++ b/db/post_migrate/20220628111752_drop_token_index_from_ci_builds.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class DropTokenIndexFromCiBuilds < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_ci_builds_on_token_partial'
+
+ def up
+ remove_concurrent_index_by_name :ci_builds, INDEX_NAME
+ end
+
+ # rubocop:disable Migration/PreventIndexCreation
+ def down
+ add_concurrent_index :ci_builds, :token, unique: true, where: 'token IS NOT NULL', name: INDEX_NAME
+ end
+ # rubocop:enable Migration/PreventIndexCreation
+end
diff --git a/db/post_migrate/20220715163254_update_notes_in_past.rb b/db/post_migrate/20220715163254_update_notes_in_past.rb
new file mode 100644
index 00000000000..1c46a3bc9dc
--- /dev/null
+++ b/db/post_migrate/20220715163254_update_notes_in_past.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class UpdateNotesInPast < Gitlab::Database::Migration[2.0]
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ loop do
+ update_count = define_batchable_model('notes')
+ .where('created_at < ?', '1970-01-01').limit(100)
+ .update_all(created_at: '1970-01-01 00:00:00')
+
+ break if update_count == 0
+ end
+ end
+
+ def down
+ # no op
+ end
+end
diff --git a/db/schema_migrations/20220627122229 b/db/schema_migrations/20220627122229
new file mode 100644
index 00000000000..040376e1aa0
--- /dev/null
+++ b/db/schema_migrations/20220627122229
@@ -0,0 +1 @@
+29ab69647b53c331aefdd62e8fbcc1567df4424a8e7ae6f8eb7b1e9afa7a6911
\ No newline at end of file
diff --git a/db/schema_migrations/20220627122230 b/db/schema_migrations/20220627122230
new file mode 100644
index 00000000000..82ba0d503ee
--- /dev/null
+++ b/db/schema_migrations/20220627122230
@@ -0,0 +1 @@
+6d65af0d20cd80cf3367f48c5447ff33046e982ac1cfd55aaf52a7cc2330e428
\ No newline at end of file
diff --git a/db/schema_migrations/20220628111752 b/db/schema_migrations/20220628111752
new file mode 100644
index 00000000000..747546f1ba4
--- /dev/null
+++ b/db/schema_migrations/20220628111752
@@ -0,0 +1 @@
+5a4a6355d1954735a05831e17c97e2879320f2cb313be56fb72e1cd2c20d9090
\ No newline at end of file
diff --git a/db/schema_migrations/20220715163254 b/db/schema_migrations/20220715163254
new file mode 100644
index 00000000000..71461af7b68
--- /dev/null
+++ b/db/schema_migrations/20220715163254
@@ -0,0 +1 @@
+ea8182741ce0b30f2de23041d1f6bafaf6e04a7a7d0f50abcd04462683637596
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index bca2b45c8bb..7080cf1936f 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -21669,6 +21669,23 @@ CREATE TABLE user_interacted_projects (
project_id integer NOT NULL
);
+CREATE TABLE user_namespace_callouts (
+ id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ namespace_id bigint NOT NULL,
+ dismissed_at timestamp with time zone,
+ feature_name smallint NOT NULL
+);
+
+CREATE SEQUENCE user_namespace_callouts_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE user_namespace_callouts_id_seq OWNED BY user_namespace_callouts.id;
+
CREATE TABLE user_permission_export_uploads (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -23602,6 +23619,8 @@ ALTER TABLE ONLY user_details ALTER COLUMN user_id SET DEFAULT nextval('user_det
ALTER TABLE ONLY user_group_callouts ALTER COLUMN id SET DEFAULT nextval('user_group_callouts_id_seq'::regclass);
+ALTER TABLE ONLY user_namespace_callouts ALTER COLUMN id SET DEFAULT nextval('user_namespace_callouts_id_seq'::regclass);
+
ALTER TABLE ONLY user_permission_export_uploads ALTER COLUMN id SET DEFAULT nextval('user_permission_export_uploads_id_seq'::regclass);
ALTER TABLE ONLY user_preferences ALTER COLUMN id SET DEFAULT nextval('user_preferences_id_seq'::regclass);
@@ -25879,6 +25898,9 @@ ALTER TABLE ONLY user_highest_roles
ALTER TABLE ONLY user_interacted_projects
ADD CONSTRAINT user_interacted_projects_pkey PRIMARY KEY (project_id, user_id);
+ALTER TABLE ONLY user_namespace_callouts
+ ADD CONSTRAINT user_namespace_callouts_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY user_permission_export_uploads
ADD CONSTRAINT user_permission_export_uploads_pkey PRIMARY KEY (id);
@@ -27495,8 +27517,6 @@ CREATE INDEX index_ci_builds_on_status_and_type_and_runner_id ON ci_builds USING
CREATE UNIQUE INDEX index_ci_builds_on_token_encrypted ON ci_builds USING btree (token_encrypted) WHERE (token_encrypted IS NOT NULL);
-CREATE UNIQUE INDEX index_ci_builds_on_token_partial ON ci_builds USING btree (token) WHERE (token IS NOT NULL);
-
CREATE INDEX index_ci_builds_on_updated_at ON ci_builds USING btree (updated_at);
CREATE INDEX index_ci_builds_on_upstream_pipeline_id ON ci_builds USING btree (upstream_pipeline_id) WHERE (upstream_pipeline_id IS NOT NULL);
@@ -28911,6 +28931,8 @@ CREATE INDEX index_notification_settings_on_source_and_level_and_user ON notific
CREATE UNIQUE INDEX index_notifications_on_user_id_and_source_id_and_source_type ON notification_settings USING btree (user_id, source_id, source_type);
+CREATE UNIQUE INDEX index_ns_user_callouts_feature ON user_namespace_callouts USING btree (user_id, feature_name, namespace_id);
+
CREATE INDEX index_oauth_access_grants_on_resource_owner_id ON oauth_access_grants USING btree (resource_owner_id, application_id, created_at);
CREATE UNIQUE INDEX index_oauth_access_grants_on_token ON oauth_access_grants USING btree (token);
@@ -29913,6 +29935,8 @@ CREATE INDEX index_user_highest_roles_on_user_id_and_highest_access_level ON use
CREATE INDEX index_user_interacted_projects_on_user_id ON user_interacted_projects USING btree (user_id);
+CREATE INDEX index_user_namespace_callouts_on_namespace_id ON user_namespace_callouts USING btree (namespace_id);
+
CREATE INDEX index_user_permission_export_uploads_on_user_id_and_status ON user_permission_export_uploads USING btree (user_id, status);
CREATE INDEX index_user_preferences_on_gitpod_enabled ON user_preferences USING btree (gitpod_enabled);
@@ -31780,6 +31804,9 @@ ALTER TABLE ONLY ci_pipelines
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_27548c6db3 FOREIGN KEY (hashed_storage_migrated_event_id) REFERENCES geo_hashed_storage_migrated_events(id) ON DELETE CASCADE;
+ALTER TABLE ONLY user_namespace_callouts
+ ADD CONSTRAINT fk_27a69fd1bd FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY merge_requests_compliance_violations
ADD CONSTRAINT fk_290ec1ab02 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
@@ -31885,6 +31912,9 @@ ALTER TABLE ONLY releases
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_4a99ebfd60 FOREIGN KEY (repositories_changed_event_id) REFERENCES geo_repositories_changed_events(id) ON DELETE CASCADE;
+ALTER TABLE ONLY user_namespace_callouts
+ ADD CONSTRAINT fk_4b1257f385 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY sbom_occurrences
ADD CONSTRAINT fk_4b88e5b255 FOREIGN KEY (component_version_id) REFERENCES sbom_component_versions(id) ON DELETE CASCADE;
diff --git a/doc/administration/operations/rails_console.md b/doc/administration/operations/rails_console.md
index d6500bc25ae..660a99faaf8 100644
--- a/doc/administration/operations/rails_console.md
+++ b/doc/administration/operations/rails_console.md
@@ -43,6 +43,35 @@ The console is in the toolbox pod. Refer to our [Kubernetes cheat sheet](https:/
To exit the console, type: `quit`.
+## Enable Active Record logging
+
+You can enable output of Active Record debug logging in the Rails console
+session by running:
+
+```ruby
+ActiveRecord::Base.logger = Logger.new($stdout)
+```
+
+This shows information about database queries triggered by any Ruby code
+you may run in the console. To turn off logging again, run:
+
+```ruby
+ActiveRecord::Base.logger = nil
+```
+
+## Disable database statement timeout
+
+You can disable the PostgreSQL statement timeout for the current Rails console
+session by running:
+
+```ruby
+ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
+```
+
+This change only affects the current Rails console session and is
+not persisted in the GitLab production environment or in the next Rails
+console session.
+
## Output Rails console session history
Enter the following command on the rails console to display
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index 4b28b578ebc..2ea79a15e6e 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -9,83 +9,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Sometimes things don't work the way they should. Here are some tips on debugging issues out
in production.
-## Starting a Rails console session
-
-Troubleshooting and debugging your GitLab instance often requires a Rails console.
-
-Your type of GitLab installation determines how
-[to start a rails console](../operations/rails_console.md).
-See also:
-
-- [GitLab Rails Console Cheat Sheet](gitlab_rails_cheat_sheet.md).
-
-### Enabling Active Record logging
-
-You can enable output of Active Record debug logging in the Rails console
-session by running:
-
-```ruby
-ActiveRecord::Base.logger = Logger.new($stdout)
-```
-
-This shows information about database queries triggered by any Ruby code
-you may run in the console. To turn off logging again, run:
-
-```ruby
-ActiveRecord::Base.logger = nil
-```
-
-### Disabling database statement timeout
-
-You can disable the PostgreSQL statement timeout for the current Rails console
-session by running:
-
-```ruby
-ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
-```
-
-This change only affects the current Rails console session and is
-not persisted in the GitLab production environment or in the next Rails
-console session.
-
-### Output Rails console session history
-
-If you'd like to output your Rails console command history in a format that's
-easy to copy and save for future reference, you can run:
-
-```ruby
-puts Readline::HISTORY.to_a
-```
-
-## Using the Rails runner
-
-If you need to run some Ruby code in the context of your GitLab production
-environment, you can do so using the [Rails runner](https://guides.rubyonrails.org/command_line.html#rails-runner). When executing a script file, the script must be accessible by the `git` user.
-
-**For Omnibus installations**
-
-```shell
-sudo gitlab-rails runner "RAILS_COMMAND"
-
-# Example with a two-line Ruby script
-sudo gitlab-rails runner "user = User.first; puts user.username"
-
-# Example with a ruby script file (make sure to use the full path)
-sudo gitlab-rails runner /path/to/script.rb
-```
-
-**For installations from source**
-
-```shell
-sudo -u git -H bundle exec rails runner -e production "RAILS_COMMAND"
-
-# Example with a two-line Ruby script
-sudo -u git -H bundle exec rails runner -e production "user = User.first; puts user.username"
-
-# Example with a ruby script file (make sure to use the full path)
-sudo -u git -H bundle exec rails runner -e production /path/to/script.rb
-```
-
## More information
- [Debugging Stuck Ruby Processes](https://newrelic.com/blog/best-practices/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9)
diff --git a/doc/api/index.md b/doc/api/index.md
index cf14a9f405b..26447a2223d 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -453,12 +453,14 @@ Keyset-pagination allows for more efficient retrieval of pages and - in contrast
to offset-based pagination - runtime is independent of the size of the
collection.
-This method is controlled by the following parameters:
+This method is controlled by the following parameters. `order_by` and `sort` are both mandatory.
-| Parameter | Description |
-|--------------| ------------|
-| `pagination` | `keyset` (to enable keyset pagination). |
-| `per_page` | Number of items to list per page (default: `20`, max: `100`). |
+| Parameter | Required | Description |
+|--------------| ------------ | --------- |
+| `pagination` | yes | `keyset` (to enable keyset pagination). |
+| `per_page` | no | Number of items to list per page (default: `20`, max: `100`). |
+| `order_by` | yes | Column by which to order by. |
+| `sort` | yes | Sort order (`asc` or `desc`) |
In the following example, we list 50 [projects](projects.md) per page, ordered
by `id` ascending.
diff --git a/doc/api/notes.md b/doc/api/notes.md
index fbcf5e28f79..f7caae59b4d 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -146,7 +146,7 @@ Parameters:
| `issue_iid` | integer | yes | The IID of an issue. |
| `body` | string | yes | The content of a note. Limited to 1,000,000 characters. |
| `confidential` | boolean | no | The confidential flag of a note. Default is false. |
-| `created_at` | string | no | Date time string, ISO 8601 formatted. Example: `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) |
+| `created_at` | string | no | Date time string, ISO 8601 formatted. It must be after 1970-01-01. Example: `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) |
```shell
curl --request POST --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note"
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index 7777afeed39..0a0c5e4d2a6 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -316,11 +316,12 @@ and [Container Scanning](../../user/application_security/container_scanning/inde
You can find the schemas for these scanners here:
-- [SAST](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json)
-- [DAST](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dast-report-format.json)
-- [Dependency Scanning](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dependency-scanning-report-format.json)
+- [Cluster Image Scanning](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/cluster-image-scanning-report-format.json)
- [Container Scanning](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/container-scanning-report-format.json)
- [Coverage Fuzzing](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/coverage-fuzzing-report-format.json)
+- [DAST](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dast-report-format.json)
+- [Dependency Scanning](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dependency-scanning-report-format.json)
+- [SAST](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json)
- [Secret Detection](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/secret-detection-report-format.json)
### Retention period for vulnerabilities
diff --git a/doc/user/application_security/terminology/index.md b/doc/user/application_security/terminology/index.md
index 392bfa1dde2..d50cce3b4e8 100644
--- a/doc/user/application_security/terminology/index.md
+++ b/doc/user/application_security/terminology/index.md
@@ -220,11 +220,12 @@ once it's imported into the database.
The type of scan. This must be one of the following:
-- `container_scanning`
-- `dependency_scanning`
-- `dast`
-- `sast`
- `cluster_image_scanning`
+- `container_scanning`
+- `dast`
+- `dependency_scanning`
+- `sast`
+- `secret_detection`
### Scanner
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index af84b746280..427c412219a 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -145,7 +145,7 @@ To create a personal access token programmatically:
```
This code can be shortened into a single-line shell command by using the
-[Rails runner](../../administration/troubleshooting/debug.md#using-the-rails-runner):
+[Rails runner](../../administration/operations/rails_console.md#using-the-rails-runner):
```shell
sudo gitlab-rails runner "token = User.find_by_username('automation-bot').personal_access_tokens.create(scopes: [:read_user, :read_repository], name: 'Automation token'); token.set_token('token-string-here123'); token.save!"
@@ -177,7 +177,7 @@ To revoke a token programmatically:
```
This code can be shortened into a single-line shell command using the
-[Rails runner](../../administration/troubleshooting/debug.md#using-the-rails-runner):
+[Rails runner](../../administration/operations/rails_console.md#using-the-rails-runner):
```shell
sudo gitlab-rails runner "PersonalAccessToken.find_by_token('token-string-here123').revoke!"
diff --git a/doc/user/project/merge_requests/img/merge_method_ff_v15_0.png b/doc/user/project/merge_requests/img/merge_method_ff_v15_0.png
deleted file mode 100644
index 323fd03ffa2..00000000000
Binary files a/doc/user/project/merge_requests/img/merge_method_ff_v15_0.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/merge_method_merge_commit_v15_0.png b/doc/user/project/merge_requests/img/merge_method_merge_commit_v15_0.png
deleted file mode 100644
index b880c2c0e04..00000000000
Binary files a/doc/user/project/merge_requests/img/merge_method_merge_commit_v15_0.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/merge_method_merge_commit_with_semi_linear_history_v15_0.png b/doc/user/project/merge_requests/img/merge_method_merge_commit_with_semi_linear_history_v15_0.png
deleted file mode 100644
index 9eab71e9d3c..00000000000
Binary files a/doc/user/project/merge_requests/img/merge_method_merge_commit_with_semi_linear_history_v15_0.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/methods/index.md b/doc/user/project/merge_requests/methods/index.md
index d3221162cfd..63b464e5ff4 100644
--- a/doc/user/project/merge_requests/methods/index.md
+++ b/doc/user/project/merge_requests/methods/index.md
@@ -23,7 +23,26 @@ merge requests are merged into an existing branch.
This setting is the default. It always creates a separate merge commit,
even when using [squash](../squash_and_merge.md). An example commit graph generated using this merge method:
-![Commit graph for merge commits](../img/merge_method_merge_commit_v15_0.png)
+```mermaid
+gitGraph
+ commit id: "Init"
+ branch mr-branch-1
+ commit
+ checkout main
+ commit
+ branch mr-branch-2
+ commit
+ checkout mr-branch-1
+ commit
+ checkout main
+ branch squash-mr
+ commit id: "Squashed commits"
+ checkout main
+ merge squash-mr
+ merge mr-branch-1
+ commit
+ merge mr-branch-2
+```
- For regular merges, it is equivalent to the command `git merge --no-ff `.
- For squash merges, it squashes all commits in the source branch before merging it normally. It performs actions similar to:
@@ -42,7 +61,25 @@ A merge commit is created for every merge, but the branch is only merged if
a fast-forward merge is possible. This ensures that if the merge request build
succeeded, the target branch build also succeeds after the merge. An example commit graph generated using this merge method:
-![Commit graph for merge commit with semi-linear history](../img/merge_method_merge_commit_with_semi_linear_history_v15_0.png)
+```mermaid
+gitGraph
+ commit id: "Init"
+ branch mr-branch-1
+ commit
+ commit
+ checkout main
+ merge mr-branch-1
+ branch mr-branch-2
+ commit
+ commit
+ checkout main
+ merge mr-branch-2
+ commit
+ branch squash-mr
+ commit id: "Squashed commits"
+ checkout main
+ merge squash-mr
+```
When you visit the merge request page with `Merge commit with semi-linear history`
method selected, you can accept it **only if a fast-forward merge is possible**.
@@ -63,7 +100,14 @@ fast-forward merge requests, you can retain a linear Git history and a way
to accept merge requests without creating merge commits. An example commit graph
generated using this merge method:
-![Commit graph for fast-forward merge](../img/merge_method_ff_v15_0.png)
+```mermaid
+gitGraph
+ commit id: "Init"
+ commit id: "Merge mr-branch-1"
+ commit id: "Merge mr-branch-2"
+ commit id: "Commit on main"
+ commit id: "Merge squash-mr"
+```
This method is equivalent to `git merge --ff ` for regular merges, and to
`git merge -squash ` for squash merges.
diff --git a/lib/gitlab/ci/reports/test_reports.rb b/lib/gitlab/ci/reports/test_report.rb
similarity index 98%
rename from lib/gitlab/ci/reports/test_reports.rb
rename to lib/gitlab/ci/reports/test_report.rb
index a5a630642e5..4fc10dd736e 100644
--- a/lib/gitlab/ci/reports/test_reports.rb
+++ b/lib/gitlab/ci/reports/test_report.rb
@@ -3,7 +3,7 @@
module Gitlab
module Ci
module Reports
- class TestReports
+ class TestReport
attr_reader :test_suites
def initialize
diff --git a/lib/gitlab/ci/reports/test_reports_comparer.rb b/lib/gitlab/ci/reports/test_reports_comparer.rb
index c6f17f0764f..497831ae5a7 100644
--- a/lib/gitlab/ci/reports/test_reports_comparer.rb
+++ b/lib/gitlab/ci/reports/test_reports_comparer.rb
@@ -9,7 +9,7 @@ module Gitlab
attr_reader :base_reports, :head_reports
def initialize(base_reports, head_reports)
- @base_reports = base_reports || TestReports.new
+ @base_reports = base_reports || TestReport.new
@head_reports = head_reports
end
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index e2ea66fdbc9..4a467d18f0a 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -578,3 +578,4 @@ zentao_tracker_data: :gitlab_main
dingtalk_tracker_data: :gitlab_main
zoom_meetings: :gitlab_main
batched_background_migration_job_transition_logs: :gitlab_shared
+user_namespace_callouts: :gitlab_main
diff --git a/lib/gitlab/database/migrations/test_batched_background_runner.rb b/lib/gitlab/database/migrations/test_batched_background_runner.rb
index 0c6a8d3d856..f38d847b0e8 100644
--- a/lib/gitlab/database/migrations/test_batched_background_runner.rb
+++ b/lib/gitlab/database/migrations/test_batched_background_runner.rb
@@ -14,7 +14,7 @@ module Gitlab
def jobs_by_migration_name
Gitlab::Database::BackgroundMigration::BatchedMigration
.executable
- .created_after(2.days.ago) # Simple way to exclude migrations already running before migration testing
+ .created_after(3.hours.ago) # Simple way to exclude migrations already running before migration testing
.to_h do |migration|
batching_strategy = migration.batch_class.new(connection: connection)
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 36a840372c5..76855f2950d 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -6,9 +6,6 @@ module Gitlab
URL_REGEX = %r{https?://[^'" ]+}.freeze
GIT_INVALID_URL_REGEX = /^git\+#{URL_REGEX}/.freeze
REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze
- VALID_LINK_ATTRIBUTES = %w[href rel target].freeze
-
- include ActionView::Helpers::SanitizeHelper
class_attribute :file_type
@@ -65,10 +62,9 @@ module Gitlab
end
def link_tag(name, url)
- sanitize(
- %{#{ERB::Util.html_escape_once(name)}},
- attributes: VALID_LINK_ATTRIBUTES
- )
+ href_attribute = %{href="#{ERB::Util.html_escape_once(url)}" } if Gitlab::UrlSanitizer.valid_web?(url)
+
+ %{#{ERB::Util.html_escape_once(name)}}.html_safe
end
# Links package names based on regex.
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index f72217dedde..bb5bbeeb27e 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -128,6 +128,9 @@ module Gitlab
@loaded_size = @data.bytesize if @data
@loaded_all_data = @loaded_size == size
+ # Recalculate binary status if we loaded all data
+ @binary = nil if @loaded_all_data
+
record_metric_blob_size
record_metric_truncated(truncated?)
end
diff --git a/lib/gitlab/pagination/keyset/cursor_based_request_context.rb b/lib/gitlab/pagination/keyset/cursor_based_request_context.rb
index e06d7e48ca3..41b90846345 100644
--- a/lib/gitlab/pagination/keyset/cursor_based_request_context.rb
+++ b/lib/gitlab/pagination/keyset/cursor_based_request_context.rb
@@ -5,6 +5,8 @@ module Gitlab
module Keyset
class CursorBasedRequestContext
DEFAULT_SORT_DIRECTION = :desc
+ DEFAULT_SORT_COLUMN = :id
+
attr_reader :request_context
delegate :params, to: :request_context
@@ -28,7 +30,7 @@ module Gitlab
end
def order_by
- { params[:order_by].to_sym => params[:sort]&.to_sym || DEFAULT_SORT_DIRECTION }
+ { (params[:order_by]&.to_sym || DEFAULT_SORT_COLUMN) => (params[:sort]&.to_sym || DEFAULT_SORT_DIRECTION) }
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e2aaf645b0a..ece81a08f5a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19590,6 +19590,9 @@ msgstr ""
msgid "If no options are selected, only administrators can register runners."
msgstr ""
+msgid "If none of the options work, try contacting a GitLab administrator."
+msgstr ""
+
msgid "If the number of active users exceeds the user limit, you will be charged for the number of %{users_over_license_link} at your next license reconciliation."
msgstr ""
@@ -26491,6 +26494,9 @@ msgstr ""
msgid "Notes|You're only seeing %{boldStart}other activity%{boldEnd} in the feed. To add a comment, switch to one of the following options."
msgstr ""
+msgid "Note|The created date provided is too far in the past."
+msgstr ""
+
msgid "Nothing to preview."
msgstr ""
@@ -31990,6 +31996,9 @@ msgstr ""
msgid "Reconfigure"
msgstr ""
+msgid "Recover password"
+msgstr ""
+
msgid "Recovery Codes"
msgstr ""
@@ -36255,6 +36264,9 @@ msgstr ""
msgid "Sign-in count:"
msgstr ""
+msgid "Sign-in failed because %{error}."
+msgstr ""
+
msgid "Sign-in page"
msgstr ""
@@ -36264,6 +36276,9 @@ msgstr ""
msgid "Sign-in text"
msgstr ""
+msgid "Sign-in using %{provider} auth failed"
+msgstr ""
+
msgid "Sign-out page URL"
msgstr ""
@@ -41103,6 +41118,9 @@ msgstr ""
msgid "Try grouping with different labels"
msgstr ""
+msgid "Try logging in using your username or email. If you have forgotten your password, try recovering it"
+msgstr ""
+
msgid "Try out GitLab Pipelines"
msgstr ""
@@ -44021,6 +44039,9 @@ msgstr ""
msgid "WorkItem|Child items"
msgstr ""
+msgid "WorkItem|Child removed"
+msgstr ""
+
msgid "WorkItem|Closed"
msgstr ""
@@ -44051,6 +44072,9 @@ msgstr ""
msgid "WorkItem|Open"
msgstr ""
+msgid "WorkItem|Remove"
+msgstr ""
+
msgid "WorkItem|Select type"
msgstr ""
@@ -44084,6 +44108,9 @@ msgstr ""
msgid "WorkItem|Type"
msgstr ""
+msgid "WorkItem|Undo"
+msgstr ""
+
msgid "WorkItem|Work Items"
msgstr ""
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 0f6845dc5ee..dbb5c357acb 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -29,5 +29,9 @@ FactoryBot.define do
trait :with_push_branch_filter do
push_events_branch_filter { 'my-branch-*' }
end
+
+ trait :permanently_disabled do
+ recent_failures { WebHook::FAILURE_THRESHOLD + 1 }
+ end
end
end
diff --git a/spec/factories/users/namespace_user_callouts.rb b/spec/factories/users/namespace_user_callouts.rb
new file mode 100644
index 00000000000..fded63d0cce
--- /dev/null
+++ b/spec/factories/users/namespace_user_callouts.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :namespace_callout, class: 'Users::NamespaceCallout' do
+ feature_name { :invite_members_banner }
+
+ user
+ namespace
+ end
+end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index d312965f6cf..44fd21e510a 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe "Admin Runners" do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, namespace: namespace, creator: user) }
- context "runners registration" do
+ describe "runners registration" do
before do
visit admin_runners_path
end
@@ -164,7 +164,9 @@ RSpec.describe "Admin Runners" do
end
describe 'filter by status' do
- let!(:never_contacted) { create(:ci_runner, :instance, description: 'runner-never-contacted', contacted_at: nil) }
+ let!(:never_contacted) do
+ create(:ci_runner, :instance, description: 'runner-never-contacted', contacted_at: nil)
+ end
before do
create(:ci_runner, :instance, description: 'runner-1', contacted_at: Time.zone.now)
@@ -326,13 +328,15 @@ RSpec.describe "Admin Runners" do
visit admin_runners_path
page.within('[data-testid="runner-type-tabs"]') do
click_on 'Instance'
-
- expect(page).to have_link('Instance', class: 'active')
end
end
it_behaves_like 'shows no runners found'
+ it 'shows active tab' do
+ expect(page).to have_link('Instance', class: 'active')
+ end
+
it 'shows no runner' do
expect(page).not_to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
@@ -402,8 +406,8 @@ RSpec.describe "Admin Runners" do
end
it 'sorts by last contact date' do
- create(:ci_runner, :instance, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
- create(:ci_runner, :instance, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
+ create(:ci_runner, :instance, description: 'runner-1', contacted_at: '2018-07-12')
+ create(:ci_runner, :instance, description: 'runner-2', contacted_at: '2018-07-13')
visit admin_runners_path
@@ -448,13 +452,13 @@ RSpec.describe "Admin Runners" do
it 'updates ACTIVE runner status to paused=false' do
visit admin_runners_path('status[]': 'ACTIVE')
- expect(page).to have_current_path(admin_runners_path('paused[]': 'false') )
+ expect(page).to have_current_path(admin_runners_path('paused[]': 'false'))
end
it 'updates PAUSED runner status to paused=true' do
visit admin_runners_path('status[]': 'PAUSED')
- expect(page).to have_current_path(admin_runners_path('paused[]': 'true') )
+ expect(page).to have_current_path(admin_runners_path('paused[]': 'true'))
end
end
end
@@ -477,7 +481,9 @@ RSpec.describe "Admin Runners" do
describe 'runner show page breadcrumbs' do
it 'contains the current runner id and token' do
page.within '[data-testid="breadcrumb-links"]' do
- expect(page.find('[data-testid="breadcrumb-current-link"]')).to have_link("##{runner.id} (#{runner.short_sha})")
+ expect(page.find('[data-testid="breadcrumb-current-link"]')).to have_link(
+ "##{runner.id} (#{runner.short_sha})"
+ )
end
end
end
@@ -515,16 +521,16 @@ RSpec.describe "Admin Runners" do
describe "Runner edit page" do
let(:runner) { create(:ci_runner, :project) }
+ let!(:project1) { create(:project) }
+ let!(:project2) { create(:project) }
before do
- @project1 = create(:project)
- @project2 = create(:project)
visit edit_admin_runner_path(runner)
wait_for_requests
end
- describe 'runner edit page breadcrumbs' do
+ describe 'breadcrumbs' do
it 'contains the current runner id and token' do
page.within '[data-testid="breadcrumb-links"]' do
expect(page).to have_link("##{runner.id} (#{runner.short_sha})")
@@ -539,7 +545,7 @@ RSpec.describe "Admin Runners" do
end
end
- describe 'when a runner is updated', :js do
+ context 'when a runner is updated', :js do
before do
click_on _('Save changes')
wait_for_requests
@@ -556,21 +562,21 @@ RSpec.describe "Admin Runners" do
describe 'projects' do
it 'contains project names' do
- expect(page).to have_content(@project1.full_name)
- expect(page).to have_content(@project2.full_name)
+ expect(page).to have_content(project1.full_name)
+ expect(page).to have_content(project2.full_name)
end
end
describe 'search' do
before do
search_form = find('#runner-projects-search')
- search_form.fill_in 'search', with: @project1.name
+ search_form.fill_in 'search', with: project1.name
search_form.click_button 'Search'
end
it 'contains name of correct project' do
- expect(page).to have_content(@project1.full_name)
- expect(page).not_to have_content(@project2.full_name)
+ expect(page).to have_content(project1.full_name)
+ expect(page).not_to have_content(project2.full_name)
end
end
@@ -584,12 +590,12 @@ RSpec.describe "Admin Runners" do
assigned_project = page.find('[data-testid="assigned-projects"]')
expect(page).to have_content('Runner assigned to project.')
- expect(assigned_project).to have_content(@project2.path)
+ expect(assigned_project).to have_content(project2.path)
end
end
context 'with specific runner' do
- let(:runner) { create(:ci_runner, :project, projects: [@project1]) }
+ let(:runner) { create(:ci_runner, :project, projects: [project1]) }
before do
visit edit_admin_runner_path(runner)
@@ -599,7 +605,7 @@ RSpec.describe "Admin Runners" do
end
context 'with locked runner' do
- let(:runner) { create(:ci_runner, :project, projects: [@project1], locked: true) }
+ let(:runner) { create(:ci_runner, :project, projects: [project1], locked: true) }
before do
visit edit_admin_runner_path(runner)
@@ -610,7 +616,7 @@ RSpec.describe "Admin Runners" do
end
describe 'disable/destroy' do
- let(:runner) { create(:ci_runner, :project, projects: [@project1]) }
+ let(:runner) { create(:ci_runner, :project, projects: [project1]) }
before do
visit edit_admin_runner_path(runner)
@@ -624,7 +630,7 @@ RSpec.describe "Admin Runners" do
new_runner_project = page.find('[data-testid="unassigned-projects"]')
expect(page).to have_content('Runner unassigned from project.')
- expect(new_runner_project).to have_content(@project1.path)
+ expect(new_runner_project).to have_content(project1.path)
end
end
end
diff --git a/spec/features/groups/group_runners_spec.rb b/spec/features/groups/group_runners_spec.rb
index b6aaab207ce..a129db6cb6f 100644
--- a/spec/features/groups/group_runners_spec.rb
+++ b/spec/features/groups/group_runners_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe "Group Runners" do
describe "Group runners page", :js do
let!(:group_registration_token) { group.runners_token }
- context "runners registration" do
+ describe "runners registration" do
before do
visit group_runners_path(group)
end
@@ -128,7 +128,7 @@ RSpec.describe "Group Runners" do
end
end
- context 'filtered search' do
+ describe 'filtered search' do
before do
visit group_runners_path(group)
end
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 2dafd66b406..1d3effd4a2a 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -591,14 +591,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'when a new failures exists' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_failed)
end
@@ -639,14 +639,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'when an existing failure exists' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_failed)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_failed)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
@@ -686,14 +686,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'when a resolved failure exists' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_failed)
end
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
@@ -732,14 +732,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'when a new error exists' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_error)
end
@@ -779,14 +779,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'when an existing error exists' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_error)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_error)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
@@ -825,14 +825,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'when a resolved error exists' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_error)
end
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
@@ -871,7 +871,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
context 'properly truncates the report' do
let(:base_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
10.times do |index|
reports.get_suite('rspec').add_test_case(
create_test_case_rspec_failed(index))
@@ -882,7 +882,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
end
let(:head_reports) do
- Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ Gitlab::Ci::Reports::TestReport.new.tap do |reports|
10.times do |index|
reports.get_suite('rspec').add_test_case(
create_test_case_rspec_failed(index))
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index e222f0d34a9..f5cafa2b2ec 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -464,6 +464,20 @@ RSpec.describe 'File blob', :js do
end
end
+ context 'binary file that appears to be text in the first 1024 bytes' do
+ before do
+ visit_blob('encoding/binary-1.bin', ref: 'binary-encoding')
+ end
+
+ it 'displays the blob' do
+ expect(page).to have_link('Download (23.81 KiB)')
+ # does not show a viewer switcher
+ expect(page).not_to have_selector('.js-blob-viewer-switcher')
+ expect(page).not_to have_selector('.js-copy-blob-source-btn:not(.disabled)')
+ expect(page).not_to have_link('Open raw')
+ end
+ end
+
context 'empty file' do
before do
project.add_maintainer(project.creator)
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index 56506ada3ce..dcd6f1239bb 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -169,8 +169,8 @@ RSpec.describe 'Diff file viewer', :js, :with_clean_rails_cache do
wait_for_requests
end
- it 'shows there is no preview' do
- expect(page).to have_content('No preview for this file type')
+ it 'shows that file was added' do
+ expect(page).to have_content('File added')
end
end
end
diff --git a/spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap b/spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap
deleted file mode 100644
index 2ccfe4f91e7..00000000000
--- a/spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap
+++ /dev/null
@@ -1,215 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ResourceLinksBlock with defaults renders correct component 1`] = `
-