New upstream version 13.1.1

This commit is contained in:
Pirate Praveen 2020-07-01 16:08:20 +05:30
parent 6158a767e6
commit 9028ee3161
48 changed files with 796 additions and 229 deletions

View file

@ -1,5 +1,159 @@
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 13.1.0 (2020-06-22)
### Security (1 change)
- Ensure passwords and access tokens don't appear in SCIM errors.
### Removed (2 changes)
- Revert Add scanner name. !33442
- Stop recording connection pool metrics for load balancing hosts. !33749
### Fixed (35 changes, 2 of them are from the community)
- Stop recording JSON parser errors due to third party license scanning integrations. !26944
- Improve Requirements Management empty state. !30716 (Marcin Sedlak-Jakubowski)
- Display warning for issues moved from service desk on frontend. !31803
- Add truncation for the environment dropdown. !32267
- Add negated params filter to epics search. !32296
- Clean up serialized objects in audit_events. !32434
- Prevent last Group Managed Account owner with access from accidental unlinking. !32473
- Fix issues with editing epic on Boards sidebar. !32503 (Eulyeon Ko)
- Dependency List shows only vulnerabilities from Gemnasium analyzer instead of all Dependency Scanning reports. !32560
- Ensure users can unlink Group SAML when the group has SAML disabled. !32655
- Fix Elasticsearch query error for ES 7.7. !32813
- Display epic issues with null relative position. !33105
- Fix description diff delete button not hidden after clicked. !33127
- Fix Elasticsearch illegal_argument_exception when searching for large files. !33176
- Support nuget dependencies in the API. !33389
- Fix GraphQL query to fetch vulnerable projects of subgroups. !33410
- Preserve order when applying scoped labels by title. !33420
- Revert breaking changes to allow conan_sources.tgz and conan_export.tgz files in the Conan repository. !33435
- Improve regex for geo auth keys checker. !33447
- Fix confidentiality note on epic's comment field to display the correct warning text. !33486
- Geo - Does not sync files on Object Storage when syncing object storage is enabled, but the Object Storage is disabled for the data type. !33561
- Fix creating merge requests with approval rules. !33582
- Specify group url in notification email. !33613
- Fix column overlap on long texts. !33614
- Geo - Does not try to unlink file when it not possible to get the file path while cleaning orphaned registries. !33658
- Geo: Fix synchronization disabled on primary UI. !33760
- Change the linkType argument for issueLinks query to enum. !33828
- Allow admins to see project despite of ip restriction set on a group level. !34086
- Return empty scans when head_pipeline is missing. !34193
- Render security dashboard for projects without pipeline available. !34219
- Include Epics in Roadmap whose parents are not part of Roadmap. !34243
- Geo - Fix Gitlab::Geo::Replication::BaseTransfer for orphaned registries. !34292
- Fix errors when pushing with an expired license. !34458
- Enable active class on group packages sidebar navigation items. !34518
- Fix creating/updating issues with epic_iid parameter on API. !34641
### Changed (18 changes)
- Update header for security config page and change to GlTable. !31471
- Display expiring subscription banner on more pages. !31497
- Remove license management from CI/CD settings. !31553
- Elasticsearch integration started using aliases instead of using indices directly. !32107
- Geo Form Validations. !32263
- Move Group-level IP address restriction feature to GitLab Premium. !32396
- Add link to Node URL. !32471
- Display Project and Subgroup milestones on Roadmap. !32496
- Enable CI Minutes-related Feature Flags by default. !32581
- Remove partial word matching from code search. !32771
- Add authentication information to usage ping. !32790
- Add tooltip with description to unknown severity badge icon. !33131
- Improve the clarity of the MR approvals tooltip UI. !33329
- Change approved to merged in Contribution Analytics. !33535
- Update merge train settings checkbox text. !34073
- Analytics Insights page: Load charts individually, instead of waiting for all data to load before displaying the page. !34290
- Add polling to metrics widget. !34314
- Added CI_HAS_OPEN_REQUIREMENTS environment variable. !34419
### Performance (11 changes)
- Fix N+1 queries for Elastic Web Search projects scope. !30346
- Fix N+1 queries for Elastic Search merge_requests scope. !30546
- Improve performance of Advanced Search API (Issues scope) by preloading more associations. !30778
- Update index_ci_builds_on_name_and_security_type_eq_ci_build index to support secret-detection. !31785
- Fix N+1 queries for Elastic Search projects scope. !32688
- Fix N+1 queries for Elastic Search milestones scope. !33327
- Geo - Make registry table SSOT for LFS objects. !33432
- Harden operations_dashboard_default_dashboard usage query. !33952
- Harden users_created usage data queries. !33956
- Harden security ci build job queries. !33966
- Save composer.json to package metadata. !34494
### Added (54 changes, 3 of them are from the community)
- Add list limit metric to API. !27324
- Implement Go module proxy MVC (package manager for Go). !27746 (Ethan Reesor)
- Improve Vulnerability Management with Standalone Vulnerabilities. !28212
- Add viewer and linker for go.mod and go.sum. !28262 (Ethan Reesor @firelizzard)
- Add usage statistics for modsecurity total packets/anomalous packets. !28535
- REST API membership responses for group owner enqueries include group managed account emails. !30584
- Add table to Issues Analytics. !30603
- Add ability to pause and resume Elasticsearch indexing. !30621
- Show the status of stand-alone secrets analyzer on the configuration page. !31167
- Support transferring and displaying image uploads on Status Page. !31269
- Add Pipeline.securityReportSummary to GraphQL. !31550
- Create test reports table. !31643
- Show usage graph for each storage type. !31649
- Add requirements filtering on author username and search by title. !31857
- Add ability to download patch from vulnerability page. !32000
- Add callout for user count threshold. !32404
- Make group/namespace name in email a link to group overview page. !32461
- Add ability to select Epic while creating a New Issue. !32572
- Expose test reports on GraphQL. !32599
- Allow approval rules to be reset to project defaults. !32657
- Save setting for auto-fix feature. !32690
- Geo - Make Geo::RegistryConsistencyWorker clean up unused registries. !32695
- Add policy for auto_fix. !32783
- Send email notifications on Issue Iteration change. !32817
- Add support for bulk editing health status and epic on issues. !32875
- Add quick actions for iterations in issues. !32904
- Add Elasticsearch to Sidekiq ServerMetrics. !32937
- Added CI parser for requirements reports. !33031
- Add state events to burnup chart data. !33048
- Introduce `userNotesCount` field for VulnerabilityType on GraphQL API. !33058
- Add project audit events API. !33155 (Julien Millau)
- Introduce `issueLinks` field for VulnerabilityType on GraphQL API. !33173
- Add Elasticsearch metrics in Rack middleware. !33233
- Adds NuGet project and license URL to the package details page. !33268
- Adds a new Dependencies tab for NuGet packages on the package details page. !33303
- Allow to specify multiple domains when restricting group membership by email domain. !33498
- Add MR approval stats to group contribution analytics. !33601
- Create sticky section in security dashboard layout. !33651
- Add Network Policy Management to the Threat Monitoring page. !33667
- Show secret_detection in report types. !33682
- Adds NuGet package icon to package details page. !33701
- Ability to make PAT expiration optional in self managed instances. !33783
- Add secret detection for GraphQL API. !33797
- Show test report status badge on Requirements list. !33848
- Persist user preferences for boards. !33892
- Fixes inconsistent package title icon colors. !33933
- Retry failed vulnerability export background jobs. !33986
- Add MR note to standalone vulnerability page. !34146
- Show issue link on security dashboard when vulnerability has an issue. !34157
- Geo Package Files - Sync Counts. !34205
- Upgrade to `license_scanning` report v2.1. !34224
- Display Saved User Lists by Feature Flags. !34294
- Add csv export button to group security dashboard. !34374
- Alert Users That Lists Are Modified By API Only. !34559
### Other (9 changes, 2 of them are from the community)
- Update deprecated Vue 3 slot syntax in ee/app/assets/javascripts/vue_shared/security_reports/components/modal.vue. !31966 (Gilang Gumilar)
- Added DB index on confidential epics column. !32443
- Allow ci minutes reset service to continue in case of failure. !32867
- Remove unused index for vulnerabiliy confidence levels. !33149
- Update vulnerabilities badge design in Dependency List. !33417
- Add geo_primary_replication_events to usage data. !33424
- Add new status page attributes to usage ping. !33790
- Remove optimized_elasticsearch_indexes_project feature flag. !33965
- Relocate Go models. !34338 (Ethan Reesor (@firelizzard))
## 13.0.6 (2020-06-10) ## 13.0.6 (2020-06-10)
### Security (1 change) ### Security (1 change)

