Commit 6b96d119 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@13-1-stable-ee

parent 8b7c4494
...@@ -416,7 +416,7 @@ end ...@@ -416,7 +416,7 @@ end
gem 'octokit', '~> 4.15' gem 'octokit', '~> 4.15'
# https://gitlab.com/gitlab-org/gitlab/issues/207207 # 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 'email_reply_trimmer', '~> 0.1'
gem 'html2text' gem 'html2text'
......
...@@ -390,7 +390,7 @@ GEM ...@@ -390,7 +390,7 @@ GEM
opentracing (~> 0.4) opentracing (~> 0.4)
redis (> 3.0.0, < 5.0.0) redis (> 3.0.0, < 5.0.0)
gitlab-license (1.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-markup (1.7.1)
gitlab-net-dns (0.9.1) gitlab-net-dns (0.9.1)
gitlab-puma (4.3.3.gitlab.2) gitlab-puma (4.3.3.gitlab.2)
...@@ -1241,7 +1241,7 @@ DEPENDENCIES ...@@ -1241,7 +1241,7 @@ DEPENDENCIES
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0) gitlab-labkit (= 0.12.0)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.4) gitlab-mail_room (~> 0.0.6)
gitlab-markup (~> 1.7.1) gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1) gitlab-net-dns (~> 0.9.1)
gitlab-puma (~> 4.3.3.gitlab.2) gitlab-puma (~> 4.3.3.gitlab.2)
......
...@@ -2,8 +2,8 @@ import { slugify } from '~/lib/utils/text_utility'; ...@@ -2,8 +2,8 @@ import { slugify } from '~/lib/utils/text_utility';
import createGqClient, { fetchPolicies } from '~/lib/graphql'; import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { parseTemplatingVariables } from './variable_mapping';
import { NOT_IN_DB_PREFIX, linkTypes } from '../constants'; import { NOT_IN_DB_PREFIX, linkTypes } from '../constants';
import { mergeURLVariables, parseTemplatingVariables } from './variable_mapping';
import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants'; import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants';
import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range'; import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range';
import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility'; import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility';
...@@ -289,7 +289,7 @@ export const mapToDashboardViewModel = ({ ...@@ -289,7 +289,7 @@ export const mapToDashboardViewModel = ({
}) => { }) => {
return { return {
dashboard, dashboard,
variables: parseTemplatingVariables(templating), variables: mergeURLVariables(parseTemplatingVariables(templating)),
links: links.map(mapLinksToViewModel), links: links.map(mapLinksToViewModel),
panelGroups: panel_groups.map(mapToPanelGroupViewModel), panelGroups: panel_groups.map(mapToPanelGroupViewModel),
}; };
......
import { isString } from 'lodash'; import { isString } from 'lodash';
import { templatingVariablesFromUrl } from '../utils';
import { VARIABLE_TYPES } from '../constants'; import { VARIABLE_TYPES } from '../constants';
/** /**
...@@ -164,4 +165,39 @@ export const parseTemplatingVariables = ({ variables = {} } = {}) => ...@@ -164,4 +165,39 @@ export const parseTemplatingVariables = ({ variables = {} } = {}) =>
return acc; 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 {}; export default {};
...@@ -170,11 +170,10 @@ export const convertVariablesForURL = variables => ...@@ -170,11 +170,10 @@ export const convertVariablesForURL = variables =>
* begin with a constant prefix so that it doesn't collide with * begin with a constant prefix so that it doesn't collide with
* other URL params. * other URL params.
* *
* @param {String} New URL * @param {String} search URL
* @returns {Object} The custom variables defined by the user in the URL * @returns {Object} The custom variables defined by the user in the URL
*/ */
export const templatingVariablesFromUrl = (search = window.location.search) => {
export const getPromCustomVariablesFromUrl = (search = window.location.search) => {
const params = queryToObject(search); const params = queryToObject(search);
// pick the params with variable prefix // pick the params with variable prefix
const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX)); const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX));
...@@ -353,39 +352,4 @@ export const barChartsDataParser = (data = []) => ...@@ -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 {}; export default {};
...@@ -18,8 +18,7 @@ module Repositories ...@@ -18,8 +18,7 @@ module Repositories
skip_around_action :set_session_storage skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
before_action :parse_repo_path prepend_before_action :authenticate_user, :parse_repo_path
before_action :authenticate_user
private private
......
# 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
# 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
...@@ -85,8 +85,6 @@ module Users ...@@ -85,8 +85,6 @@ module Users
# remove - The IDs of the authorization rows to remove. # remove - The IDs of the authorization rows to remove.
# add - Rows to insert in the form `[user id, project id, access level]` # add - Rows to insert in the form `[user id, project id, access level]`
def update_authorizations(remove = [], add = []) def update_authorizations(remove = [], add = [])
return if remove.empty? && add.empty?
User.transaction do User.transaction do
user.remove_project_authorizations(remove) unless remove.empty? user.remove_project_authorizations(remove) unless remove.empty?
ProjectAuthorization.insert_authorizations(add) unless add.empty? ProjectAuthorization.insert_authorizations(add) unless add.empty?
......
...@@ -11,6 +11,14 @@ ...@@ -11,6 +11,14 @@
:weight: 1 :weight: 1
:idempotent: true :idempotent: true
:tags: [] :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 - :name: authorized_project_update:authorized_project_update_user_refresh_with_low_urgency
:feature_category: :authentication_and_authorization :feature_category: :authentication_and_authorization
:has_external_dependencies: :has_external_dependencies:
...@@ -99,6 +107,14 @@ ...@@ -99,6 +107,14 @@
:weight: 1 :weight: 1
:idempotent: :idempotent:
:tags: [] :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 - :name: cronjob:ci_archive_traces_cron
:feature_category: :continuous_integration :feature_category: :continuous_integration
:has_external_dependencies: :has_external_dependencies:
......
# 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
# 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
---
title: Periodically recompute project authorizations
merge_request: 34071
author:
type: added
---
title: Do not mask key comments for DeployKeys
merge_request: 35014
author:
type: fixed
---
title: Load user before logging git http-requests
merge_request: 34923
author:
type: fixed
---
title: Fix missing templating vars set from URL in metrics dashboard
merge_request: 34668
author:
type: fixed
...@@ -499,6 +499,9 @@ Settings.cron_jobs['x509_issuer_crl_check_worker']['job_class'] = 'X509IssuerCrl ...@@ -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'] ||= Settingslogic.new({})
Settings.cron_jobs['users_create_statistics_worker']['cron'] ||= '2 15 * * *' Settings.cron_jobs['users_create_statistics_worker']['cron'] ||= '2 15 * * *'
Settings.cron_jobs['users_create_statistics_worker']['job_class'] = 'Users::CreateStatisticsWorker' 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 Gitlab.ee do
Settings.cron_jobs['adjourned_group_deletion_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['adjourned_group_deletion_worker'] ||= Settingslogic.new({})
......
...@@ -491,7 +491,10 @@ introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/issues/25381). ...@@ -491,7 +491,10 @@ introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/issues/25381).
### Batch Suggestions ### 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 You can apply multiple suggestions at once to reduce the number of commits added
to your branch to address your reviewers' requests. to your branch to address your reviewers' requests.
...@@ -512,6 +515,27 @@ 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") ![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 ## Start a thread by replying to a standard comment
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9 > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9
......
...@@ -80,8 +80,9 @@ The default syntax theme is White, and you can choose among 5 different themes: ...@@ -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 [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). 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), 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
which applies to the entire Web IDE screen. the [solarized dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/219228),
which apply to the entire Web IDE screen.
## Behavior ## Behavior
......
...@@ -181,17 +181,23 @@ so that everyone involved can participate in the discussion. ...@@ -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. > [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 Discussion threads can be resolved on Designs.
or unresolved by clicking the **Resolve thread** icon at the first comment of the
discussion.
![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. 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:
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) ![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 ## Referring to designs in Markdown
......
...@@ -7,6 +7,8 @@ description: "The static site editor enables users to edit content on static web ...@@ -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. > - [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. > - 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:** DANGER: **Danger:**
In GitLab 13.0, we [introduced breaking changes](https://gitlab.com/gitlab-org/gitlab/-/issues/213282) 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 ...@@ -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 content of the pages without prior knowledge of Git nor of your site's
codebase. 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 ### Use the Static Site Editor to edit your content
For instance, suppose you are a recently hired technical writer at a large For instance, suppose you are a recently hired technical writer at a large
......
...@@ -45,17 +45,19 @@ Single file editing is based on the [Ace Editor](https://ace.c9.io). ...@@ -45,17 +45,19 @@ Single file editing is based on the [Ace Editor](https://ace.c9.io).
### Themes ### 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. 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). 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), 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
which applies to the entire Web IDE screen. 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 | Solarized Dark Theme | Dark Theme |
|---------------------------------------------------------------|-----------------------------------------| |---------------------------------------------------------------|-------------------------------------------------------------|-----------------------------------------|
| ![Solarized Light Theme](img/solarized_light_theme_v13.0.png) | ![Dark Theme](img/dark_theme_v13.0.png) | | ![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 ## Configure the Web IDE
......
...@@ -25,8 +25,7 @@ module API ...@@ -25,8 +25,7 @@ module API
get "deploy_keys" do get "deploy_keys" do
authenticated_as_admin! authenticated_as_admin!
deploy_keys = DeployKey.all.preload_users present paginate(DeployKey.all), with: Entities::DeployKey
present paginate(deploy_keys), with: Entities::SSHKey
end end
params do params do
...@@ -43,7 +42,7 @@ module API ...@@ -43,7 +42,7 @@ module API
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ":id/deploy_keys" do 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 present paginate(keys), with: Entities::DeployKeysProject
end end
...@@ -105,7 +104,7 @@ module API ...@@ -105,7 +104,7 @@ module API
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
desc 'Update an existing deploy key for a project' do desc 'Update an existing deploy key for a project' do
success Entities::SSHKey success Entities::DeployKey
end end
params do params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key' requires :key_id, type: Integer, desc: 'The ID of the deploy key'
...@@ -140,7 +139,7 @@ module API ...@@ -140,7 +139,7 @@ module API
desc 'Enable a deploy key for a project' do desc 'Enable a deploy key for a project' do
detail 'This feature was added in GitLab 8.11' detail 'This feature was added in GitLab 8.11'
success Entities::SSHKey success Entities::DeployKey
end end
params do params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key' requires :key_id, type: Integer, desc: 'The ID of the deploy key'
...@@ -150,7 +149,7 @@ module API ...@@ -150,7 +149,7 @@ module API
current_user, declared_params).execute current_user, declared_params).execute
if key if key
present key, with: Entities::SSHKey present key, with: Entities::DeployKey
else else
not_found!('Deploy Key') not_found!('Deploy Key')
end end
......
# frozen_string_literal: true
module API
module Entities
class DeployKey < Entities::SSHKey
expose :key
end
end
end
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
module API module API
module Entities module Entities
class DeployKeyWithUser < Entities::SSHKeyWithUser class DeployKeyWithUser < Entities::DeployKey
expose :user, using: Entities::UserPublic
expose :deploy_keys_projects expose :deploy_keys_projects
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module API module API
module Entities module Entities
class DeployKeysProject < Grape::Entity class DeployKeysProject < Grape::Entity
expose :deploy_key, merge: true, using: Entities::SSHKey expose :deploy_key, merge: true, using: Entities::DeployKey
expose :can_push expose :can_push
end end
end end
......
...@@ -60,10 +60,21 @@ RSpec.describe Repositories::GitHttpController do ...@@ -60,10 +60,21 @@ RSpec.describe Repositories::GitHttpController do
get :info_refs, params: params get :info_refs, params: params
end 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 end
context 'with exceptions' do context 'with exceptions' do
before do before do
allow(controller).to receive(:authenticate_user).and_return(true)
allow(controller).to receive(:verify_workhorse_api!).and_return(true) allow(controller).to receive(:verify_workhorse_api!).and_return(true)
end end
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
convertToGrafanaTimeRange, convertToGrafanaTimeRange,
addDashboardMetaDataToLink, addDashboardMetaDataToLink,
} from '~/monitoring/stores/utils'; } from '~/monitoring/stores/utils';
import * as urlUtils from '~/lib/utils/url_utility';
import { annotationsData } from '../mock_data'; import { annotationsData } from '../mock_data';
import { NOT_IN_DB_PREFIX } from '~/monitoring/constants'; import { NOT_IN_DB_PREFIX } from '~/monitoring/constants';
...@@ -398,6 +399,118 @@ describe('mapToDashboardViewModel', () => { ...@@ -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', () => { describe('normalizeQueryResult', () => {
......
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'; import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
describe('parseTemplatingVariables', () => { describe('parseTemplatingVariables', () => {
...@@ -21,3 +22,73 @@ describe('parseTemplatingVariables', () => { ...@@ -21,3 +22,73 @@ describe('parseTemplatingVariables', () => {
expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected); 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);
});
});
...@@ -169,8 +169,8 @@ describe('monitoring/utils', () => { ...@@ -169,8 +169,8 @@ describe('monitoring/utils', () => {
}); });
}); });
describe('getPromCustomVariablesFromUrl', () => { describe('templatingVariablesFromUrl', () => {
const { getPromCustomVariablesFromUrl } = monitoringUtils; const { templatingVariablesFromUrl } = monitoringUtils;
beforeEach(() => { beforeEach(() => {
jest.spyOn(urlUtils, 'queryToObject'); jest.spyOn(urlUtils, 'queryToObject');
...@@ -195,7 +195,7 @@ describe('monitoring/utils', () => { ...@@ -195,7 +195,7 @@ describe('monitoring/utils', () => {
'var-pod': 'POD', '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', () => { it('returns an empty object when no custom variables are present', () => {
...@@ -203,7 +203,7 @@ describe('monitoring/utils', () => { ...@@ -203,7 +203,7 @@ describe('monitoring/utils', () => {
dashboard: '.gitlab/dashboards/custom_dashboard.yml', dashboard: '.gitlab/dashboards/custom_dashboard.yml',
}); });
expect(getPromCustomVariablesFromUrl()).toStrictEqual({}); expect(templatingVariablesFromUrl()).toStrictEqual({});
}); });
}); });
...@@ -427,76 +427,6 @@ describe('monitoring/utils', () => { ...@@ -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', () => { describe('convertVariablesForURL', () => {
it.each` it.each`
input | expected input | expected
......
...@@ -99,6 +99,8 @@ describe 'lograge', type: :request do ...@@ -99,6 +99,8 @@ describe 'lograge', type: :request do
end end
context 'with a log subscriber' do context 'with a log subscriber' do
include_context 'parsed logs'
let(:subscriber) { Lograge::LogSubscribers::ActionController.new } let(:subscriber) { Lograge::LogSubscribers::ActionController.new }
let(:event) do let(:event) do
...@@ -119,16 +121,6 @@ describe 'lograge', type: :request do ...@@ -119,16 +121,6 @@ describe 'lograge', type: :request do
) )
end 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 describe 'with an exception' do
let(:exception) { RuntimeError.new('bad request') } let(:exception) { RuntimeError.new('bad request') }
let(:backtrace) { caller } let(:backtrace) { caller }
......
# 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
# 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
# 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
...@@ -8,7 +8,7 @@ describe API::DeployKeys do ...@@ -8,7 +8,7 @@ describe API::DeployKeys do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id) } let(:project) { create(:project, creator_id: user.id) }
let(:project2) { 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 let!(:deploy_keys_project) do
create(:deploy_keys_project, project: project, deploy_key: deploy_key) create(:deploy_keys_project, project: project, deploy_key: deploy_key)
...@@ -40,32 +40,6 @@ describe API::DeployKeys do ...@@ -40,32 +40,6 @@ describe API::DeployKeys do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id) expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end 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
end end
...@@ -82,25 +56,6 @@ describe API::DeployKeys do ...@@ -82,25 +56,6 @@ describe API::DeployKeys do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title) expect(json_response.first['title']).to eq(deploy_key.title)
end 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 end
describe 'GET /projects/:id/deploy_keys/:key_id' do describe 'GET /projects/:id/deploy_keys/:key_id' do
...@@ -111,13 +66,6 @@ describe API::DeployKeys do ...@@ -111,13 +66,6 @@ describe API::DeployKeys do
expect(json_response['title']).to eq(deploy_key.title) expect(json_response['title']).to eq(deploy_key.title)
end 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 it 'returns 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/deploy_keys/404", admin) get api("/projects/#{project.id}/deploy_keys/404", admin)
......
...@@ -3,19 +3,14 @@ ...@@ -3,19 +3,14 @@
require 'spec_helper' require 'spec_helper'
describe JwtController do describe JwtController do
include_context 'parsed logs'
let(:service) { double(execute: {}) } let(:service) { double(execute: {}) }
let(:service_class) { double(new: service) } let(:service_class) { double(new: service) }
let(:service_name) { 'test' } let(:service_name) { 'test' }
let(:parameters) { { service: service_name } } 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 before do
Lograge.logger = logger
stub_const('JwtController::SERVICES', service_name => service_class) stub_const('JwtController::SERVICES', service_name => service_class)
end end
......
# 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
# 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
# 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
# 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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment