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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-03 00:10:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-03 00:10:20 +0300
commitdf52f8c8af971b336f520e291da02e1ba6a1a9dd (patch)
tree41bb94e2d74f8c43e83abaf04f7e4e35cfd56251
parentf3c61892ecbcad3bfe57f06f197ae9e8996970db (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/qa-common/main.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/message_form.vue17
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/constants.js1
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/edit.js5
-rw-r--r--app/assets/javascripts/header_search/components/app.vue2
-rw-r--r--app/assets/javascripts/header_search/index.js2
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss24
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss6
-rw-r--r--app/assets/stylesheets/themes/dark_mode_overrides.scss2
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss4
-rw-r--r--app/assets/stylesheets/themes/theme_light_gray.scss2
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb1
-rw-r--r--app/helpers/broadcast_messages_helper.rb3
-rw-r--r--app/models/broadcast_message.rb11
-rw-r--r--app/models/deploy_key.rb4
-rw-r--r--app/models/deploy_keys_project.rb1
-rw-r--r--app/services/post_receive_service.rb9
-rw-r--r--app/views/layouts/_header_search.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--db/migrate/20230529163335_add_show_in_cli_to_broadcast_message.rb7
-rw-r--r--db/schema_migrations/202305291633351
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/deploy_keys.md18
-rw-r--r--doc/user/admin_area/broadcast_messages.md5
-rw-r--r--lib/api/deploy_keys.rb4
-rw-r--r--lib/api/entities/deploy_key.rb1
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/factories/broadcast_messages.rb1
-rw-r--r--spec/factories/deploy_keys_projects.rb4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/deploy_key.json6
-rw-r--r--spec/frontend/admin/broadcast_messages/components/message_form_spec.js13
-rw-r--r--spec/frontend/header_search/init_spec.js2
-rw-r--r--spec/helpers/broadcast_messages_helper_spec.rb2
-rw-r--r--spec/models/broadcast_message_spec.rb30
-rw-r--r--spec/models/deploy_key_spec.rb16
-rw-r--r--spec/requests/api/deploy_keys_spec.rb28
-rw-r--r--spec/services/post_receive_service_spec.rb10
-rw-r--r--spec/support/helpers/search_helpers.rb4
40 files changed, 215 insertions, 50 deletions
diff --git a/.gitlab/ci/qa-common/main.gitlab-ci.yml b/.gitlab/ci/qa-common/main.gitlab-ci.yml
index 72f49123190..ddcd49dafb0 100644
--- a/.gitlab/ci/qa-common/main.gitlab-ci.yml
+++ b/.gitlab/ci/qa-common/main.gitlab-ci.yml
@@ -6,7 +6,7 @@ workflow:
include:
- project: gitlab-org/quality/pipeline-common
- ref: 7.1.0
+ ref: 7.2.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 1528d1d6716..e3a5b365cee 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-01c879aca2c628e691e28d791f24185a22db55f2
+1ed139ab2d95e27491b49fcced61d0dc99834ab2
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
index c37251af0ab..427e6c14327 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
@@ -72,6 +72,10 @@ export default {
update: s__('BroadcastMessages|Update broadcast message'),
updateError: s__('BroadcastMessages|There was an error updating broadcast message.'),
cancel: __('Cancel'),
+ showInCli: s__('BroadcastMessages|Git remote responses'),
+ showInCliDescription: s__(
+ 'BroadcastMessages|Show the broadcast message in a command-line interface as a Git remote response',
+ ),
},
messageThemes: THEMES,
messageTypes: TYPES,
@@ -97,6 +101,7 @@ export default {
startsAt: new Date(this.broadcastMessage.startsAt.getTime()),
endsAt: new Date(this.broadcastMessage.endsAt.getTime()),
renderedMessage: '',
+ showInCli: this.broadcastMessage.showInCli,
};
},
computed: {
@@ -127,6 +132,7 @@ export default {
target_access_levels: this.targetAccessLevels,
starts_at: this.startsAt.toISOString(),
ends_at: this.endsAt.toISOString(),
+ show_in_cli: this.showInCli,
});
},
},
@@ -226,6 +232,17 @@ export default {
<span>{{ $options.i18n.dismissableDescription }}</span>
</gl-form-checkbox>
</gl-form-group>
+
+ <gl-form-group :label="$options.i18n.showInCli" label-for="show-in-cli-checkbox">
+ <gl-form-checkbox
+ id="show-in-cli-checkbox"
+ v-model="showInCli"
+ class="gl-mt-3"
+ data-testid="show-in-cli-checkbox"
+ >
+ <span>{{ $options.i18n.showInCliDescription }}</span>
+ </gl-form-checkbox>
+ </gl-form-group>
</template>
<gl-form-group :label="$options.i18n.targetRoles" data-testid="target-roles-checkboxes">
diff --git a/app/assets/javascripts/admin/broadcast_messages/constants.js b/app/assets/javascripts/admin/broadcast_messages/constants.js
index 9f64b2dcaa0..ed137181a48 100644
--- a/app/assets/javascripts/admin/broadcast_messages/constants.js
+++ b/app/assets/javascripts/admin/broadcast_messages/constants.js
@@ -30,4 +30,5 @@ export const NEW_BROADCAST_MESSAGE = {
targetAccessLevels: [],
startsAt: new Date(),
endsAt: new Date(),
+ showInCli: true,
};
diff --git a/app/assets/javascripts/admin/broadcast_messages/edit.js b/app/assets/javascripts/admin/broadcast_messages/edit.js
index 91dae949d45..33b3b028c58 100644
--- a/app/assets/javascripts/admin/broadcast_messages/edit.js
+++ b/app/assets/javascripts/admin/broadcast_messages/edit.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
import MessageForm from './components/message_form.vue';
export default () => {
@@ -16,6 +17,7 @@ export default () => {
targetPath,
startsAt,
endsAt,
+ showInCli,
} = el.dataset;
return new Vue({
@@ -34,11 +36,12 @@ export default () => {
message,
broadcastType,
theme,
- dismissable: dismissable === 'true',
+ dismissable: parseBoolean(dismissable),
targetAccessLevels: JSON.parse(targetAccessLevels),
targetPath,
startsAt: new Date(startsAt),
endsAt: new Date(endsAt),
+ showInCli: parseBoolean(showInCli),
},
},
});
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index 422ec27346e..8c7612f37ff 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -215,7 +215,7 @@ export default {
<form
role="search"
:aria-label="$options.i18n.SEARCH_GITLAB"
- class="header-search gl-relative gl-rounded-base gl-w-full"
+ class="header-search-form gl-relative gl-rounded-base gl-w-full"
:class="searchBarClasses"
data-testid="header-search-form"
>
diff --git a/app/assets/javascripts/header_search/index.js b/app/assets/javascripts/header_search/index.js
index 766d4066e6a..2bbad5f3f98 100644
--- a/app/assets/javascripts/header_search/index.js
+++ b/app/assets/javascripts/header_search/index.js
@@ -16,7 +16,7 @@ export const initHeaderSearchApp = (search = '') => {
}
const searchContainer = headerEl.querySelector('.global-search-container');
- const newHeader = headerEl.querySelector('.header-search-new');
+ const newHeader = headerEl.querySelector('.header-search');
const { searchPath, issuesPath, mrPath, autocompletePath } = el.dataset;
let { searchContext } = el.dataset;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 0c53b3fd866..b2ba1d8830d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -76,7 +76,7 @@ $search-input-field-x-min-width: 200px;
}
}
- .header-search {
+ .header-search-form {
min-width: $search-input-field-min-width;
// This is a temporary workaround!
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index bbcc830353f..11eb39a11cb 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -826,15 +826,15 @@ kbd {
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
margin: 0 2px;
}
-.navbar-gitlab .header-search {
+.navbar-gitlab .header-search-form {
min-width: 320px;
}
@media (min-width: 768px) and (max-width: 1199.98px) {
- .navbar-gitlab .header-search {
+ .navbar-gitlab .header-search-form {
min-width: 200px;
}
}
-.navbar-gitlab .header-search .keyboard-shortcut-helper {
+.navbar-gitlab .header-search-form .keyboard-shortcut-helper {
transform: translateY(calc(50% - 2px));
box-shadow: none;
border-color: transparent;
@@ -1720,7 +1720,7 @@ body.gl-dark .navbar-gitlab .navbar-sub-nav {
body.gl-dark .navbar-gitlab .nav > li {
color: #ececef;
}
-body.gl-dark .navbar-gitlab .nav > li.header-search-new {
+body.gl-dark .navbar-gitlab .nav > li.header-search {
color: #ececef;
}
body.gl-dark .navbar-gitlab .nav > li > a .notification-dot {
@@ -1757,25 +1757,25 @@ body.gl-dark
.notification-dot {
background-color: #ececef;
}
-body.gl-dark .header-search {
+body.gl-dark .header-search-form {
background-color: rgba(236, 236, 239, 0.2) !important;
border-radius: 4px;
}
-body.gl-dark .header-search svg.gl-search-box-by-type-search-icon {
+body.gl-dark .header-search-form svg.gl-search-box-by-type-search-icon {
color: rgba(236, 236, 239, 0.8);
}
-body.gl-dark .header-search input {
+body.gl-dark .header-search-form input {
background-color: transparent;
color: rgba(236, 236, 239, 0.8);
box-shadow: inset 0 0 0 1px rgba(236, 236, 239, 0.4);
}
-body.gl-dark .header-search input::placeholder {
+body.gl-dark .header-search-form input::placeholder {
color: rgba(236, 236, 239, 0.8);
}
-body.gl-dark .header-search input:active::placeholder {
+body.gl-dark .header-search-form input:active::placeholder {
color: #737278;
}
-body.gl-dark .header-search .keyboard-shortcut-helper {
+body.gl-dark .header-search-form .keyboard-shortcut-helper {
color: #ececef;
background-color: rgba(236, 236, 239, 0.2);
}
@@ -1799,11 +1799,11 @@ body.gl-dark .navbar-gitlab .navbar-nav li.active > button {
color: var(--gl-text-color);
background-color: var(--gray-200);
}
-body.gl-dark .navbar-gitlab .header-search {
+body.gl-dark .navbar-gitlab .header-search-form {
background-color: var(--gray-100) !important;
box-shadow: inset 0 0 0 1px var(--border-color) !important;
}
-body.gl-dark .navbar-gitlab .header-search:active {
+body.gl-dark .navbar-gitlab .header-search-form:active {
background-color: var(--gray-100) !important;
box-shadow: inset 0 0 0 1px var(--blue-200) !important;
}
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 44651a66fcb..28a35c82a59 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -826,15 +826,15 @@ kbd {
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
margin: 0 2px;
}
-.navbar-gitlab .header-search {
+.navbar-gitlab .header-search-form {
min-width: 320px;
}
@media (min-width: 768px) and (max-width: 1199.98px) {
- .navbar-gitlab .header-search {
+ .navbar-gitlab .header-search-form {
min-width: 200px;
}
}
-.navbar-gitlab .header-search .keyboard-shortcut-helper {
+.navbar-gitlab .header-search-form .keyboard-shortcut-helper {
transform: translateY(calc(50% - 2px));
box-shadow: none;
border-color: transparent;
diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss
index 3a18f735217..030e41046d3 100644
--- a/app/assets/stylesheets/themes/dark_mode_overrides.scss
+++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss
@@ -261,7 +261,7 @@ body.gl-dark {
}
}
- .header-search {
+ .header-search-form {
background-color: var(--gray-100) !important;
box-shadow: inset 0 0 0 1px var(--border-color) !important;
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index 6e46100dbb3..f841a9047cc 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -68,7 +68,7 @@
> li {
color: $search-and-nav-links;
- &.header-search-new {
+ &.header-search {
color: $gray-900;
}
@@ -151,7 +151,7 @@
}
}
- .header-search {
+ .header-search-form {
background-color: $search-and-nav-links-a20 !important;
border-radius: $border-radius-default;
diff --git a/app/assets/stylesheets/themes/theme_light_gray.scss b/app/assets/stylesheets/themes/theme_light_gray.scss
index a0cbec9a92b..9b7fc10e769 100644
--- a/app/assets/stylesheets/themes/theme_light_gray.scss
+++ b/app/assets/stylesheets/themes/theme_light_gray.scss
@@ -52,7 +52,7 @@ body {
}
}
- .header-search {
+ .header-search-form {
background-color: $white !important;
box-shadow: inset 0 0 0 1px $border-color !important;
border-radius: $border-radius-default;
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 821c3cc1635..7f85103816e 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -93,6 +93,7 @@ module Admin
target_path
broadcast_type
dismissable
+ show_in_cli
], target_access_levels: []).reverse_merge!(target_access_levels: [])
end
end
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 2f14c907b12..a62ffa144f1 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -95,7 +95,8 @@ module BroadcastMessagesHelper
target_path: broadcast_message.target_path,
starts_at: broadcast_message.starts_at.iso8601,
ends_at: broadcast_message.ends_at.iso8601,
- target_access_level_options: target_access_level_options.to_json
+ target_access_level_options: target_access_level_options.to_json,
+ show_in_cli: broadcast_message.show_in_cli.to_s
}
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 733018160cd..fd7a8708683 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -22,6 +22,7 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
validates :ends_at, presence: true
validates :broadcast_type, presence: true
validates :target_access_levels, inclusion: { in: ALLOWED_TARGET_ACCESS_LEVELS }
+ validates :show_in_cli, allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') }
validates :color, allow_blank: true, color: true
validates :font, allow_blank: true, color: true
@@ -29,6 +30,8 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
attribute :color, default: '#E75E40'
attribute :font, default: '#FFFFFF'
+ scope :current_and_future_messages, -> { where('ends_at > :now', now: Time.current).order_id_asc }
+
CACHE_KEY = 'broadcast_message_current_json'
BANNER_CACHE_KEY = 'broadcast_message_current_banner_json'
NOTIFICATION_CACHE_KEY = 'broadcast_message_current_notification_json'
@@ -60,6 +63,10 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
end
end
+ def current_show_in_cli_banner_messages
+ current_banner_messages.select(&:show_in_cli?)
+ end
+
def current_notification_messages(current_path: nil, user_access_level: nil)
fetch_messages NOTIFICATION_CACHE_KEY, current_path, user_access_level do
current_and_future_messages.notification
@@ -72,10 +79,6 @@ class BroadcastMessage < MainClusterwide::ApplicationRecord
end
end
- def current_and_future_messages
- where('ends_at > :now', now: Time.current).order_id_asc
- end
-
def cache
::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
Gitlab::JsonCache.new
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index ef31bedc3a8..f9fa4bd212c 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -10,15 +10,19 @@ class DeployKey < Key
has_many :projects, through: :deploy_keys_projects
has_many :deploy_keys_projects_with_write_access, -> { with_write_access }, class_name: "DeployKeysProject", inverse_of: :deploy_key
+ has_many :deploy_keys_projects_with_readonly_access, -> { with_readonly_access }, class_name: "DeployKeysProject", inverse_of: :deploy_key
has_many :projects_with_write_access, -> { includes(:route) }, class_name: 'Project', through: :deploy_keys_projects_with_write_access, source: :project
+ has_many :projects_with_readonly_access, -> { includes(:route) }, class_name: 'Project', through: :deploy_keys_projects_with_readonly_access, source: :project
has_many :protected_branch_push_access_levels, class_name: '::ProtectedBranch::PushAccessLevel', inverse_of: :deploy_key
has_many :protected_tag_create_access_levels, class_name: '::ProtectedTag::CreateAccessLevel', inverse_of: :deploy_key
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where(deploy_keys_projects: { project_id: projects }) }
scope :with_write_access, -> { joins(:deploy_keys_projects).merge(DeployKeysProject.with_write_access) }
+ scope :with_readonly_access, -> { joins(:deploy_keys_projects).merge(DeployKeysProject.with_readonly_access) }
scope :are_public, -> { where(public: true) }
scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, namespace: :route] }) }
scope :including_projects_with_write_access, -> { includes(:projects_with_write_access) }
+ scope :including_projects_with_readonly_access, -> { includes(:projects_with_readonly_access) }
accepts_nested_attributes_for :deploy_keys_projects, reject_if: :reject_deploy_keys_projects?
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 363ef0b1c9a..e114b7297eb 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -5,6 +5,7 @@ class DeployKeysProject < ApplicationRecord
belongs_to :deploy_key, inverse_of: :deploy_keys_projects
scope :in_project, ->(project) { where(project: project) }
scope :with_write_access, -> { where(can_push: true) }
+ scope :with_readonly_access, -> { where(can_push: false) }
accepts_nested_attributes_for :deploy_key
diff --git a/app/services/post_receive_service.rb b/app/services/post_receive_service.rb
index c376b4036f8..5ab5732ecf5 100644
--- a/app/services/post_receive_service.rb
+++ b/app/services/post_receive_service.rb
@@ -86,14 +86,15 @@ class PostReceiveService
banner = nil
if project
- scoped_messages = BroadcastMessage.current_banner_messages(current_path: project.full_path).select do |message|
- message.target_path.present? && message.matches_current_path(project.full_path)
- end
+ scoped_messages =
+ BroadcastMessage.current_banner_messages(current_path: project.full_path).select do |message|
+ message.target_path.present? && message.matches_current_path(project.full_path) && message.show_in_cli?
+ end
banner = scoped_messages.last
end
- banner ||= BroadcastMessage.current_banner_messages.last
+ banner ||= BroadcastMessage.current_show_in_cli_banner_messages.last
banner&.message
end
diff --git a/app/views/layouts/_header_search.html.haml b/app/views/layouts/_header_search.html.haml
index 28118cf4aaa..1d5f2583bbd 100644
--- a/app/views/layouts/_header_search.html.haml
+++ b/app/views/layouts/_header_search.html.haml
@@ -1,4 +1,4 @@
-#js-header-search.header-search.is-not-active.gl-relative.gl-w-full{ data: { 'search-context' => header_search_context.to_json,
+#js-header-search.header-search-form.is-not-active.gl-relative.gl-w-full{ data: { 'search-context' => header_search_context.to_json,
'search-path' => search_path,
'issues-path' => issues_dashboard_path,
'mr-path' => merge_requests_dashboard_path,
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 7156a0e5931..2c6ccb4abaf 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -29,7 +29,7 @@
.navbar-collapse.gl-transition-medium.collapse.gl-mr-auto.global-search-container.hide-when-top-nav-responsive-open
- search_menu_item = top_nav_search_menu_item_attrs
%ul.nav.navbar-nav.gl-w-full.gl-align-items-center
- %li.nav-item.header-search-new.gl-display-none.gl-lg-display-block.gl-w-full
+ %li.nav-item.header-search.gl-display-none.gl-lg-display-block.gl-w-full
- unless current_controller?(:search)
= render 'layouts/header_search'
%li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' }
diff --git a/db/migrate/20230529163335_add_show_in_cli_to_broadcast_message.rb b/db/migrate/20230529163335_add_show_in_cli_to_broadcast_message.rb
new file mode 100644
index 00000000000..3529b3d84e5
--- /dev/null
+++ b/db/migrate/20230529163335_add_show_in_cli_to_broadcast_message.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddShowInCliToBroadcastMessage < Gitlab::Database::Migration[2.1]
+ def change
+ add_column :broadcast_messages, :show_in_cli, :boolean, default: true, null: false
+ end
+end
diff --git a/db/schema_migrations/20230529163335 b/db/schema_migrations/20230529163335
new file mode 100644
index 00000000000..5a165b81efa
--- /dev/null
+++ b/db/schema_migrations/20230529163335
@@ -0,0 +1 @@
+91e92552fb6283fd1d796c057418bf1f8346f7834eea324aab0fa82f0277c51b \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 9856a06a66f..7822973b778 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -12761,7 +12761,8 @@ CREATE TABLE broadcast_messages (
broadcast_type smallint DEFAULT 1 NOT NULL,
dismissable boolean,
target_access_levels integer[] DEFAULT '{}'::integer[] NOT NULL,
- theme smallint DEFAULT 0 NOT NULL
+ theme smallint DEFAULT 0 NOT NULL,
+ show_in_cli boolean DEFAULT true NOT NULL
);
CREATE SEQUENCE broadcast_messages_id_seq
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 61e93b78067..003a5963ada 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -13,6 +13,8 @@ The deploy keys API can return in responses fingerprints of the public key in th
## List all deploy keys **(FREE SELF)**
+> `projects_with_readonly_access` [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119147) in GitLab 16.0.
+
Get a list of all deploy keys across all projects of the GitLab instance. This
endpoint requires administrator access and is not available on GitLab.com.
@@ -63,7 +65,8 @@ Example response:
"path_with_namespace": "sidney_jones/project3",
"created_at": "2021-10-25T18:33:17.666Z"
}
- ]
+ ],
+ "projects_with_readonly_access": []
},
{
"id": 3,
@@ -73,7 +76,18 @@ Example response:
"fingerprint_sha256": "SHA256:lGI/Ys/Wx7PfMhUO1iuBH92JQKYN+3mhJZvWO4Q5ims",
"created_at": "2013-10-02T11:12:29Z",
"expires_at": null,
- "projects_with_write_access": []
+ "projects_with_write_access": [],
+ "projects_with_readonly_access": [
+ {
+ "id": 74,
+ "description": null,
+ "name": "project3",
+ "name_with_namespace": "Sidney Jones / project3",
+ "path": "project3",
+ "path_with_namespace": "sidney_jones/project3",
+ "created_at": "2021-10-25T18:33:17.666Z"
+ }
+ ]
}
]
```
diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md
index acb3e92fff8..fe5628e3737 100644
--- a/doc/user/admin_area/broadcast_messages.md
+++ b/doc/user/admin_area/broadcast_messages.md
@@ -19,7 +19,7 @@ Broadcast messages can be managed using the [broadcast messages API](../../api/b
## Banners
-Banners are shown on the top of a page and in Git remote responses.
+Banners are shown on the top of a page and optionally in the command line as a Git remote response.
![Broadcast Message Banner](img/broadcast_messages_banner_v15_0.png)
@@ -32,7 +32,7 @@ remote:
...
```
-If more than one banner is active at one time, they are displayed in a stack in order of creation.
+If more than one banner is active at one time, they are displayed at the top of the page in order of creation. In the command line, only the latest banner is shown.
## Notifications
@@ -69,6 +69,7 @@ To add a broadcast message:
- `text-decoration`
1. Select a **Theme**. The default theme is `indigo`.
1. Select the **Dismissable** checkbox to enable users to dismiss the broadcast message.
+1. Optional. Clear the **Git remote responses** checkbox to prevent broadcast messages from being displayed in the command line as Git remote responses.
1. Optional. Select **Target roles** to only show the broadcast message to users with the selected roles. The message displays on group, subgroup, and project pages, and does not display in Git remote responses.
1. If required, add a **Target Path** to only show the broadcast message on URLs matching that path. You can use the wildcard character `*` to match multiple URLs, for example `mygroup/myproject*`.
1. Select a date and time (UTC) for the message to start and end.
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 634d6052b99..f8379392531 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -41,8 +41,10 @@ module API
authenticated_as_admin!
deploy_keys = params[:public] ? DeployKey.are_public : DeployKey.all
+ deploy_keys = deploy_keys.including_projects_with_write_access.including_projects_with_readonly_access
- present paginate(deploy_keys.including_projects_with_write_access), with: Entities::DeployKey, include_projects_with_write_access: true
+ present paginate(deploy_keys),
+ with: Entities::DeployKey, include_projects_with_write_access: true, include_projects_with_readonly_access: true
end
params do
diff --git a/lib/api/entities/deploy_key.rb b/lib/api/entities/deploy_key.rb
index 1bcd06f2c88..0e82d9abb63 100644
--- a/lib/api/entities/deploy_key.rb
+++ b/lib/api/entities/deploy_key.rb
@@ -14,6 +14,7 @@ module API
documentation: { type: 'string', example: 'SHA256:Jrs3LD1Ji30xNLtTVf9NDCj7kkBgPBb2pjvTZ3HfIgU' }
expose :projects_with_write_access, using: Entities::ProjectIdentity, if: -> (_, options) { options[:include_projects_with_write_access] }
+ expose :projects_with_readonly_access, using: Entities::ProjectIdentity, if: -> (_, options) { options[:include_projects_with_readonly_access] }
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3634d963636..44f1a66c33b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8227,6 +8227,9 @@ msgstr ""
msgid "BroadcastMessages|Ends at"
msgstr ""
+msgid "BroadcastMessages|Git remote responses"
+msgstr ""
+
msgid "BroadcastMessages|Green"
msgstr ""
@@ -8260,6 +8263,9 @@ msgstr ""
msgid "BroadcastMessages|Red"
msgstr ""
+msgid "BroadcastMessages|Show the broadcast message in a command-line interface as a Git remote response"
+msgstr ""
+
msgid "BroadcastMessages|Starts at"
msgstr ""
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index fa8d255ae79..0602ce31136 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -5,6 +5,7 @@ FactoryBot.define do
message { "MyText" }
starts_at { 1.day.ago }
ends_at { 1.day.from_now }
+ show_in_cli { true }
broadcast_type { :banner }
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 2a429bf8e56..11833691329 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -8,5 +8,9 @@ FactoryBot.define do
trait :write_access do
can_push { true }
end
+
+ trait :readonly_access do
+ can_push { false }
+ end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/deploy_key.json b/spec/fixtures/api/schemas/public_api/v4/deploy_key.json
index 4f8b5c8422e..77e533488f0 100644
--- a/spec/fixtures/api/schemas/public_api/v4/deploy_key.json
+++ b/spec/fixtures/api/schemas/public_api/v4/deploy_key.json
@@ -50,6 +50,12 @@
"items": {
"$ref": "project/identity.json"
}
+ },
+ "projects_with_readonly_access": {
+ "type": "array",
+ "items": {
+ "$ref": "project/identity.json"
+ }
}
},
"additionalProperties": false
diff --git a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
index ad5f4f6c3c1..dca77e67cac 100644
--- a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
+++ b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
@@ -36,6 +36,7 @@ describe('MessageForm', () => {
const findSubmitButton = () => wrapper.findComponent('[data-testid=submit-button]');
const findCancelButton = () => wrapper.findComponent('[data-testid=cancel-button]');
const findForm = () => wrapper.findComponent(GlForm);
+ const findShowInCli = () => wrapper.findComponent('[data-testid=show-in-cli-checkbox]');
function createComponent({ broadcastMessage = {} } = {}) {
wrapper = mount(MessageForm, {
@@ -99,6 +100,18 @@ describe('MessageForm', () => {
});
});
+ describe('showInCli checkbox', () => {
+ it('renders for Banners', () => {
+ createComponent({ broadcastMessage: { broadcastType: TYPE_BANNER } });
+ expect(findShowInCli().exists()).toBe(true);
+ });
+
+ it('does not render for Notifications', () => {
+ createComponent({ broadcastMessage: { broadcastType: TYPE_NOTIFICATION } });
+ expect(findShowInCli().exists()).toBe(false);
+ });
+ });
+
describe('target roles checkboxes', () => {
it('renders target roles', () => {
createComponent();
diff --git a/spec/frontend/header_search/init_spec.js b/spec/frontend/header_search/init_spec.js
index 9ccc6919b81..baf3c6f08b2 100644
--- a/spec/frontend/header_search/init_spec.js
+++ b/spec/frontend/header_search/init_spec.js
@@ -8,7 +8,7 @@ describe('Header Search EventListener', () => {
jest.restoreAllMocks();
setHTMLFixture(`
<div class="js-header-content">
- <div class="header-search" id="js-header-search" data-autocomplete-path="/search/autocomplete" data-issues-path="/dashboard/issues" data-mr-path="/dashboard/merge_requests" data-search-context="{}" data-search-path="/search">
+ <div class="header-search-form" id="js-header-search" data-autocomplete-path="/search/autocomplete" data-issues-path="/dashboard/issues" data-mr-path="/dashboard/merge_requests" data-search-context="{}" data-search-path="/search">
<input autocomplete="off" class="form-control gl-form-input gl-search-box-by-type-input" data-qa-selector="search_box" id="search" name="search" placeholder="Search GitLab" type="text">
</div>
</div>`);
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index 5d6d404d24d..05e745e249e 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -173,7 +173,7 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do
it 'returns the expected message data attributes' do
keys = [
:id, :message, :broadcast_type, :theme, :dismissable, :target_access_levels, :messages_path,
- :preview_path, :target_path, :starts_at, :ends_at, :target_access_level_options
+ :preview_path, :target_path, :starts_at, :ends_at, :target_access_level_options, :show_in_cli
]
expect(broadcast_message_data(message).keys).to match(keys)
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 5fcf6813b0a..13653dc95ff 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe BroadcastMessage do
+RSpec.describe BroadcastMessage, feature_category: :onboarding do
subject { build(:broadcast_message) }
it { is_expected.to be_valid }
@@ -24,11 +24,17 @@ RSpec.describe BroadcastMessage do
it { is_expected.to allow_value(1).for(:broadcast_type) }
it { is_expected.not_to allow_value(nil).for(:broadcast_type) }
it { is_expected.not_to allow_value(nil).for(:target_access_levels) }
+ it { is_expected.not_to allow_value(nil).for(:show_in_cli) }
it do
is_expected.to validate_inclusion_of(:target_access_levels)
.in_array(described_class::ALLOWED_TARGET_ACCESS_LEVELS)
end
+
+ it do
+ is_expected.to validate_inclusion_of(:show_in_cli)
+ .in_array([true, false])
+ end
end
describe 'default values' do
@@ -331,6 +337,18 @@ RSpec.describe BroadcastMessage do
end
end
+ describe '.current_show_in_cli_banner_messages', :use_clean_rails_memory_store_caching do
+ subject { -> { described_class.current_show_in_cli_banner_messages } }
+
+ it 'only returns banner messages that has show_in_cli as true' do
+ show_in_cli_message = create(:broadcast_message)
+ create(:broadcast_message, broadcast_type: :notification)
+ create(:broadcast_message, show_in_cli: false)
+
+ expect(subject.call).to contain_exactly(show_in_cli_message)
+ end
+ end
+
describe '#attributes' do
it 'includes message_html field' do
expect(subject.attributes.keys).to include("cached_markdown_version", "message_html")
@@ -404,4 +422,14 @@ RSpec.describe BroadcastMessage do
message.flush_redis_cache
end
end
+
+ describe '#current_and_future_messages' do
+ let_it_be(:message_a) { create(:broadcast_message, ends_at: 1.day.ago) }
+ let_it_be(:message_b) { create(:broadcast_message, ends_at: Time.current + 2.days) }
+ let_it_be(:message_c) { create(:broadcast_message, ends_at: Time.current + 7.days) }
+
+ it 'returns only current and future messages by ascending ends_at' do
+ expect(described_class.current_and_future_messages).to eq [message_b, message_c]
+ end
+ end
end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 337fa40b4ba..528b36babc6 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -20,6 +20,20 @@ RSpec.describe DeployKey, :mailer do
.source(:project)
end
+ it do
+ is_expected.to have_many(:deploy_keys_projects_with_readonly_access)
+ .conditions(can_push: false)
+ .class_name('DeployKeysProject')
+ .inverse_of(:deploy_key)
+ end
+
+ it do
+ is_expected.to have_many(:projects_with_readonly_access)
+ .class_name('Project')
+ .through(:deploy_keys_projects_with_readonly_access)
+ .source(:project)
+ end
+
it { is_expected.to have_many(:projects) }
it { is_expected.to have_many(:protected_branch_push_access_levels).inverse_of(:deploy_key) }
it { is_expected.to have_many(:protected_tag_create_access_levels).inverse_of(:deploy_key) }
@@ -95,7 +109,7 @@ RSpec.describe DeployKey, :mailer do
it { is_expected.to be_empty }
end
- context 'and this deploy key has not write access to the project' do
+ context 'and this deploy key has no write access to the project' do
let(:specific_deploy_key) { create(:deploy_key, deploy_keys_projects: [create(:deploy_keys_project, project: project)]) }
it { is_expected.to be_empty }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 18a9211df3e..30c345ef458 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -59,6 +59,17 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect { make_api_request }.not_to exceed_all_query_limit(control)
end
+ it 'avoids N+1 database queries', :use_sql_query_cache, :request_store do
+ create(:deploy_keys_project, :readonly_access, project: project2, deploy_key: deploy_key)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { make_api_request }
+
+ deploy_key2 = create(:deploy_key, public: true)
+ create(:deploy_keys_project, :readonly_access, project: project3, deploy_key: deploy_key2)
+
+ expect { make_api_request }.not_to exceed_all_query_limit(control)
+ end
+
context 'when `public` parameter is `true`' do
it 'only returns public deploy keys' do
make_api_request({ public: true })
@@ -81,6 +92,21 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect(response_projects_with_write_access[1]['id']).to eq(project3.id)
end
end
+
+ context 'projects_with_readonly_access' do
+ let!(:deploy_keys_project2) { create(:deploy_keys_project, :readonly_access, project: project2, deploy_key: deploy_key) }
+ let!(:deploy_keys_project3) { create(:deploy_keys_project, :readonly_access, project: project3, deploy_key: deploy_key) }
+
+ it 'returns projects with readonly access' do
+ make_api_request
+
+ response_projects_with_readonly_access = json_response.first['projects_with_readonly_access']
+
+ expect(response_projects_with_readonly_access[0]['id']).to eq(project.id)
+ expect(response_projects_with_readonly_access[1]['id']).to eq(project2.id)
+ expect(response_projects_with_readonly_access[2]['id']).to eq(project3.id)
+ end
+ end
end
end
@@ -103,6 +129,7 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
expect(json_response.first).not_to have_key(:projects_with_write_access)
+ expect(json_response.first).not_to have_key(:projects_with_readonly_access)
end
it 'returns multiple deploy keys without N + 1' do
@@ -129,6 +156,7 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect(json_response['title']).to eq(deploy_key.title)
expect(json_response).not_to have_key(:projects_with_write_access)
+ expect(json_response).not_to have_key(:projects_with_readonly_access)
end
it 'returns 404 Not Found with invalid ID' do
diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb
index 13bd103003f..20d86f74f86 100644
--- a/spec/services/post_receive_service_spec.rb
+++ b/spec/services/post_receive_service_spec.rb
@@ -214,11 +214,17 @@ RSpec.describe PostReceiveService, feature_category: :team_planning do
end
context 'broadcast message banner exists' do
- it 'outputs a broadcast message' do
- broadcast_message = create(:broadcast_message)
+ it 'outputs a broadcast message when show_in_cli is true' do
+ broadcast_message = create(:broadcast_message, show_in_cli: true)
expect(subject).to include(build_alert_message(broadcast_message.message))
end
+
+ it 'does not output a broadcast message when show_in_cli is false' do
+ create(:broadcast_message, show_in_cli: false)
+
+ expect(has_alert_messages?(subject)).to be_falsey
+ end
end
context 'broadcast message notification exists' do
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index 75853371c0f..72673648b89 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -2,7 +2,7 @@
module SearchHelpers
def fill_in_search(text)
- page.within('.header-search-new') do
+ page.within('.header-search') do
find('#search').click
fill_in 'search', with: text
end
@@ -11,7 +11,7 @@ module SearchHelpers
end
def submit_search(query)
- page.within('.header-search, .search-page-form') do
+ page.within('.header-search-form, .search-page-form') do
field = find_field('search')
field.click
field.fill_in(with: query)