View file

@ -2,6 +2,20 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 13.1.1 (2020-06-23)
### Fixed (4 changes)
- Fix missing templating vars set from URL in metrics dashboard. !34668
- Fix edit status dropdown overflow. !34847
- Load user before logging git http-requests. !34923
- Do not mask key comments for DeployKeys. !35014
### Added (1 change)
- Periodically recompute project authorizations. !34071
## 13.1.0 (2020-06-22) ## 13.1.0 (2020-06-22)
### Removed (4 changes, 2 of them are from the community) ### Removed (4 changes, 2 of them are from the community)

View file

@ -1 +1 @@
203182ffe94da165d4ff81332b1b3fff9771e631 13.1.1

View file

@ -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'

View file

@ -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
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)

View file

@ -1 +1 @@
13.1.0 13.1.1

View file

@ -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 = ({
}) => { }) => {
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),
}; };

View file

@ -1,4 +1,5 @@
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 = {} } = {}) =>
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 {};

View file

@ -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 = []) =>
{}, {},
); );
/**
* 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 {};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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?

View file

@ -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 @@
: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:

View file

@ -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

View file

@ -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

View file

@ -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({})

View file

@ -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.
![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

View file

@ -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

View file

@ -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

View file

@ -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
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View file

@ -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

View file

@ -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
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
# 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
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
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

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module API
module Entities
class DeployKey < Entities::SSHKey
expose :key
end
end
end

View file

@ -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

View file

@ -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

View file

@ -40,8 +40,8 @@
"@babel/plugin-syntax-import-meta": "^7.10.1", "@babel/plugin-syntax-import-meta": "^7.10.1",
"@babel/preset-env": "^7.10.1", "@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5", "@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.139.0", "@gitlab/svgs": "1.140.0",
"@gitlab/ui": "16.12.1", "@gitlab/ui": "17.0.1",
"@gitlab/visual-review-tools": "1.6.1", "@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1", "@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2", "@sentry/browser": "^5.10.2",

View file

@ -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

View file

@ -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', () => {
}); });
}); });
}); });
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', () => {

View file

@ -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'; import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
describe('parseTemplatingVariables', () => { 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);
});
});

View file

@ -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', () => {
'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', () => {
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', () => {
}); });
}); });
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

View file

@ -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
) )
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 }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
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
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
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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -835,15 +835,15 @@
eslint-plugin-vue "^6.2.1" eslint-plugin-vue "^6.2.1"
vue-eslint-parser "^7.0.0" vue-eslint-parser "^7.0.0"
"@gitlab/svgs@1.139.0": "@gitlab/svgs@1.140.0":
version "1.139.0" version "1.140.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.139.0.tgz#8a4874e76000e2dd7d3ed3a8967d62bed47d7ea7" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.140.0.tgz#593f1f65b0df57c3399fcfb9f472f59aa64da074"
integrity sha512-o1KAmQLYL727HodlPHkmj+d+Kdw8OIgHzlKmmPYMzeE+As2l1oz6CTilca56KqXGklOgrouC9P2puMwyX8e/6g== integrity sha512-6gANJGi2QkpvOgFTMcY3SIwEqhO69i6R3jU4BSskkVziwDdAWxGonln22a4Iu//Iv0NrsFDpAA0jIVfnJzw0iA==
"@gitlab/ui@16.12.1": "@gitlab/ui@17.0.1":
version "16.12.1" version "17.0.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-16.12.1.tgz#4d6865308596b09e36961210df7a8a489aaadb6d" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.0.1.tgz#daf036dfdc095f94123c80c3fb1ab5fe4dcbf95b"
integrity sha512-jF6/I71Q0mjHetIRDO8O4VO2KIGWKL/yH2Mdb/CqQKaEasgnc/YpuyHGCsBXqDPxCjRbXPeKp0EywICQx4agZA== integrity sha512-JSUGruV6oploADF0Sc0BBY43Des3utU9iWCnR8BAmttKFXFFNUKwTf908yZPGJtfnVyjJkVioOCOYkvUZ0jngg==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"