New upstream version 13.1.1
This commit is contained in:
parent
6158a767e6
commit
9028ee3161
48 changed files with 796 additions and 229 deletions
154
CHANGELOG-EE.md
154
CHANGELOG-EE.md
|
@ -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)
|
||||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -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)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
203182ffe94da165d4ff81332b1b3fff9771e631
|
13.1.1
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -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
|
||||||
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)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
13.1.0
|
13.1.1
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 @@
|
||||||
: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:
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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).
|
||||||
|
|
||||||
### 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
|
||||||
|
|
|
@ -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,18 +181,24 @@ 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.
|
There are two ways to resolve/unresolve a Design thread:
|
||||||
|
|
||||||
|
1. You can mark a thread as resolved or unresolved by clicking the checkmark icon for **Resolve thread** in the top-right corner of the first comment of the discussion:
|
||||||
|
|
||||||
![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
|
![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
|
||||||
|
|
||||||
Pinned comments can also be resolved or unresolved in their threads.
|
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
|
When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
|
||||||
the thread once published.
|
the thread once published:
|
||||||
|
|
||||||
![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
|
![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
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217160) in **GitLab 13.1**.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217160) in **GitLab 13.1**.
|
||||||
|
|
|
@ -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 |
BIN
doc/user/project/web_ide/img/dark_theme_v13_0.png
Normal file
BIN
doc/user/project/web_ide/img/dark_theme_v13_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
BIN
doc/user/project/web_ide/img/solarized_dark_theme_v13_1.png
Normal file
BIN
doc/user/project/web_ide/img/solarized_dark_theme_v13_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
Before Width: | Height: | Size: 772 KiB |
BIN
doc/user/project/web_ide/img/solarized_light_theme_v13_0.png
Normal file
BIN
doc/user/project/web_ide/img/solarized_light_theme_v13_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 190 KiB |
|
@ -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
|
||||||
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
|
||||||
|
|
9
lib/api/entities/deploy_key.rb
Normal file
9
lib/api/entities/deploy_key.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module API
|
||||||
|
module Entities
|
||||||
|
class DeployKey < Entities::SSHKey
|
||||||
|
expose :key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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 @@
|
||||||
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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
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', () => {
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
22
spec/lib/api/entities/deploy_key_spec.rb
Normal file
22
spec/lib/api/entities/deploy_key_spec.rb
Normal 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
|
25
spec/lib/api/entities/deploy_keys_project_spec.rb
Normal file
25
spec/lib/api/entities/deploy_keys_project_spec.rb
Normal 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
|
22
spec/lib/api/entities/ssh_key_spec.rb
Normal file
22
spec/lib/api/entities/ssh_key_spec.rb
Normal 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
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
16
yarn.lock
16
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue