summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js4
-rw-r--r--app/assets/javascripts/monitoring/stores/variable_mapping.js36
-rw-r--r--app/assets/javascripts/monitoring/utils.js40
-rw-r--r--app/controllers/repositories/git_http_client_controller.rb3
-rw-r--r--app/services/authorized_project_update/periodic_recalculate_service.rb18
-rw-r--r--app/services/authorized_project_update/recalculate_for_user_range_service.rb20
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb2
-rw-r--r--app/workers/all_queues.yml16
-rw-r--r--app/workers/authorized_project_update/periodic_recalculate_worker.rb20
-rw-r--r--app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb20
-rw-r--r--changelogs/unreleased/205273-project_auth_periodic_recompute.yml5
-rw-r--r--changelogs/unreleased/223218-re-introduce-ssh-key-comments-via-the-api.yml5
-rw-r--r--changelogs/unreleased/bvl-fix-git-http-user-logging.yml5
-rw-r--r--changelogs/unreleased/fix-templating-variables-set-from-url.yml5
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--doc/user/discussions/index.md26
-rw-r--r--doc/user/profile/preferences.md5
-rw-r--r--doc/user/project/issues/design_management.md22
-rw-r--r--doc/user/project/static_site_editor/index.md7
-rw-r--r--doc/user/project/web_ide/img/dark_theme_v13.0.pngbin852854 -> 0 bytes
-rw-r--r--doc/user/project/web_ide/img/dark_theme_v13_0.pngbin0 -> 205992 bytes
-rw-r--r--doc/user/project/web_ide/img/solarized_dark_theme_v13_1.pngbin0 -> 117131 bytes
-rw-r--r--doc/user/project/web_ide/img/solarized_light_theme_v13.0.pngbin790377 -> 0 bytes
-rw-r--r--doc/user/project/web_ide/img/solarized_light_theme_v13_0.pngbin0 -> 194914 bytes
-rw-r--r--doc/user/project/web_ide/index.md14
-rw-r--r--lib/api/deploy_keys.rb11
-rw-r--r--lib/api/entities/deploy_key.rb9
-rw-r--r--lib/api/entities/deploy_key_with_user.rb3
-rw-r--r--lib/api/entities/deploy_keys_project.rb2
-rw-r--r--spec/controllers/repositories/git_http_controller_spec.rb11
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js113
-rw-r--r--spec/frontend/monitoring/store/variable_mapping_spec.js73
-rw-r--r--spec/frontend/monitoring/utils_spec.js78
-rw-r--r--spec/initializers/lograge_spec.rb12
-rw-r--r--spec/lib/api/entities/deploy_key_spec.rb22
-rw-r--r--spec/lib/api/entities/deploy_keys_project_spec.rb25
-rw-r--r--spec/lib/api/entities/ssh_key_spec.rb22
-rw-r--r--spec/requests/api/deploy_keys_spec.rb54
-rw-r--r--spec/requests/jwt_controller_spec.rb9
-rw-r--r--spec/services/authorized_project_update/periodic_recalculate_service_spec.rb30
-rw-r--r--spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb19
-rw-r--r--spec/support/shared_contexts/controllers/logging_shared_context.rb21
-rw-r--r--spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb27
-rw-r--r--spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb30
46 files changed, 636 insertions, 217 deletions
diff --git a/Gemfile b/Gemfile
index 5477ee3d181..33d4ebb7f22 100644
--- a/Gemfile
+++ b/Gemfile
@@ -416,7 +416,7 @@ end
gem 'octokit', '~> 4.15'
# https://gitlab.com/gitlab-org/gitlab/issues/207207
-gem 'gitlab-mail_room', '~> 0.0.4', require: 'mail_room'
+gem 'gitlab-mail_room', '~> 0.0.6', require: 'mail_room'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
diff --git a/Gemfile.lock b/Gemfile.lock
index 319c4df3f9d..2e3537e223f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -390,7 +390,7 @@ GEM
opentracing (~> 0.4)
redis (> 3.0.0, < 5.0.0)
gitlab-license (1.0.0)
- gitlab-mail_room (0.0.4)
+ gitlab-mail_room (0.0.6)
gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1)
gitlab-puma (4.3.3.gitlab.2)
@@ -1241,7 +1241,7 @@ DEPENDENCIES
gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0)
gitlab-license (~> 1.0)
- gitlab-mail_room (~> 0.0.4)
+ gitlab-mail_room (~> 0.0.6)
gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1)
gitlab-puma (~> 4.3.3.gitlab.2)
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 058fab5f4fc..5795e756282 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -2,8 +2,8 @@ import { slugify } from '~/lib/utils/text_utility';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { parseTemplatingVariables } from './variable_mapping';
import { NOT_IN_DB_PREFIX, linkTypes } from '../constants';
+import { mergeURLVariables, parseTemplatingVariables } from './variable_mapping';
import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants';
import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range';
import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility';
@@ -289,7 +289,7 @@ export const mapToDashboardViewModel = ({
}) => {
return {
dashboard,
- variables: parseTemplatingVariables(templating),
+ variables: mergeURLVariables(parseTemplatingVariables(templating)),
links: links.map(mapLinksToViewModel),
panelGroups: panel_groups.map(mapToPanelGroupViewModel),
};
diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js
index 66b9899f673..c0a8150063b 100644
--- a/app/assets/javascripts/monitoring/stores/variable_mapping.js
+++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js
@@ -1,4 +1,5 @@
import { isString } from 'lodash';
+import { templatingVariablesFromUrl } from '../utils';
import { VARIABLE_TYPES } from '../constants';
/**
@@ -164,4 +165,39 @@ export const parseTemplatingVariables = ({ variables = {} } = {}) =>
return acc;
}, {});
+/**
+ * Custom variables are defined in the dashboard yml file
+ * and their values can be passed through the URL.
+ *
+ * On component load, this method merges variables data
+ * from the yml file with URL data to store in the Vuex store.
+ * Not all params coming from the URL need to be stored. Only
+ * the ones that have a corresponding variable defined in the
+ * yml file.
+ *
+ * This ensures that there is always a single source of truth
+ * for variables
+ *
+ * This method can be improved further. See the below issue
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/217713
+ *
+ * @param {Object} varsFromYML template variables from yml file
+ * @returns {Object}
+ */
+export const mergeURLVariables = (varsFromYML = {}) => {
+ const varsFromURL = templatingVariablesFromUrl();
+ const variables = {};
+ Object.keys(varsFromYML).forEach(key => {
+ if (Object.prototype.hasOwnProperty.call(varsFromURL, key)) {
+ variables[key] = {
+ ...varsFromYML[key],
+ value: varsFromURL[key],
+ };
+ } else {
+ variables[key] = varsFromYML[key];
+ }
+ });
+ return variables;
+};
+
export default {};
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 95d544bd6d4..4d2927a066e 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -170,11 +170,10 @@ export const convertVariablesForURL = variables =>
* begin with a constant prefix so that it doesn't collide with
* other URL params.
*
- * @param {String} New URL
+ * @param {String} search URL
* @returns {Object} The custom variables defined by the user in the URL
*/
-
-export const getPromCustomVariablesFromUrl = (search = window.location.search) => {
+export const templatingVariablesFromUrl = (search = window.location.search) => {
const params = queryToObject(search);
// pick the params with variable prefix
const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX));
@@ -353,39 +352,4 @@ export const barChartsDataParser = (data = []) =>
{},
);
-/**
- * Custom variables are defined in the dashboard yml file
- * and their values can be passed through the URL.
- *
- * On component load, this method merges variables data
- * from the yml file with URL data to store in the Vuex store.
- * Not all params coming from the URL need to be stored. Only
- * the ones that have a corresponding variable defined in the
- * yml file.
- *
- * This ensures that there is always a single source of truth
- * for variables
- *
- * This method can be improved further. See the below issue
- * https://gitlab.com/gitlab-org/gitlab/-/issues/217713
- *
- * @param {Object} varsFromYML template variables from yml file
- * @returns {Object}
- */
-export const mergeURLVariables = (varsFromYML = {}) => {
- const varsFromURL = getPromCustomVariablesFromUrl();
- const variables = {};
- Object.keys(varsFromYML).forEach(key => {
- if (Object.prototype.hasOwnProperty.call(varsFromURL, key)) {
- variables[key] = {
- ...varsFromYML[key],
- value: varsFromURL[key],
- };
- } else {
- variables[key] = varsFromYML[key];
- }
- });
- return variables;
-};
-
export default {};
diff --git a/app/controllers/repositories/git_http_client_controller.rb b/app/controllers/repositories/git_http_client_controller.rb
index d03daa406cf..e02955433b2 100644
--- a/app/controllers/repositories/git_http_client_controller.rb
+++ b/app/controllers/repositories/git_http_client_controller.rb
@@ -18,8 +18,7 @@ module Repositories
skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
- before_action :parse_repo_path
- before_action :authenticate_user
+ prepend_before_action :authenticate_user, :parse_repo_path
private
diff --git a/app/services/authorized_project_update/periodic_recalculate_service.rb b/app/services/authorized_project_update/periodic_recalculate_service.rb
new file mode 100644
index 00000000000..91c0f50e5e0
--- /dev/null
+++ b/app/services/authorized_project_update/periodic_recalculate_service.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module AuthorizedProjectUpdate
+ class PeriodicRecalculateService
+ BATCH_SIZE = 480
+ DELAY_INTERVAL = 30.seconds.to_i
+
+ def execute
+ # Using this approach (instead of eg. User.each_batch) keeps the arguments
+ # the same for AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker
+ # even if the user list changes, so we can deduplicate these jobs.
+ (1..User.maximum(:id)).each_slice(BATCH_SIZE).with_index do |batch, index|
+ delay = DELAY_INTERVAL * index
+ AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker.perform_in(delay, *batch.minmax)
+ end
+ end
+ end
+end
diff --git a/app/services/authorized_project_update/recalculate_for_user_range_service.rb b/app/services/authorized_project_update/recalculate_for_user_range_service.rb
new file mode 100644
index 00000000000..14b0f5d6117
--- /dev/null
+++ b/app/services/authorized_project_update/recalculate_for_user_range_service.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module AuthorizedProjectUpdate
+ class RecalculateForUserRangeService
+ def initialize(start_user_id, end_user_id)
+ @start_user_id = start_user_id
+ @end_user_id = end_user_id
+ end
+
+ def execute
+ User.where(id: start_user_id..end_user_id).select(:id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord
+ Users::RefreshAuthorizedProjectsService.new(user).execute
+ end
+ end
+
+ private
+
+ attr_reader :start_user_id, :end_user_id
+ end
+end
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 0e7a4821bdf..621266f00e1 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -85,8 +85,6 @@ module Users
# remove - The IDs of the authorization rows to remove.
# add - Rows to insert in the form `[user id, project id, access level]`
def update_authorizations(remove = [], add = [])
- return if remove.empty? && add.empty?
-
User.transaction do
user.remove_project_authorizations(remove) unless remove.empty?
ProjectAuthorization.insert_authorizations(add) unless add.empty?
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 0699be0f4cb..3baa2166812 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -11,6 +11,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: authorized_project_update:authorized_project_update_user_refresh_over_user_range
+ :feature_category: :authentication_and_authorization
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: authorized_project_update:authorized_project_update_user_refresh_with_low_urgency
:feature_category: :authentication_and_authorization
:has_external_dependencies:
@@ -99,6 +107,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:authorized_project_update_periodic_recalculate
+ :feature_category: :source_code_management
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:ci_archive_traces_cron
:feature_category: :continuous_integration
:has_external_dependencies:
diff --git a/app/workers/authorized_project_update/periodic_recalculate_worker.rb b/app/workers/authorized_project_update/periodic_recalculate_worker.rb
new file mode 100644
index 00000000000..0d1ad67d7bb
--- /dev/null
+++ b/app/workers/authorized_project_update/periodic_recalculate_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module AuthorizedProjectUpdate
+ class PeriodicRecalculateWorker
+ include ApplicationWorker
+ # This worker does not perform work scoped to a context
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ feature_category :source_code_management
+ urgency :low
+
+ idempotent!
+
+ def perform
+ if ::Feature.enabled?(:periodic_project_authorization_recalculation, default_enabled: true)
+ AuthorizedProjectUpdate::PeriodicRecalculateService.new.execute
+ end
+ end
+ end
+end
diff --git a/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb
new file mode 100644
index 00000000000..336b1c5443e
--- /dev/null
+++ b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module AuthorizedProjectUpdate
+ class UserRefreshOverUserRangeWorker
+ include ApplicationWorker
+
+ feature_category :authentication_and_authorization
+ urgency :low
+ queue_namespace :authorized_project_update
+ deduplicate :until_executing, including_scheduled: true
+
+ idempotent!
+
+ def perform(start_user_id, end_user_id)
+ if ::Feature.enabled?(:periodic_project_authorization_recalculation, default_enabled: true)
+ AuthorizedProjectUpdate::RecalculateForUserRangeService.new(start_user_id, end_user_id).execute
+ end
+ end
+ end
+end
diff --git a/changelogs/unreleased/205273-project_auth_periodic_recompute.yml b/changelogs/unreleased/205273-project_auth_periodic_recompute.yml
new file mode 100644
index 00000000000..2548ad23367
--- /dev/null
+++ b/changelogs/unreleased/205273-project_auth_periodic_recompute.yml
@@ -0,0 +1,5 @@
+---
+title: Periodically recompute project authorizations
+merge_request: 34071
+author:
+type: added
diff --git a/changelogs/unreleased/223218-re-introduce-ssh-key-comments-via-the-api.yml b/changelogs/unreleased/223218-re-introduce-ssh-key-comments-via-the-api.yml
new file mode 100644
index 00000000000..f173dfc2d47
--- /dev/null
+++ b/changelogs/unreleased/223218-re-introduce-ssh-key-comments-via-the-api.yml
@@ -0,0 +1,5 @@
+---
+title: Do not mask key comments for DeployKeys
+merge_request: 35014
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-fix-git-http-user-logging.yml b/changelogs/unreleased/bvl-fix-git-http-user-logging.yml
new file mode 100644
index 00000000000..70a1194b23d
--- /dev/null
+++ b/changelogs/unreleased/bvl-fix-git-http-user-logging.yml
@@ -0,0 +1,5 @@
+---
+title: Load user before logging git http-requests
+merge_request: 34923
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-templating-variables-set-from-url.yml b/changelogs/unreleased/fix-templating-variables-set-from-url.yml
new file mode 100644
index 00000000000..d5b1c07f976
--- /dev/null
+++ b/changelogs/unreleased/fix-templating-variables-set-from-url.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing templating vars set from URL in metrics dashboard
+merge_request: 34668
+author:
+type: fixed
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 9d9f24183d5..0afd43634c3 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -499,6 +499,9 @@ Settings.cron_jobs['x509_issuer_crl_check_worker']['job_class'] = 'X509IssuerCrl
Settings.cron_jobs['users_create_statistics_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['users_create_statistics_worker']['cron'] ||= '2 15 * * *'
Settings.cron_jobs['users_create_statistics_worker']['job_class'] = 'Users::CreateStatisticsWorker'
+Settings.cron_jobs['authorized_project_update_periodic_recalculate_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['authorized_project_update_periodic_recalculate_worker']['cron'] ||= '45 1 * * 6'
+Settings.cron_jobs['authorized_project_update_periodic_recalculate_worker']['job_class'] = 'AuthorizedProjectUpdate::PeriodicRecalculateWorker'
Gitlab.ee do
Settings.cron_jobs['adjourned_group_deletion_worker'] ||= Settingslogic.new({})
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 5ee11c553af..44802214d7b 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -491,7 +491,10 @@ introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/issues/25381).
### Batch Suggestions
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/25486) in GitLab 13.1.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/#alpha).
+> - It's deployed behind a feature flag, disabled by default.
+> - It's disabled on GitLab.com.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-batch-suggestions).
You can apply multiple suggestions at once to reduce the number of commits added
to your branch to address your reviewers' requests.
@@ -512,6 +515,27 @@ to your branch to address your reviewers' requests.
![A code change suggestion displayed, with the button to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
+#### Enable or disable Batch Suggestions
+
+Batch Suggestions is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
+can enable it for your instance.
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:batched_suggestions)
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:batched_suggestions)
+```
+
## Start a thread by replying to a standard comment
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index ccaea61ae4b..a5fa3cf373f 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -80,8 +80,9 @@ The default syntax theme is White, and you can choose among 5 different themes:
[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in 13.0, the theme
you choose also applies to the [Web IDE](../project/web_ide/index.md)'s code editor and [Snippets](../snippets.md).
-The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808),
-which applies to the entire Web IDE screen.
+The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808) and
+the [solarized dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/219228),
+which apply to the entire Web IDE screen.
## Behavior
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 981c2a7c34a..ac397592a3b 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -181,17 +181,23 @@ so that everyone involved can participate in the discussion.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13049) in GitLab 13.1.
-Discussion threads can be resolved on Designs. You can mark a thread as resolved
-or unresolved by clicking the **Resolve thread** icon at the first comment of the
-discussion.
+Discussion threads can be resolved on Designs.
-![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
+There are two ways to resolve/unresolve a Design thread:
-Pinned comments can also be resolved or unresolved in their threads.
-When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
-the thread once published.
+1. You can mark a thread as resolved or unresolved by clicking the checkmark icon for **Resolve thread** in the top-right corner of the first comment of the discussion:
-![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
+ ![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
+
+1. Design threads can also be resolved or unresolved in their threads by using a checkbox.
+ When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
+ the thread once published:
+
+ ![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
+
+Note that your resolved comment pins will disappear from the Design to free up space for new discussions.
+However, if you need to revisit or find a resolved discussion, all of your resolved threads will be
+available in the **Resolved Comment** area at the bottom of the right sidebar.
## Referring to designs in Markdown
diff --git a/doc/user/project/static_site_editor/index.md b/doc/user/project/static_site_editor/index.md
index e2e498b605a..15c3bd5522e 100644
--- a/doc/user/project/static_site_editor/index.md
+++ b/doc/user/project/static_site_editor/index.md
@@ -7,6 +7,8 @@ description: "The static site editor enables users to edit content on static web
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28758) in GitLab 12.10.
> - WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214559) in GitLab 13.0.
+> - Support for adding images through the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216640) in GitLab 13.1.
+> - Markdown front matter hidden on the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216834) in GitLab 13.1.
DANGER: **Danger:**
In GitLab 13.0, we [introduced breaking changes](https://gitlab.com/gitlab-org/gitlab/-/issues/213282)
@@ -78,6 +80,11 @@ Anyone satisfying the [requirements](#requirements) will be able to edit the
content of the pages without prior knowledge of Git nor of your site's
codebase.
+NOTE: **Note:**
+From GitLab 13.1 onwards, the YAML front matter of Markdown files is hidden on the
+WYSIWYG editor to avoid unintended changes. To edit it, use the Markdown editing mode, the regular
+GitLab file editor, or the Web IDE.
+
### Use the Static Site Editor to edit your content
For instance, suppose you are a recently hired technical writer at a large
diff --git a/doc/user/project/web_ide/img/dark_theme_v13.0.png b/doc/user/project/web_ide/img/dark_theme_v13.0.png
deleted file mode 100644
index 6034bc52c05..00000000000
--- a/doc/user/project/web_ide/img/dark_theme_v13.0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/web_ide/img/dark_theme_v13_0.png b/doc/user/project/web_ide/img/dark_theme_v13_0.png
new file mode 100644
index 00000000000..f1999363904
--- /dev/null
+++ b/doc/user/project/web_ide/img/dark_theme_v13_0.png
Binary files differ
diff --git a/doc/user/project/web_ide/img/solarized_dark_theme_v13_1.png b/doc/user/project/web_ide/img/solarized_dark_theme_v13_1.png
new file mode 100644
index 00000000000..ccb9cf6f126
--- /dev/null
+++ b/doc/user/project/web_ide/img/solarized_dark_theme_v13_1.png
Binary files differ
diff --git a/doc/user/project/web_ide/img/solarized_light_theme_v13.0.png b/doc/user/project/web_ide/img/solarized_light_theme_v13.0.png
deleted file mode 100644
index f3c4aa142a4..00000000000
--- a/doc/user/project/web_ide/img/solarized_light_theme_v13.0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/web_ide/img/solarized_light_theme_v13_0.png b/doc/user/project/web_ide/img/solarized_light_theme_v13_0.png
new file mode 100644
index 00000000000..adf6d3c6b02
--- /dev/null
+++ b/doc/user/project/web_ide/img/solarized_light_theme_v13_0.png
Binary files differ
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 0ddc9762bc5..ce20f2308e6 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -45,17 +45,19 @@ Single file editing is based on the [Ace Editor](https://ace.c9.io).
### Themes
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in GitLab 13.0.
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in GitLab in 13.0.
+> - Full Solarized Dark Theme [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219228) in GitLab 13.1.
All the themes GitLab supports for syntax highlighting are added to the Web IDE's code editor.
You can pick a theme from your [profile preferences](../../profile/preferences.md).
-The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808),
-which applies to the entire Web IDE screen.
+The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808) and
+the [solarized dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/219228),
+which apply to the entire Web IDE screen.
-| Solarized Light Theme | Dark Theme |
-|---------------------------------------------------------------|-----------------------------------------|
-| ![Solarized Light Theme](img/solarized_light_theme_v13.0.png) | ![Dark Theme](img/dark_theme_v13.0.png) |
+| Solarized Light Theme | Solarized Dark Theme | Dark Theme |
+|---------------------------------------------------------------|-------------------------------------------------------------|-----------------------------------------|
+| ![Solarized Light Theme](img/solarized_light_theme_v13_0.png) | ![Solarized Dark Theme](img/solarized_dark_theme_v13_1.png) | ![Dark Theme](img/dark_theme_v13_0.png) |
## Configure the Web IDE
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 11340e91aae..3259b615369 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -25,8 +25,7 @@ module API
get "deploy_keys" do
authenticated_as_admin!
- deploy_keys = DeployKey.all.preload_users
- present paginate(deploy_keys), with: Entities::SSHKey
+ present paginate(DeployKey.all), with: Entities::DeployKey
end
params do
@@ -43,7 +42,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ":id/deploy_keys" do
- keys = user_project.deploy_keys_projects.preload(deploy_key: [:user])
+ keys = user_project.deploy_keys_projects.preload(:deploy_key)
present paginate(keys), with: Entities::DeployKeysProject
end
@@ -105,7 +104,7 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
desc 'Update an existing deploy key for a project' do
- success Entities::SSHKey
+ success Entities::DeployKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
@@ -140,7 +139,7 @@ module API
desc 'Enable a deploy key for a project' do
detail 'This feature was added in GitLab 8.11'
- success Entities::SSHKey
+ success Entities::DeployKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
@@ -150,7 +149,7 @@ module API
current_user, declared_params).execute
if key
- present key, with: Entities::SSHKey
+ present key, with: Entities::DeployKey
else
not_found!('Deploy Key')
end
diff --git a/lib/api/entities/deploy_key.rb b/lib/api/entities/deploy_key.rb
new file mode 100644
index 00000000000..ed922c24eda
--- /dev/null
+++ b/lib/api/entities/deploy_key.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class DeployKey < Entities::SSHKey
+ expose :key
+ end
+ end
+end
diff --git a/lib/api/entities/deploy_key_with_user.rb b/lib/api/entities/deploy_key_with_user.rb
index 31024dc3910..a8f6440b67c 100644
--- a/lib/api/entities/deploy_key_with_user.rb
+++ b/lib/api/entities/deploy_key_with_user.rb
@@ -2,7 +2,8 @@
module API
module Entities
- class DeployKeyWithUser < Entities::SSHKeyWithUser
+ class DeployKeyWithUser < Entities::DeployKey
+ expose :user, using: Entities::UserPublic
expose :deploy_keys_projects
end
end
diff --git a/lib/api/entities/deploy_keys_project.rb b/lib/api/entities/deploy_keys_project.rb
index 64725459167..12a86fbdf8e 100644
--- a/lib/api/entities/deploy_keys_project.rb
+++ b/lib/api/entities/deploy_keys_project.rb
@@ -3,7 +3,7 @@
module API
module Entities
class DeployKeysProject < Grape::Entity
- expose :deploy_key, merge: true, using: Entities::SSHKey
+ expose :deploy_key, merge: true, using: Entities::DeployKey
expose :can_push
end
end
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb
index aafb933df32..c938df8cf4e 100644
--- a/spec/controllers/repositories/git_http_controller_spec.rb
+++ b/spec/controllers/repositories/git_http_controller_spec.rb
@@ -60,10 +60,21 @@ RSpec.describe Repositories::GitHttpController do
get :info_refs, params: params
end
+
+ include_context 'parsed logs' do
+ it 'adds user info to the logs' do
+ get :info_refs, params: params
+
+ expect(log_data).to include('username' => user.username,
+ 'user_id' => user.id,
+ 'meta.user' => user.username)
+ end
+ end
end
context 'with exceptions' do
before do
+ allow(controller).to receive(:authenticate_user).and_return(true)
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
end
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 3a70bda51da..2dea40585f1 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -9,6 +9,7 @@ import {
convertToGrafanaTimeRange,
addDashboardMetaDataToLink,
} from '~/monitoring/stores/utils';
+import * as urlUtils from '~/lib/utils/url_utility';
import { annotationsData } from '../mock_data';
import { NOT_IN_DB_PREFIX } from '~/monitoring/constants';
@@ -398,6 +399,118 @@ describe('mapToDashboardViewModel', () => {
});
});
});
+
+ describe('templating variables mapping', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'queryToObject');
+ });
+
+ afterEach(() => {
+ urlUtils.queryToObject.mockRestore();
+ });
+
+ it('sets variables as-is from yml file if URL has no variables', () => {
+ const response = {
+ dashboard: 'Dashboard Name',
+ links: [],
+ templating: {
+ variables: {
+ pod: 'kubernetes',
+ pod_2: 'kubernetes-2',
+ },
+ },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce();
+
+ expect(mapToDashboardViewModel(response)).toMatchObject({
+ dashboard: 'Dashboard Name',
+ links: [],
+ variables: {
+ pod: {
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
+ },
+ pod_2: {
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ },
+ });
+ });
+
+ it('sets variables as-is from yml file if URL has no matching variables', () => {
+ const response = {
+ dashboard: 'Dashboard Name',
+ links: [],
+ templating: {
+ variables: {
+ pod: 'kubernetes',
+ pod_2: 'kubernetes-2',
+ },
+ },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-environment': 'POD',
+ });
+
+ expect(mapToDashboardViewModel(response)).toMatchObject({
+ dashboard: 'Dashboard Name',
+ links: [],
+ variables: {
+ pod: {
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
+ },
+ pod_2: {
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ },
+ });
+ });
+
+ it('merges variables from URL with the ones from yml file', () => {
+ const response = {
+ dashboard: 'Dashboard Name',
+ links: [],
+ templating: {
+ variables: {
+ pod: 'kubernetes',
+ pod_2: 'kubernetes-2',
+ },
+ },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-environment': 'POD',
+ 'var-pod': 'POD1',
+ 'var-pod_2': 'POD2',
+ });
+
+ expect(mapToDashboardViewModel(response)).toMatchObject({
+ dashboard: 'Dashboard Name',
+ links: [],
+ variables: {
+ pod: {
+ label: 'pod',
+ type: 'text',
+ value: 'POD1',
+ },
+ pod_2: {
+ label: 'pod_2',
+ type: 'text',
+ value: 'POD2',
+ },
+ },
+ });
+ });
+ });
});
describe('normalizeQueryResult', () => {
diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js
index c44bb957166..5164ed1b54b 100644
--- a/spec/frontend/monitoring/store/variable_mapping_spec.js
+++ b/spec/frontend/monitoring/store/variable_mapping_spec.js
@@ -1,4 +1,5 @@
-import { parseTemplatingVariables } from '~/monitoring/stores/variable_mapping';
+import { parseTemplatingVariables, mergeURLVariables } from '~/monitoring/stores/variable_mapping';
+import * as urlUtils from '~/lib/utils/url_utility';
import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
describe('parseTemplatingVariables', () => {
@@ -21,3 +22,73 @@ describe('parseTemplatingVariables', () => {
expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected);
});
});
+
+describe('mergeURLVariables', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'queryToObject');
+ });
+
+ afterEach(() => {
+ urlUtils.queryToObject.mockRestore();
+ });
+
+ it('returns empty object if variables are not defined in yml or URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ expect(mergeURLVariables({})).toEqual({});
+ });
+
+ it('returns empty object if variables are defined in URL but not in yml', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ });
+
+ expect(mergeURLVariables({})).toEqual({});
+ });
+
+ it('returns yml variables if variables defined in yml but not in the URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ const params = {
+ env: 'one',
+ instance: 'localhost',
+ };
+
+ expect(mergeURLVariables(params)).toEqual(params);
+ });
+
+ it('returns yml variables if variables defined in URL do not match with yml variables', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ };
+ const ymlParams = {
+ pod: { value: 'one' },
+ service: { value: 'database' },
+ };
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(ymlParams)).toEqual(ymlParams);
+ });
+
+ it('returns merged yml and URL variables if there is some match', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost:8080',
+ };
+ const ymlParams = {
+ instance: { value: 'localhost' },
+ service: { value: 'database' },
+ };
+
+ const merged = {
+ instance: { value: 'localhost:8080' },
+ service: { value: 'database' },
+ };
+
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(ymlParams)).toEqual(merged);
+ });
+});
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index aa5a4459a72..039cf275eea 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -169,8 +169,8 @@ describe('monitoring/utils', () => {
});
});
- describe('getPromCustomVariablesFromUrl', () => {
- const { getPromCustomVariablesFromUrl } = monitoringUtils;
+ describe('templatingVariablesFromUrl', () => {
+ const { templatingVariablesFromUrl } = monitoringUtils;
beforeEach(() => {
jest.spyOn(urlUtils, 'queryToObject');
@@ -195,7 +195,7 @@ describe('monitoring/utils', () => {
'var-pod': 'POD',
});
- expect(getPromCustomVariablesFromUrl()).toEqual(expect.objectContaining({ pod: 'POD' }));
+ expect(templatingVariablesFromUrl()).toEqual(expect.objectContaining({ pod: 'POD' }));
});
it('returns an empty object when no custom variables are present', () => {
@@ -203,7 +203,7 @@ describe('monitoring/utils', () => {
dashboard: '.gitlab/dashboards/custom_dashboard.yml',
});
- expect(getPromCustomVariablesFromUrl()).toStrictEqual({});
+ expect(templatingVariablesFromUrl()).toStrictEqual({});
});
});
@@ -427,76 +427,6 @@ describe('monitoring/utils', () => {
});
});
- describe('mergeURLVariables', () => {
- beforeEach(() => {
- jest.spyOn(urlUtils, 'queryToObject');
- });
-
- afterEach(() => {
- urlUtils.queryToObject.mockRestore();
- });
-
- it('returns empty object if variables are not defined in yml or URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
-
- expect(monitoringUtils.mergeURLVariables({})).toEqual({});
- });
-
- it('returns empty object if variables are defined in URL but not in yml', () => {
- urlUtils.queryToObject.mockReturnValueOnce({
- 'var-env': 'one',
- 'var-instance': 'localhost',
- });
-
- expect(monitoringUtils.mergeURLVariables({})).toEqual({});
- });
-
- it('returns yml variables if variables defined in yml but not in the URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
-
- const params = {
- env: 'one',
- instance: 'localhost',
- };
-
- expect(monitoringUtils.mergeURLVariables(params)).toEqual(params);
- });
-
- it('returns yml variables if variables defined in URL do not match with yml variables', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost',
- };
- const ymlParams = {
- pod: { value: 'one' },
- service: { value: 'database' },
- };
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
-
- expect(monitoringUtils.mergeURLVariables(ymlParams)).toEqual(ymlParams);
- });
-
- it('returns merged yml and URL variables if there is some match', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost:8080',
- };
- const ymlParams = {
- instance: { value: 'localhost' },
- service: { value: 'database' },
- };
-
- const merged = {
- instance: { value: 'localhost:8080' },
- service: { value: 'database' },
- };
-
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
-
- expect(monitoringUtils.mergeURLVariables(ymlParams)).toEqual(merged);
- });
- });
-
describe('convertVariablesForURL', () => {
it.each`
input | expected
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index f283ac100a9..9e5eab4fc6b 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -99,6 +99,8 @@ describe 'lograge', type: :request do
end
context 'with a log subscriber' do
+ include_context 'parsed logs'
+
let(:subscriber) { Lograge::LogSubscribers::ActionController.new }
let(:event) do
@@ -119,16 +121,6 @@ describe 'lograge', type: :request do
)
end
- let(:log_output) { StringIO.new }
- let(:logger) do
- Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
- end
- let(:log_data) { Gitlab::Json.parse(log_output.string) }
-
- before do
- Lograge.logger = logger
- end
-
describe 'with an exception' do
let(:exception) { RuntimeError.new('bad request') }
let(:backtrace) { caller }
diff --git a/spec/lib/api/entities/deploy_key_spec.rb b/spec/lib/api/entities/deploy_key_spec.rb
new file mode 100644
index 00000000000..704dabae63b
--- /dev/null
+++ b/spec/lib/api/entities/deploy_key_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::DeployKey do
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ let(:deploy_key) { create(:deploy_key, public: true) }
+ let(:entity) { described_class.new(deploy_key) }
+
+ it 'includes basic fields', :aggregate_failures do
+ is_expected.to include(
+ id: deploy_key.id,
+ title: deploy_key.title,
+ created_at: deploy_key.created_at,
+ expires_at: deploy_key.expires_at,
+ key: deploy_key.key
+ )
+ end
+ end
+end
diff --git a/spec/lib/api/entities/deploy_keys_project_spec.rb b/spec/lib/api/entities/deploy_keys_project_spec.rb
new file mode 100644
index 00000000000..a357467d7ce
--- /dev/null
+++ b/spec/lib/api/entities/deploy_keys_project_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::DeployKeysProject do
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ let(:deploy_keys_project) { create(:deploy_keys_project, :write_access) }
+ let(:entity) { described_class.new(deploy_keys_project) }
+
+ it 'includes basic fields', :aggregate_failures do
+ deploy_key = deploy_keys_project.deploy_key
+
+ is_expected.to include(
+ id: deploy_key.id,
+ title: deploy_key.title,
+ created_at: deploy_key.created_at,
+ expires_at: deploy_key.expires_at,
+ key: deploy_key.key,
+ can_push: deploy_keys_project.can_push
+ )
+ end
+ end
+end
diff --git a/spec/lib/api/entities/ssh_key_spec.rb b/spec/lib/api/entities/ssh_key_spec.rb
new file mode 100644
index 00000000000..25a0fecfb75
--- /dev/null
+++ b/spec/lib/api/entities/ssh_key_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::SSHKey do
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ let(:key) { create(:key, user: create(:user)) }
+ let(:entity) { described_class.new(key) }
+
+ it 'includes basic fields', :aggregate_failures do
+ is_expected.to include(
+ id: key.id,
+ title: key.title,
+ created_at: key.created_at,
+ expires_at: key.expires_at,
+ key: key.publishable_key
+ )
+ end
+ end
+end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 1baa18b53ce..e8cc6bc71ae 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -8,7 +8,7 @@ describe API::DeployKeys do
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id) }
let(:project2) { create(:project, creator_id: user.id) }
- let(:deploy_key) { create(:deploy_key, public: true, user: user) }
+ let(:deploy_key) { create(:deploy_key, public: true) }
let!(:deploy_keys_project) do
create(:deploy_keys_project, project: project, deploy_key: deploy_key)
@@ -40,32 +40,6 @@ describe API::DeployKeys do
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end
-
- it 'returns all deploy keys with comments replaced with'\
- 'a simple identifier of username + hostname' do
- get api('/deploy_keys', admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
-
- keys = json_response.map { |key_detail| key_detail['key'] }
- expect(keys).to all(include("#{user.name} (#{Gitlab.config.gitlab.host}"))
- end
-
- context 'N+1 queries' do
- before do
- get api('/deploy_keys', admin)
- end
-
- it 'avoids N+1 queries', :request_store do
- control_count = ActiveRecord::QueryRecorder.new { get api('/deploy_keys', admin) }.count
-
- create_list(:deploy_key, 2, public: true, user: create(:user))
-
- expect { get api('/deploy_keys', admin) }.not_to exceed_query_limit(control_count)
- end
- end
end
end
@@ -82,25 +56,6 @@ describe API::DeployKeys do
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
end
-
- context 'N+1 queries' do
- before do
- get api("/projects/#{project.id}/deploy_keys", admin)
- end
-
- it 'avoids N+1 queries', :request_store do
- control_count = ActiveRecord::QueryRecorder.new do
- get api("/projects/#{project.id}/deploy_keys", admin)
- end.count
-
- deploy_key = create(:deploy_key, user: create(:user))
- create(:deploy_keys_project, project: project, deploy_key: deploy_key)
-
- expect do
- get api("/projects/#{project.id}/deploy_keys", admin)
- end.not_to exceed_query_limit(control_count)
- end
- end
end
describe 'GET /projects/:id/deploy_keys/:key_id' do
@@ -111,13 +66,6 @@ describe API::DeployKeys do
expect(json_response['title']).to eq(deploy_key.title)
end
- it 'exposes key comment as a simple identifier of username + hostname' do
- get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['key']).to include("#{deploy_key.user_name} (#{Gitlab.config.gitlab.host})")
- end
-
it 'returns 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/deploy_keys/404", admin)
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index d860179f0a7..617587e2fa6 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -3,19 +3,14 @@
require 'spec_helper'
describe JwtController do
+ include_context 'parsed logs'
+
let(:service) { double(execute: {}) }
let(:service_class) { double(new: service) }
let(:service_name) { 'test' }
let(:parameters) { { service: service_name } }
- let(:log_output) { StringIO.new }
- let(:logger) do
- Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
- end
- let(:log_data) { Gitlab::Json.parse(log_output.string) }
before do
- Lograge.logger = logger
-
stub_const('JwtController::SERVICES', service_name => service_class)
end
diff --git a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
new file mode 100644
index 00000000000..020056da36e
--- /dev/null
+++ b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::PeriodicRecalculateService do
+ subject(:service) { described_class.new }
+
+ describe '#execute' do
+ let(:batch_size) { 2 }
+
+ let_it_be(:users) { create_list(:user, 4) }
+
+ before do
+ stub_const('AuthorizedProjectUpdate::PeriodicRecalculateService::BATCH_SIZE', batch_size)
+
+ User.delete([users[1], users[2]])
+ end
+
+ it 'calls AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker' do
+ (1..User.maximum(:id)).each_slice(batch_size).with_index do |batch, index|
+ delay = AuthorizedProjectUpdate::PeriodicRecalculateService::DELAY_INTERVAL * index
+
+ expect(AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker).to(
+ receive(:perform_in).with(delay, *batch.minmax))
+ end
+
+ service.execute
+ end
+ end
+end
diff --git a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
new file mode 100644
index 00000000000..28cbda6f4fd
--- /dev/null
+++ b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::RecalculateForUserRangeService do
+ describe '#execute' do
+ let_it_be(:users) { create_list(:user, 2) }
+
+ it 'calls Users::RefreshAuthorizedProjectsService' do
+ users.each do |user|
+ expect(Users::RefreshAuthorizedProjectsService).to(
+ receive(:new).with(user).and_call_original)
+ end
+
+ range = users.map(&:id).minmax
+ described_class.new(*range).execute
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/controllers/logging_shared_context.rb b/spec/support/shared_contexts/controllers/logging_shared_context.rb
new file mode 100644
index 00000000000..986a96f3a8d
--- /dev/null
+++ b/spec/support/shared_contexts/controllers/logging_shared_context.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# This context replaces the logger and exposes the `log_data` variable for
+# inspection
+RSpec.shared_context 'parsed logs' do
+ let(:logger) do
+ Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
+ end
+
+ let(:log_output) { StringIO.new }
+ let(:log_data) { Gitlab::Json.parse(log_output.string) }
+
+ around do |example|
+ initial_logger = Lograge.logger
+ Lograge.logger = logger
+
+ example.run
+
+ Lograge.logger = initial_logger
+ end
+end
diff --git a/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
new file mode 100644
index 00000000000..fcd073953b6
--- /dev/null
+++ b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::PeriodicRecalculateWorker do
+ describe '#perform' do
+ it 'calls AuthorizedProjectUpdate::PeriodicRecalculateService' do
+ expect_next_instance_of(AuthorizedProjectUpdate::PeriodicRecalculateService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform
+ end
+
+ context 'feature flag :periodic_project_authorization_recalculation is disabled' do
+ before do
+ stub_feature_flags(periodic_project_authorization_recalculation: false)
+ end
+
+ it 'does not call AuthorizedProjectUpdate::PeriodicRecalculateService' do
+ expect(AuthorizedProjectUpdate::PeriodicRecalculateService).not_to receive(:new)
+
+ subject.perform
+ end
+ end
+ end
+end
diff --git a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
new file mode 100644
index 00000000000..5d1c405dfd0
--- /dev/null
+++ b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do
+ let(:start_user_id) { 42 }
+ let(:end_user_id) { 4242 }
+
+ describe '#perform' do
+ it 'calls AuthorizedProjectUpdate::RecalculateForUserRangeService' do
+ expect_next_instance_of(AuthorizedProjectUpdate::RecalculateForUserRangeService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform(start_user_id, end_user_id)
+ end
+
+ context 'feature flag :periodic_project_authorization_recalculation is disabled' do
+ before do
+ stub_feature_flags(periodic_project_authorization_recalculation: false)
+ end
+
+ it 'does not call AuthorizedProjectUpdate::RecalculateForUserRangeService' do
+ expect(AuthorizedProjectUpdate::RecalculateForUserRangeService).not_to receive(:new)
+
+ subject.perform(start_user_id, end_user_id)
+ end
+ end
+ end
+end