Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-13 09:08:37 +00:00
parent 2c15256184
commit 0e65189f85
90 changed files with 1219 additions and 229 deletions

View File

@ -45,6 +45,7 @@
"DevOps",
"Elasticsearch",
"Facebook",
"GDK",
"Git LFS",
"git-annex",
"Git",

View File

@ -1,5 +1,7 @@
<script>
import * as Sentry from '@sentry/browser';
import {
GlAlert,
GlLoadingIcon,
GlNewDropdown,
GlNewDropdownItem,
@ -19,10 +21,14 @@ export default {
resolved: s__('AlertManagement|Resolved'),
},
i18n: {
errorMsg: s__(
'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.',
),
fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'),
overviewTitle: s__('AlertManagement|Overview'),
},
components: {
GlAlert,
GlLoadingIcon,
GlNewDropdown,
GlNewDropdownItem,
@ -58,20 +64,35 @@ export default {
update(data) {
return data?.project?.alertManagementAlerts?.nodes?.[0] ?? null;
},
error(error) {
this.errored = true;
Sentry.captureException(error);
},
},
},
data() {
return { alert: null };
return { alert: null, errored: false, isErrorDismissed: false };
},
computed: {
loading() {
return this.$apollo.queries.alert.loading;
},
showErrorMsg() {
return this.errored && !this.isErrorDismissed;
},
},
methods: {
dismissError() {
this.isErrorDismissed = true;
},
},
};
</script>
<template>
<div>
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
{{ $options.i18n.errorMsg }}
</gl-alert>
<div v-if="loading"><gl-loading-icon size="lg" class="mt-3" /></div>
<div
v-if="alert"

View File

@ -46,6 +46,7 @@ export default {
'isGroup',
'maskableRegex',
'selectedEnvironment',
'isProtectedByDefault',
]),
canSubmit() {
return (
@ -123,6 +124,7 @@ export default {
'addWildCardScope',
'resetSelectedEnvironment',
'setSelectedEnvironment',
'setVariableProtected',
]),
deleteVarAndClose() {
this.deleteVariable(this.variableBeingEdited);
@ -147,6 +149,11 @@ export default {
}
this.hideModal();
},
setVariableProtectedByDefault() {
if (this.isProtectedByDefault && !this.variableBeingEdited) {
this.setVariableProtected();
}
},
},
};
</script>
@ -159,6 +166,7 @@ export default {
static
lazy
@hidden="resetModalHandler"
@shown="setVariableProtectedByDefault"
>
<form>
<ci-key-field

View File

@ -5,14 +5,16 @@ import { parseBoolean } from '~/lib/utils/common_utils';
export default () => {
const el = document.getElementById('js-ci-project-variables');
const { endpoint, projectId, group, maskableRegex } = el.dataset;
const { endpoint, projectId, group, maskableRegex, protectedByDefault } = el.dataset;
const isGroup = parseBoolean(group);
const isProtectedByDefault = parseBoolean(protectedByDefault);
const store = createStore({
endpoint,
projectId,
isGroup,
maskableRegex,
isProtectedByDefault,
});
return new Vue({

View File

@ -20,6 +20,10 @@ export const resetEditing = ({ commit, dispatch }) => {
commit(types.RESET_EDITING);
};
export const setVariableProtected = ({ commit }) => {
commit(types.SET_VARIABLE_PROTECTED);
};
export const requestAddVariable = ({ commit }) => {
commit(types.REQUEST_ADD_VARIABLE);
};

View File

@ -2,6 +2,7 @@ export const TOGGLE_VALUES = 'TOGGLE_VALUES';
export const VARIABLE_BEING_EDITED = 'VARIABLE_BEING_EDITED';
export const RESET_EDITING = 'RESET_EDITING';
export const CLEAR_MODAL = 'CLEAR_MODAL';
export const SET_VARIABLE_PROTECTED = 'SET_VARIABLE_PROTECTED';
export const REQUEST_VARIABLES = 'REQUEST_VARIABLES';
export const RECEIVE_VARIABLES_SUCCESS = 'RECEIVE_VARIABLES_SUCCESS';

View File

@ -104,4 +104,8 @@ export default {
[types.SET_SELECTED_ENVIRONMENT](state, environment) {
state.selectedEnvironment = environment;
},
[types.SET_VARIABLE_PROTECTED](state) {
state.variable.protected = true;
},
};

View File

@ -5,6 +5,7 @@ export default () => ({
projectId: null,
isGroup: null,
maskableRegex: null,
isProtectedByDefault: null,
isLoading: false,
isDeleting: false,
variable: {

View File

@ -3,7 +3,16 @@ import mitt from 'mitt';
export default () => {
const emitter = mitt();
emitter.once = (event, handler) => {
const wrappedHandler = evt => {
handler(evt);
emitter.off(event, wrappedHandler);
};
emitter.on(event, wrappedHandler);
};
emitter.$on = emitter.on;
emitter.$once = emitter.once;
emitter.$off = emitter.off;
emitter.$emit = emitter.emit;

View File

@ -0,0 +1,63 @@
<script>
import { GlAlert } from '@gitlab/ui';
import getJiraImportDetailsQuery from '~/jira_import/queries/get_jira_import_details.query.graphql';
import { isInProgress } from '~/jira_import/utils';
export default {
name: 'IssuableListRoot',
components: {
GlAlert,
},
props: {
canEdit: {
type: Boolean,
required: true,
},
isJiraConfigured: {
type: Boolean,
required: true,
},
projectPath: {
type: String,
required: true,
},
},
data() {
return {
isAlertShowing: true,
};
},
apollo: {
jiraImport: {
query: getJiraImportDetailsQuery,
variables() {
return {
fullPath: this.projectPath,
};
},
update: ({ project }) => ({
isInProgress: isInProgress(project.jiraImportStatus),
}),
skip() {
return !this.isJiraConfigured || !this.canEdit;
},
},
},
computed: {
shouldShowAlert() {
return this.isAlertShowing && this.jiraImport?.isInProgress;
},
},
methods: {
hideAlert() {
this.isAlertShowing = false;
},
},
};
</script>
<template>
<gl-alert v-if="shouldShowAlert" @dismiss="hideAlert">
{{ __('Import in progress. Refresh page to see newly added issues.') }}
</gl-alert>
</template>

View File

@ -1,24 +1,62 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import IssuableListRootApp from './components/issuable_list_root_app.vue';
import IssuablesListApp from './components/issuables_list_app.vue';
export default function initIssuablesList() {
if (!gon.features || !gon.features.vueIssuablesList) {
function mountIssuableListRootApp() {
const el = document.querySelector('.js-projects-issues-root');
if (!el) {
return false;
}
Vue.use(VueApollo);
const defaultClient = createDefaultClient();
const apolloProvider = new VueApollo({
defaultClient,
});
return new Vue({
el,
apolloProvider,
render(createComponent) {
return createComponent(IssuableListRootApp, {
props: {
canEdit: parseBoolean(el.dataset.canEdit),
isJiraConfigured: parseBoolean(el.dataset.isJiraConfigured),
projectPath: el.dataset.projectPath,
},
});
},
});
}
function mountIssuablesListApp() {
if (!gon.features?.vueIssuablesList) {
return;
}
document.querySelectorAll('.js-issuables-list').forEach(el => {
const { canBulkEdit, ...data } = el.dataset;
const props = {
...data,
canBulkEdit: Boolean(canBulkEdit),
};
return new Vue({
el,
render(createElement) {
return createElement(IssuablesListApp, { props });
return createElement(IssuablesListApp, {
props: {
...data,
canBulkEdit: Boolean(canBulkEdit),
},
});
},
});
});
}
export default function initIssuablesList() {
mountIssuableListRootApp();
mountIssuablesListApp();
}

View File

@ -295,7 +295,7 @@ export default {
.then(res => res.data)
.then(data => this.checkForSpam(data))
.then(data => {
if (window.location.pathname !== data.web_url) {
if (!window.location.pathname.includes(data.web_url)) {
visitUrl(data.web_url);
}
})

View File

@ -7,6 +7,7 @@ import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
import initIssuablesList from '~/issuables_list';
import initManualOrdering from '~/manual_ordering';
document.addEventListener('DOMContentLoaded', () => {
@ -16,9 +17,11 @@ document.addEventListener('DOMContentLoaded', () => {
page: FILTERED_SEARCH.ISSUES,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new ShortcutsNavigation();
new UsersSelect();
initManualOrdering();
initIssuablesList();
});

View File

@ -14,7 +14,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts, :coverage_reports, :terraform_reports]
before_action :authorize_read_actual_head_pipeline!, only: [
:test_reports,
:exposed_artifacts,
:coverage_reports,
:terraform_reports,
:accessibility_reports
]
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
@ -136,6 +142,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
reports_response(@merge_request.compare_test_reports)
end
def accessibility_reports
if @merge_request.has_accessibility_reports?
reports_response(@merge_request.compare_accessibility_reports)
else
head :no_content
end
end
def coverage_reports
if @merge_request.has_coverage_reports?
reports_response(@merge_request.find_coverage_reports)

View File

@ -49,6 +49,15 @@ module Types
field :mode, type: GraphQL::STRING_TYPE,
description: 'Blob mode',
null: true
field :external_storage, type: GraphQL::STRING_TYPE,
description: 'Blob external storage',
null: true
field :rendered_as_text, type: GraphQL::BOOLEAN_TYPE,
description: 'Shows whether the blob is rendered as text',
method: :rendered_as_text?,
null: false
end
# rubocop: enable Graphql/AuthorizeTypes
end

View File

@ -4,12 +4,14 @@ module EventsHelper
ICON_NAMES_BY_EVENT_TYPE = {
'pushed to' => 'commit',
'pushed new' => 'commit',
'updated' => 'commit',
'created' => 'status_open',
'opened' => 'status_open',
'closed' => 'status_closed',
'accepted' => 'fork',
'commented on' => 'comment',
'deleted' => 'remove',
'destroyed' => 'remove',
'imported' => 'import',
'joined' => 'users'
}.freeze

View File

@ -525,12 +525,14 @@ class Project < ApplicationRecord
def self.public_or_visible_to_user(user = nil, min_access_level = nil)
min_access_level = nil if user&.admin?
if user
return public_to_user unless user
if user.is_a?(DeployToken)
user.projects
else
where('EXISTS (?) OR projects.visibility_level IN (?)',
user.authorizations_for_projects(min_access_level: min_access_level),
Gitlab::VisibilityLevel.levels_for_user(user))
else
public_to_user
end
end

View File

@ -249,15 +249,12 @@ class User < ApplicationRecord
enum layout: { fixed: 0, fluid: 1 }
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8 }
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: { readme: 0, activity: 1, files: 2 }
# User's role
# Note: When adding an option, it MUST go on the end of the array.
enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true
delegate :path, to: :namespace, allow_nil: true, prefix: true

View File

@ -1,8 +1,15 @@
# frozen_string_literal: true
# Include this module if we want to pass something else than the user to
# check policies. This defines several methods which the policy checker
# would call and check.
# Include this module to have an object respond to user messages without being
# a user.
#
# Use Case 1:
# Pass something else than the user to check policies. This defines several
# methods which the policy checker would call and check.
#
# Use Case 2:
# Access the API with non-user object such as deploy tokens. This defines
# several methods which the API auth flow would call.
module PolicyActor
extend ActiveSupport::Concern
@ -37,6 +44,30 @@ module PolicyActor
def alert_bot?
false
end
def deactivated?
false
end
def confirmation_required_on_sign_in?
false
end
def can?(action, subject = :global)
Ability.allowed?(self, action, subject)
end
def preferred_language
nil
end
def requires_ldap_check?
false
end
def try_obtain_ldap_lease
nil
end
end
PolicyActor.prepend_if_ee('EE::PolicyActor')

View File

@ -84,6 +84,16 @@ class ProjectPolicy < BasePolicy
project.merge_requests_allowing_push_to_user(user).any?
end
desc "Deploy token with read_package_registry scope"
condition(:read_package_registry_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry
end
desc "Deploy token with write_package_registry scope"
condition(:write_package_registry_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_package_registry
end
with_scope :subject
condition(:forking_allowed) do
@subject.feature_available?(:forking, @user)
@ -532,6 +542,16 @@ class ProjectPolicy < BasePolicy
prevent :destroy_design
end
rule { read_package_registry_deploy_token }.policy do
enable :read_package
enable :read_project
end
rule { write_package_registry_deploy_token }.policy do
enable :create_package
enable :read_project
end
private
def team_member?

View File

@ -103,17 +103,19 @@ module Auth
return unless requested_project
actions = actions.select do |action|
authorized_actions = actions.select do |action|
can_access?(requested_project, action)
end
return unless actions.present?
log_if_actions_denied(type, requested_project, actions, authorized_actions)
return unless authorized_actions.present?
# At this point user/build is already authenticated.
#
ensure_container_repository!(path, actions)
ensure_container_repository!(path, authorized_actions)
{ type: type, name: path.to_s, actions: actions }
{ type: type, name: path.to_s, actions: authorized_actions }
end
##
@ -222,5 +224,22 @@ module Auth
REGISTRY_LOGIN_ABILITIES.include?(ability)
end
end
def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions)
return if requested_actions == authorized_actions
log_info = {
message: "Denied container registry permissions",
scope_type: type,
requested_project_path: requested_project.full_path,
requested_actions: requested_actions,
authorized_actions: authorized_actions,
username: current_user&.username,
user_id: current_user&.id,
project_path: project&.full_path
}.compact
Gitlab::AuthLogger.warn(log_info)
end
end
end

View File

@ -8,7 +8,7 @@
- if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true)
- is_group = !@group.nil?
#js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex} }
#js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} }
- else
.row

View File

@ -6,6 +6,11 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
- if @project.jira_issues_import_feature_flag_enabled?
.js-projects-issues-root{ data: { can_edit: can?(current_user, :admin_project, @project).to_s,
is_jira_configured: @project.jira_service.present?.to_s,
project_path: @project.full_path } }
- if project_issues(@project).exists?
.top-area
= render 'shared/issuable/nav', type: :issues

View File

@ -0,0 +1,5 @@
---
title: Deploy token authentication for API with Maven endpoints
merge_request: 30332
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add alert on project issues page to show Jira import is in progress
merge_request: 31329
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Remove ruby_memory_bytes metric, duplicate of ruby_process_resident_memory_bytes
merge_request: 31705
author:
type: removed

View File

@ -0,0 +1,6 @@
---
title: Fixes bug where variables were not protected by default when using the correct
CI/CD admin setting
merge_request: 31655
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add Activity icons for Wiki updated and destroyed events
merge_request: 30349
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add fields to GraphQL snippet blob type
merge_request: 31710
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Log when container registry permissions are denied
merge_request: 31536
author:
type: other

View File

@ -14,6 +14,7 @@ resources :merge_requests, concerns: :awardable, except: [:new, :create, :show],
post :rebase
get :test_reports
get :exposed_artifacts
get :accessibility_reports
get :coverage_reports
get :terraform_reports

View File

@ -183,7 +183,6 @@ Some basic Ruby runtime metrics are available:
| `ruby_gc_duration_seconds` | Counter | 11.1 | Time spent by Ruby in GC |
| `ruby_gc_stat_...` | Gauge | 11.1 | Various metrics from [GC.stat](https://ruby-doc.org/core-2.6.5/GC.html#method-c-stat) |
| `ruby_file_descriptors` | Gauge | 11.1 | File descriptors per process |
| `ruby_memory_bytes` | Gauge | 11.1 | Memory usage by process |
| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |
| `ruby_process_max_fds` | Gauge | 12.0 | Maximum number of open file descriptors per process |

View File

@ -9399,6 +9399,11 @@ type SnippetBlob {
"""
binary: Boolean!
"""
Blob external storage
"""
externalStorage: String
"""
Blob mode
"""
@ -9424,6 +9429,11 @@ type SnippetBlob {
"""
rawPath: String!
"""
Shows whether the blob is rendered as text
"""
renderedAsText: Boolean!
"""
Blob highlighted data
"""

View File

@ -27882,6 +27882,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "externalStorage",
"description": "Blob external storage",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "mode",
"description": "Blob mode",
@ -27956,6 +27970,24 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "renderedAsText",
"description": "Shows whether the blob is rendered as text",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "richData",
"description": "Blob highlighted data",

View File

@ -1392,11 +1392,13 @@ Represents the snippet blob
| Name | Type | Description |
| --- | ---- | ---------- |
| `binary` | Boolean! | Shows whether the blob is binary |
| `externalStorage` | String | Blob external storage |
| `mode` | String | Blob mode |
| `name` | String | Blob name |
| `path` | String | Blob path |
| `plainData` | String | Blob plain highlighted data |
| `rawPath` | String! | Blob raw content endpoint path |
| `renderedAsText` | Boolean! | Shows whether the blob is rendered as text |
| `richData` | String | Blob highlighted data |
| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |

View File

@ -1067,7 +1067,9 @@ The only clauses currently available are:
Keywords such as `branches` or `refs` that are currently available for
`only`/`except` are not yet available in `rules` as they are being individually
considered for their usage and behavior in this context.
considered for their usage and behavior in this context. Future keyword improvements
are being discussed in our [epic for improving `rules`](https://gitlab.com/groups/gitlab-org/-/epics/2783),
where anyone can add suggestions or requests.
#### Permitted attributes

View File

@ -3,35 +3,40 @@
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is easy for everyone.
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/community/contribute/).
For a first-time step-by-step guide to the contribution process, see our
[Contributing to GitLab](https://about.gitlab.com/community/contribute/) page.
Looking for something to work on? Look for issues with the label [`Accepting merge requests`](#i-want-to-contribute).
Looking for something to work on? Look for issues with the label
[`~Accepting merge requests`](#how-to-contribute).
GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
abbreviation.
GitLab comes in two flavors:
To get an overview of GitLab community membership including those that would be reviewing or merging your contributions, please visit [the community roles page](community_roles.md).
- GitLab Community Edition (CE), our free and open source edition.
- GitLab Enterprise Edition (EE), which is our commercial edition.
Throughout this guide you will see references to CE and EE for abbreviation.
To get an overview of GitLab community membership, including those that would review or merge
your contributions, visit [the community roles page](community_roles.md).
If you want to know how the GitLab [core team](https://about.gitlab.com/community/core-team/)
operates please see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab/blob/master/PROCESS.md).
operates, see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab/blob/master/PROCESS.md).
[GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
GitLab Inc engineers should refer to the [engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/).
## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to
Report suspected security vulnerabilities in private to
`support@gitlab.com`, also see the
[disclosure section on the GitLab.com website](https://about.gitlab.com/security/disclosure/).
Please do **NOT** create publicly viewable issues for suspected security
vulnerabilities.
DANGER: **Danger:**
Do **NOT** create publicly viewable issues for suspected security vulnerabilities.
## Code of conduct
We want to create a welcoming environment for everyone who is interested in contributing.
Please visit our [Code of Conduct page](https://about.gitlab.com/community/contribute/code-of-conduct/) to learn more about our commitment to an open and welcoming environment.
Visit our [Code of Conduct page](https://about.gitlab.com/community/contribute/code-of-conduct/) to learn more about our commitment to an open and welcoming environment.
## Closing policy for issues and merge requests
@ -40,39 +45,56 @@ and merge requests is limited. Out of respect for our volunteers, issues and
merge requests not in line with the guidelines listed in this document may be
closed without notice.
Please treat our volunteers with courtesy and respect, it will go a long way
Treat our volunteers with courtesy and respect, it will go a long way
towards getting your issue resolved.
Issues and merge requests should be in English and contain appropriate language
for audiences of all ages.
If a contributor is no longer actively working on a submitted merge request
we can decide that the merge request will be finished by one of our
[Merge request coaches](https://about.gitlab.com/company/team/) or close the merge request. We make this decision
based on how important the change is for our product vision. If a merge request
coach is going to finish the merge request we assign the
~"coach will finish" label. When a team member picks up a community contribution,
If a contributor is no longer actively working on a submitted merge request,
we can:
- Decide that the merge request will be finished by one of our
[Merge request coaches](https://about.gitlab.com/company/team/).
- Close the merge request.
We make this decision based on how important the change is for our product vision. If a merge
request coach is going to finish the merge request, we assign the
`~coach will finish` label.
When a team member picks up a community contribution,
we credit the original author by adding a changelog entry crediting the author
and optionally include the original author on at least one of the commits
within the MR.
## Helping others
Please help other GitLab users when you can.
The methods people will use to seek help can be found on the [getting help page](https://about.gitlab.com/get-help/).
Help other GitLab users when you can.
The methods people use to seek help can be found on the [getting help page](https://about.gitlab.com/get-help/).
Sign up for the mailing list, answer GitLab questions on StackOverflow or
respond in the IRC channel.
Sign up for the mailing list, answer GitLab questions on StackOverflow or respond in the IRC channel.
## I want to contribute
## How to contribute
If you want to contribute to GitLab,
[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
[issues with the `~Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
are a great place to start.
If you have any questions or need help visit [Getting Help](https://about.gitlab.com/get-help/) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
learn how to communicate with GitLab. We have a [Gitter channel for contributors](https://gitter.im/gitlab/contributors),
however we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication.
Thanks for your contribution!
### GitLab Development Kit
The GitLab Development Kit (GDK) helps contributors run a local GitLab instance with all the
required dependencies. It can be used to test changes to GitLab and related projects before raising
a Merge Request.
For more information, see the [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit)
project.
## Contribution Flow
@ -92,7 +114,7 @@ When submitting code to GitLab, you may feel that your contribution requires the
When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
## Issues workflow
### Issues workflow
This [documentation](issue_workflow.md) outlines the current issue workflow:
@ -105,7 +127,7 @@ This [documentation](issue_workflow.md) outlines the current issue workflow:
- [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
- [Technical debt in follow-up issues](issue_workflow.md#technical-debt-in-follow-up-issues)
## Merge requests workflow
### Merge requests workflow
This [documentation](merge_request_workflow.md) outlines the current merge request process.
@ -120,13 +142,15 @@ This [documentation](style_guides.md) outlines the current style guidelines.
## Implement design & UI elements
This [design documentation](design.md) outlines the current process for implementing
design & UI elements.
This [design documentation](design.md) outlines the current process for implementing design and UI
elements.
## Contribute documentation
For information on how to contribute documentation, see GitLab
[documentation guidelines](../documentation/index.md).
## Getting an Enterprise Edition License
If you need a license for contributing to an EE-feature, please [follow these instructions](https://about.gitlab.com/handbook/marketing/community-relations/code-contributor-program/#for-contributors-to-the-gitlab-enterprise-edition-ee).
---
[Return to Development documentation](../README.md)
If you need a license for contributing to an EE-feature, see
[relevant information](https://about.gitlab.com/handbook/marketing/community-relations/code-contributor-program/#for-contributors-to-the-gitlab-enterprise-edition-ee).

View File

@ -20,7 +20,7 @@ Merge requests should be submitted to the appropriate project at GitLab.com, for
[Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests), etc.
If you are new to GitLab development (or web development in general), see the
[I want to contribute!](index.md#i-want-to-contribute) section to get started with
[how to contribute](index.md#how-to-contribute) section to get started with
some potentially easy issues.
To start developing GitLab, download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit)

View File

@ -16,7 +16,7 @@ Component's computed properties / methods or external helpers.
**Why?**
`$on` and `$off` methods [are removed](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md) from the Vue instance, so in Vue 3 it can't be used to create an event hub.
`$on`, `$once`, and `$off` methods [are removed](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md) from the Vue instance, so in Vue 3 it can't be used to create an event hub.
**What to use instead**
@ -55,7 +55,7 @@ import createEventHub from '~/helpers/event_hub_factory';
export default createEventHub();
```
Event hubs created with the factory expose the same methods as Vue 2 event hubs (`$on`, `$off` and
Event hubs created with the factory expose the same methods as Vue 2 event hubs (`$on`, `$once`, `$off` and
`$emit`), making them backward compatible with our previous approach.
## <template functional>

View File

@ -318,19 +318,41 @@ stub_feature_flags(ci_live_trace: false)
Feature.enabled?(:ci_live_trace) # => false
```
If you wish to set up a test where a feature flag is disabled for some
actors and not others, you can specify this in options passed to the
helper. For example, to disable the `ci_live_trace` feature flag for a
specifc project:
If you wish to set up a test where a feature flag is enabled only
for some actors and not others, you can specify this in options
passed to the helper. For example, to enable the `ci_live_trace`
feature flag for a specifc project:
```ruby
project1, project2 = build_list(:project, 2)
# Feature will only be disabled for project1
stub_feature_flags(ci_live_trace: { enabled: false, thing: project1 })
# Feature will only be enabled for project1
stub_feature_flags(ci_live_trace: project1)
Feature.enabled?(:ci_live_trace, project1) # => false
Feature.enabled?(:ci_live_trace, project2) # => true
Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, project1) # => true
Feature.enabled?(:ci_live_trace, project2) # => false
```
This represents an actual behavior of FlipperGate:
1. You can enable an override for a specified actor to be enabled
1. You can disable (remove) an override for a specified actor,
fallbacking to default state
1. There's no way to model that you explicitly disable a specified actor
```ruby
Feature.enable(:my_feature)
Feature.disable(:my_feature, project1)
Feature.enabled?(:my_feature) # => true
Feature.enabled?(:my_feature, project1) # => true
```
```ruby
Feature.disable(:my_feature2)
Feature.enable(:my_feature2, project1)
Feature.enabled?(:my_feature2) # => false
Feature.enabled?(:my_feature2, project1) # => true
```
### Pristine test environments

View File

@ -404,6 +404,9 @@ As of GitLab 10.0, the supported buildpacks are:
- buildpack-nginx v8
```
If your application needs a buildpack that is not in the above list, you
might want to use a [custom buildpack](customize.md#custom-buildpacks).
## Limitations
The following restrictions apply.

View File

@ -207,9 +207,9 @@ Enter a project name or hit enter to use the directory name as project name.
The next step is to add the GitLab Package Registry as a Maven remote. If a
project is private or you want to upload Maven artifacts to GitLab,
credentials will need to be provided for authorization too. Support is available
for [personal access tokens](#authenticating-with-a-personal-access-token) and
[CI job tokens](#authenticating-with-a-ci-job-token) only.
[Deploy tokens](../../project/deploy_tokens/index.md) and regular username/password
for [personal access tokens](#authenticating-with-a-personal-access-token),
[CI job tokens](#authenticating-with-a-ci-job-token), and
[deploy tokens](../../project/deploy_tokens/index.md) only. Regular username/password
credentials do not work.
### Authenticating with a personal access token
@ -324,6 +324,59 @@ repositories {
}
```
### Authenticating with a deploy token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0.
To authenticate with a [deploy token](./../../project/deploy_tokens/index.md),
set the scope to `api` when creating one, and add it to your Maven or Gradle configuration
files.
#### Authenticating with a deploy token in Maven
Add a corresponding section to your
[`settings.xml`](https://maven.apache.org/settings.html) file:
```xml
<settings>
<servers>
<server>
<id>gitlab-maven</id>
<configuration>
<httpHeaders>
<property>
<name>Deploy-Token</name>
<value>REPLACE_WITH_YOUR_DEPLOY_TOKEN</value>
</property>
</httpHeaders>
</configuration>
</server>
</servers>
</settings>
```
#### Authenticating with a deploy token in Gradle
To authenticate with a deploy token, add a repositories section to your
[`build.gradle`](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html)
file:
```groovy
repositories {
maven {
url "https://<gitlab-url>/api/v4/groups/<group>/-/packages/maven"
name "GitLab"
credentials(HttpHeaderCredentials) {
name = 'Deploy-Token'
value = '<deploy-token>'
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
```
## Configuring your project to use the GitLab Maven repository URL
To download and upload packages from GitLab, you need a `repository` and
@ -397,7 +450,7 @@ project's ID can be used for uploading.
### Group level Maven endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8798) in GitLab Premium 11.7.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8798) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
If you rely on many packages, it might be inefficient to include the `repository` section
with a unique URL for each package. Instead, you can use the group level endpoint for
@ -460,7 +513,7 @@ For retrieving artifacts, you can use either the
### Instance level Maven endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8274) in GitLab Premium 11.7.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8274) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
If you rely on many packages, it might be inefficient to include the `repository` section
with a unique URL for each package. Instead, you can use the instance level endpoint for

View File

@ -65,7 +65,8 @@ module API
end
def find_user_from_sources
find_user_from_access_token ||
deploy_token_from_request ||
find_user_from_access_token ||
find_user_from_job_token ||
find_user_from_warden
end
@ -90,12 +91,16 @@ module API
end
def api_access_allowed?(user)
Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
user_allowed_or_deploy_token?(user) && user.can?(:access_api)
end
def api_access_denied_message(user)
Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
end
def user_allowed_or_deploy_token?(user)
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
end
end
class_methods do

View File

@ -25,6 +25,7 @@ module Gitlab
PRIVATE_TOKEN_PARAM = :private_token
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
JOB_TOKEN_PARAM = :job_token
DEPLOY_TOKEN_HEADER = 'HTTP_DEPLOY_TOKEN'.freeze
RUNNER_TOKEN_PARAM = :token
RUNNER_JOB_TOKEN_PARAM = :token
@ -101,6 +102,16 @@ module Gitlab
access_token.user || raise(UnauthorizedError)
end
# This returns a deploy token, not a user since a deploy token does not
# belong to a user.
def deploy_token_from_request
return unless route_authentication_setting[:deploy_token_allowed]
token = current_request.env[DEPLOY_TOKEN_HEADER].presence
DeployToken.active.find_by_token(token)
end
def find_runner_from_token
return unless api_request?

View File

@ -31,7 +31,7 @@ module Gitlab
end
def sample_memory_usage
add_metric('memory_usage', value: System.memory_usage)
add_metric('memory_usage', value: System.memory_usage_rss)
end
def sample_file_descriptors

View File

@ -35,7 +35,6 @@ module Gitlab
def init_metrics
metrics = {
file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels),
memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used (RSS)', labels),
process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used (RSS)', labels),
@ -87,9 +86,7 @@ module Gitlab
end
def set_memory_usage_metrics
memory_rss = System.memory_usage
metrics[:memory_bytes].set(labels, memory_rss)
metrics[:process_resident_memory_bytes].set(labels, memory_rss)
metrics[:process_resident_memory_bytes].set(labels, System.memory_usage_rss)
if Gitlab::Utils.to_boolean(ENV['enable_memory_uss_pss'])
memory_uss_pss = System.memory_usage_uss_pss

View File

@ -18,7 +18,7 @@ module Gitlab
MAX_OPEN_FILES_PATTERN = /Max open files\s*(?<value>\d+)/.freeze
# Returns the current process' RSS (resident set size) in bytes.
def self.memory_usage
def self.memory_usage_rss
sum_matches(PROC_STATUS_PATH, rss: RSS_PATTERN)[:rss].kilobytes
end

View File

@ -55,13 +55,13 @@ module Gitlab
def run
Thread.current[THREAD_KEY] = self
@memory_before = System.memory_usage
@memory_before = System.memory_usage_rss
@started_at = System.monotonic_time
@thread_cputime_start = System.thread_cpu_time
yield
ensure
@memory_after = System.memory_usage
@memory_after = System.memory_usage_rss
@finished_at = System.monotonic_time
self.class.gitlab_transaction_cputime_seconds.observe(labels, thread_cpu_duration)

View File

@ -36,7 +36,7 @@ module Gitlab
gc_stats: gc_stats,
time_to_finish: time_to_finish,
number_of_sql_calls: sql_calls_count,
memory_usage: "#{Gitlab::Metrics::System.memory_usage.to_f / 1024 / 1024} MiB",
memory_usage: "#{Gitlab::Metrics::System.memory_usage_rss.to_f / 1024 / 1024} MiB",
label: ::Prometheus::PidProvider.worker_id
)

View File

@ -1792,6 +1792,9 @@ msgstr ""
msgid "AlertManagement|Surface alerts in GitLab"
msgstr ""
msgid "AlertManagement|There was an error displaying the alert. Please refresh the page to try again."
msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
@ -11291,6 +11294,9 @@ msgstr ""
msgid "Import in progress"
msgstr ""
msgid "Import in progress. Refresh page to see newly added issues."
msgstr ""
msgid "Import issues"
msgstr ""

View File

@ -27,7 +27,7 @@ describe Groups::Settings::IntegrationsController do
context 'when group_level_integrations not enabled' do
it 'returns not_found' do
stub_feature_flags(group_level_integrations: { enabled: false, thing: group })
stub_feature_flags(group_level_integrations: false)
get :index, params: { group_id: group }
@ -60,7 +60,7 @@ describe Groups::Settings::IntegrationsController do
context 'when group_level_integrations not enabled' do
it 'returns not_found' do
stub_feature_flags(group_level_integrations: { enabled: false, thing: group })
stub_feature_flags(group_level_integrations: false)
get :edit, params: { group_id: group, id: Service.available_services_names.sample }

View File

@ -15,7 +15,7 @@ describe Groups::Settings::RepositoryController do
describe 'POST create_deploy_token' do
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
before do
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: group })
stub_feature_flags(ajax_new_deploy_token: false)
entity.add_owner(user)
end

View File

@ -1341,6 +1341,141 @@ describe Projects::MergeRequestsController do
end
end
describe 'GET accessibility_reports' do
let(:merge_request) do
create(:merge_request,
:with_diffs,
:with_merge_request_pipeline,
target_project: project,
source_project: project
)
end
let(:pipeline) do
create(:ci_pipeline,
:success,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
before do
allow_any_instance_of(MergeRequest)
.to receive(:compare_accessibility_reports)
.and_return(accessibility_comparison)
allow_any_instance_of(MergeRequest)
.to receive(:actual_head_pipeline)
.and_return(pipeline)
end
subject do
get :accessibility_reports, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid
},
format: :json
end
context 'permissions on a public project with private CI/CD' do
let(:project) { create(:project, :repository, :public, :builds_private) }
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
context 'while signed out' do
before do
sign_out(user)
end
it 'responds with a 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to be_blank
end
end
context 'while signed in as an unrelated user' do
before do
sign_in(create(:user))
end
it 'responds with a 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to be_blank
end
end
end
context 'when feature flag is disabled' do
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
before do
stub_feature_flags(accessibility_report_view: false)
end
it 'returns 204 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when pipeline has jobs with accessibility reports' do
before do
allow_any_instance_of(MergeRequest)
.to receive(:has_accessibility_reports?)
.and_return(true)
end
context 'when processing accessibility reports is in progress' do
let(:accessibility_comparison) { { status: :parsing } }
it 'sends polling interval' do
expect(Gitlab::PollingInterval).to receive(:set_header)
subject
end
it 'returns 204 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when processing accessibility reports is completed' do
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
it 'returns accessibility reports' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ 'summary' => 1 })
end
end
context 'when user created corrupted accessibility reports' do
let(:accessibility_comparison) { { status: :error, status_reason: 'This merge request does not have accessibility reports' } }
it 'does not send polling interval' do
expect(Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 400 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq({ 'status_reason' => 'This merge request does not have accessibility reports' })
end
end
end
end
describe 'POST remove_wip' do
before do
merge_request.title = merge_request.wip_title

View File

@ -878,7 +878,7 @@ describe Projects::PipelinesController do
context 'when junit_pipeline_screenshots_view is enabled' do
before do
stub_feature_flags(junit_pipeline_screenshots_view: { enabled: true, thing: project })
stub_feature_flags(junit_pipeline_screenshots_view: project)
end
context 'when test_report contains attachment and scope is with_attachment as a URL param' do
@ -907,7 +907,7 @@ describe Projects::PipelinesController do
context 'when junit_pipeline_screenshots_view is disabled' do
before do
stub_feature_flags(junit_pipeline_screenshots_view: { enabled: false, thing: project })
stub_feature_flags(junit_pipeline_screenshots_view: false)
end
context 'when test_report contains attachment and scope is with_attachment as a URL param' do

View File

@ -36,7 +36,7 @@ describe Projects::Settings::RepositoryController do
describe 'POST create_deploy_token' do
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
before do
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
stub_feature_flags(ajax_new_deploy_token: false)
end
it_behaves_like 'a created deploy token' do

View File

@ -27,7 +27,7 @@ describe 'Explore Groups', :js do
end
before do
stub_feature_flags({ vue_issuables_list: { enabled: false, thing: group } })
stub_feature_flags(vue_issuables_list: false)
end
shared_examples 'renders public and internal projects' do

View File

@ -12,7 +12,7 @@ describe 'Group issues page' do
let(:path) { issues_group_path(group) }
before do
stub_feature_flags({ vue_issuables_list: { enabled: false, thing: group } })
stub_feature_flags(vue_issuables_list: false)
end
context 'with shared examples' do

View File

@ -20,7 +20,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
end
before do
stub_feature_flags(web_ide_default: false, single_mr_diff_view: { enabled: false, thing: target_project }, code_navigation: false)
stub_feature_flags(web_ide_default: false, single_mr_diff_view: false, code_navigation: false)
target_project.add_maintainer(user)
sign_in(user)

View File

@ -10,7 +10,7 @@ describe 'Batch diffs', :js do
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'empty-branch') }
before do
stub_feature_flags(single_mr_diff_view: { enabled: true, thing: project })
stub_feature_flags(single_mr_diff_view: project)
stub_feature_flags(diffs_batch_load: true)
sign_in(project.owner)

View File

@ -30,7 +30,7 @@ describe 'Projects > Settings > Repository settings' do
before do
stub_container_registry_config(enabled: true)
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
stub_feature_flags(ajax_new_deploy_token: project)
visit project_settings_repository_path(project)
end
@ -225,7 +225,7 @@ describe 'Projects > Settings > Repository settings' do
# Removal: https://gitlab.com/gitlab-org/gitlab/-/issues/208828
context 'with the `keep_divergent_refs` feature flag disabled' do
before do
stub_feature_flags(keep_divergent_refs: { enabled: false, thing: project })
stub_feature_flags(keep_divergent_refs: false)
end
it 'hides the "Keep divergent refs" option' do

View File

@ -11,7 +11,7 @@ describe 'Repository Settings > User sees revoke deploy token modal', :js do
before do
project.add_role(user, role)
sign_in(user)
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
stub_feature_flags(ajax_new_deploy_token: project)
visit(project_settings_repository_path(project))
click_link('Revoke')
end

View File

@ -14,7 +14,7 @@ describe ForkProjectsFinder do
let(:private_fork_member) { create(:user) }
before do
stub_feature_flags(object_pools: { enabled: false, thing: source_project })
stub_feature_flags(object_pools: source_project)
private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
private_fork.add_developer(private_fork_member)

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import AlertDetails from '~/alert_management/components/alert_details.vue';
describe('AlertDetails', () => {
@ -7,7 +7,7 @@ describe('AlertDetails', () => {
const newIssuePath = 'root/alerts/-/issues/new';
function mountComponent({
alert = {},
data = { alert: {} },
createIssueFromAlertEnabled = false,
loading = false,
} = {}) {
@ -18,7 +18,7 @@ describe('AlertDetails', () => {
newIssuePath,
},
data() {
return { alert };
return data;
},
provide: {
glFeatures: { createIssueFromAlertEnabled },
@ -46,7 +46,7 @@ describe('AlertDetails', () => {
describe('Alert details', () => {
describe('when alert is null', () => {
beforeEach(() => {
mountComponent({ alert: null });
mountComponent({ data: { alert: null } });
});
it('shows an empty state', () => {
@ -102,5 +102,17 @@ describe('AlertDetails', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
});
describe('error state', () => {
it('displays a error state correctly', () => {
mountComponent({ data: { errored: true } });
expect(wrapper.find(GlAlert).exists()).toBe(true);
});
it('does not display an error when dismissed', () => {
mountComponent({ data: { errored: true, isErrorDismissed: true } });
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
});
});
});

View File

@ -96,6 +96,13 @@ describe('Ci variable modal', () => {
findModal().vm.$emit('hidden');
expect(store.dispatch).toHaveBeenCalledWith('clearModal');
});
it('should dispatch setVariableProtected when admin settings are configured to protect variables', () => {
store.state.isProtectedByDefault = true;
findModal().vm.$emit('shown');
expect(store.dispatch).toHaveBeenCalledWith('setVariableProtected');
});
});
describe('Editing a variable', () => {

View File

@ -75,6 +75,16 @@ describe('CI variable list store actions', () => {
});
});
describe('setVariableProtected', () => {
it('commits SET_VARIABLE_PROTECTED mutation', () => {
testAction(actions.setVariableProtected, {}, {}, [
{
type: types.SET_VARIABLE_PROTECTED,
},
]);
});
});
describe('deleteVariable', () => {
it('dispatch correct actions on successful deleted variable', done => {
mock.onPatch(state.endpoint).reply(200);

View File

@ -97,4 +97,12 @@ describe('CI variable list mutations', () => {
expect(stateCopy.environments).toEqual(['dev', 'production', 'staging']);
});
});
describe('SET_VARIABLE_PROTECTED', () => {
it('should set protected value to true', () => {
mutations[types.SET_VARIABLE_PROTECTED](stateCopy);
expect(stateCopy.variable.protected).toBe(true);
});
});
});

View File

@ -1,13 +1,4 @@
import createEventHub from '~/helpers/event_hub_factory';
import mitt from 'mitt';
jest.mock('mitt');
mitt.mockReturnValue({
on: () => {},
off: () => {},
emit: () => {},
});
describe('event bus factory', () => {
let eventBus;
@ -20,17 +11,84 @@ describe('event bus factory', () => {
eventBus = null;
});
it('creates an emitter', () => {
expect(mitt).toHaveBeenCalled();
describe('underlying module', () => {
let mitt;
beforeEach(() => {
jest.resetModules();
jest.mock('mitt');
// eslint-disable-next-line global-require
mitt = require('mitt');
mitt.mockReturnValue(() => ({}));
const createEventHubActual = jest.requireActual('~/helpers/event_hub_factory').default;
eventBus = createEventHubActual();
});
it('creates an emitter', () => {
expect(mitt).toHaveBeenCalled();
});
});
it.each`
method
${'on'}
${'off'}
${'emit'}
`('binds $$method to $method ', ({ method }) => {
expect(typeof eventBus[method]).toBe('function');
expect(eventBus[method]).toBe(eventBus[`$${method}`]);
describe('instance', () => {
it.each`
method
${'on'}
${'once'}
${'off'}
${'emit'}
`('binds $$method to $method ', ({ method }) => {
expect(typeof eventBus[method]).toBe('function');
expect(eventBus[method]).toBe(eventBus[`$${method}`]);
});
});
describe('once', () => {
const event = 'foobar';
let handler;
beforeEach(() => {
jest.spyOn(eventBus, 'on');
jest.spyOn(eventBus, 'off');
handler = jest.fn();
eventBus.once(event, handler);
});
it('calls on internally', () => {
expect(eventBus.on).toHaveBeenCalled();
});
it('calls handler when event is emitted', () => {
eventBus.emit(event);
expect(handler).toHaveBeenCalled();
});
it('calls off when event is emitted', () => {
eventBus.emit(event);
expect(eventBus.off).toHaveBeenCalled();
});
it('calls the handler only once when event is emitted multiple times', () => {
eventBus.emit(event);
eventBus.emit(event);
expect(handler).toHaveBeenCalledTimes(1);
});
describe('when the handler thows an error', () => {
beforeEach(() => {
handler = jest.fn().mockImplementation(() => {
throw new Error();
});
eventBus.once(event, handler);
});
it('calls off when event is emitted', () => {
expect(() => {
eventBus.emit(event);
}).toThrow();
expect(eventBus.off).toHaveBeenCalled();
});
});
});
});

View File

@ -0,0 +1,71 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import IssuableListRootApp from '~/issuables_list/components/issuable_list_root_app.vue';
const mountComponent = ({
canEdit = true,
isAlertShowing = true,
isInProgress = false,
isJiraConfigured = true,
} = {}) =>
shallowMount(IssuableListRootApp, {
propsData: {
canEdit,
isJiraConfigured,
projectPath: 'gitlab-org/gitlab-test',
},
data() {
return {
isAlertShowing,
jiraImport: {
isInProgress,
},
};
},
});
describe('IssuableListRootApp', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when Jira import is in progress', () => {
it('shows an alert that tells the user a Jira import is in progress', () => {
wrapper = mountComponent({
isInProgress: true,
});
expect(wrapper.find(GlAlert).text()).toBe(
'Import in progress. Refresh page to see newly added issues.',
);
});
});
describe('when Jira import is not in progress', () => {
it('does not show an alert', () => {
wrapper = mountComponent();
expect(wrapper.contains(GlAlert)).toBe(false);
});
});
describe('alert message', () => {
it('is hidden when dismissed', () => {
wrapper = mountComponent({
isInProgress: true,
});
expect(wrapper.contains(GlAlert)).toBe(true);
wrapper.find(GlAlert).vm.$emit('dismiss');
return Vue.nextTick(() => {
expect(wrapper.contains(GlAlert)).toBe(false);
});
});
});
});

View File

@ -221,6 +221,19 @@ describe('Issuable output', () => {
});
});
it('does not redirect if issue has not moved and user has switched tabs', () => {
jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
data: {
web_url: '',
confidential: vm.isConfidential,
},
});
return vm.updateIssuable().then(() => {
expect(visitUrl).not.toHaveBeenCalled();
});
});
it('redirects if returned web_url has changed', () => {
jest.spyOn(vm.service, 'updateIssuable').mockResolvedValue({
data: {

View File

@ -6,8 +6,22 @@ describe GitlabSchema.types['SnippetBlob'] do
it 'has the correct fields' do
expected_fields = [:rich_data, :plain_data,
:raw_path, :size, :binary, :name, :path,
:simple_viewer, :rich_viewer, :mode]
:simple_viewer, :rich_viewer, :mode, :external_storage,
:rendered_as_text]
expect(described_class).to have_graphql_fields(*expected_fields)
end
specify { expect(described_class.fields['richData'].type).not_to be_non_null }
specify { expect(described_class.fields['plainData'].type).not_to be_non_null }
specify { expect(described_class.fields['rawPath'].type).to be_non_null }
specify { expect(described_class.fields['size'].type).to be_non_null }
specify { expect(described_class.fields['binary'].type).to be_non_null }
specify { expect(described_class.fields['name'].type).not_to be_non_null }
specify { expect(described_class.fields['path'].type).not_to be_non_null }
specify { expect(described_class.fields['simpleViewer'].type).to be_non_null }
specify { expect(described_class.fields['richViewer'].type).not_to be_non_null }
specify { expect(described_class.fields['mode'].type).not_to be_non_null }
specify { expect(described_class.fields['externalStorage'].type).not_to be_non_null }
specify { expect(described_class.fields['renderedAsText'].type).to be_non_null }
end

View File

@ -17,6 +17,10 @@ describe Gitlab::Auth::AuthFinders do
request.update_param(key, value)
end
def set_header(key, value)
env[key] = value
end
describe '#find_user_from_warden' do
context 'with CSRF token' do
before do
@ -31,7 +35,7 @@ describe Gitlab::Auth::AuthFinders do
context 'with valid credentials' do
it 'returns the user' do
env['warden'] = double("warden", authenticate: user)
set_header('warden', double("warden", authenticate: user))
expect(find_user_from_warden).to eq user
end
@ -41,7 +45,7 @@ describe Gitlab::Auth::AuthFinders do
context 'without CSRF token' do
it 'returns nil' do
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false)
env['warden'] = double("warden", authenticate: user)
set_header('warden', double("warden", authenticate: user))
expect(find_user_from_warden).to be_nil
end
@ -51,8 +55,8 @@ describe Gitlab::Auth::AuthFinders do
describe '#find_user_from_feed_token' do
context 'when the request format is atom' do
before do
env['SCRIPT_NAME'] = 'url.atom'
env['HTTP_ACCEPT'] = 'application/atom+xml'
set_header('SCRIPT_NAME', 'url.atom')
set_header('HTTP_ACCEPT', 'application/atom+xml')
end
context 'when feed_token param is provided' do
@ -94,7 +98,7 @@ describe Gitlab::Auth::AuthFinders do
context 'when the request format is not atom' do
it 'returns nil' do
env['SCRIPT_NAME'] = 'json'
set_header('SCRIPT_NAME', 'json')
set_param(:feed_token, user.feed_token)
@ -104,7 +108,7 @@ describe Gitlab::Auth::AuthFinders do
context 'when the request format is empty' do
it 'the method call does not modify the original value' do
env['SCRIPT_NAME'] = 'url.atom'
set_header('SCRIPT_NAME', 'url.atom')
env.delete('action_dispatch.request.formats')
@ -118,7 +122,7 @@ describe Gitlab::Auth::AuthFinders do
describe '#find_user_from_static_object_token' do
shared_examples 'static object request' do
before do
env['SCRIPT_NAME'] = path
set_header('SCRIPT_NAME', path)
end
context 'when token header param is present' do
@ -174,7 +178,7 @@ describe Gitlab::Auth::AuthFinders do
context 'when request format is not archive nor blob' do
before do
env['script_name'] = 'url'
set_header('script_name', 'url')
end
it 'returns nil' do
@ -183,11 +187,46 @@ describe Gitlab::Auth::AuthFinders do
end
end
describe '#deploy_token_from_request' do
let_it_be(:deploy_token) { create(:deploy_token) }
let_it_be(:route_authentication_setting) { { deploy_token_allowed: true } }
subject { deploy_token_from_request }
it { is_expected.to be_nil }
shared_examples 'an unauthenticated route' do
context 'when route is not allowed to use deploy_tokens' do
let(:route_authentication_setting) { { deploy_token_allowed: false } }
it { is_expected.to be_nil }
end
end
context 'with deploy token headers' do
before do
set_header(described_class::DEPLOY_TOKEN_HEADER, deploy_token.token)
end
it { is_expected.to eq deploy_token }
it_behaves_like 'an unauthenticated route'
context 'with incorrect token' do
before do
set_header(described_class::DEPLOY_TOKEN_HEADER, 'invalid_token')
end
it { is_expected.to be_nil }
end
end
end
describe '#find_user_from_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env['SCRIPT_NAME'] = 'url.atom'
set_header('SCRIPT_NAME', 'url.atom')
end
it 'returns nil if no access_token present' do
@ -196,13 +235,13 @@ describe Gitlab::Auth::AuthFinders do
context 'when validate_access_token! returns valid' do
it 'returns user' do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
expect(find_user_from_access_token).to eq user
end
it 'returns exception if token has no user' do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
@ -211,7 +250,7 @@ describe Gitlab::Auth::AuthFinders do
context 'with OAuth headers' do
it 'returns user' do
env['HTTP_AUTHORIZATION'] = "Bearer #{personal_access_token.token}"
set_header('HTTP_AUTHORIZATION', "Bearer #{personal_access_token.token}")
expect(find_user_from_access_token).to eq user
end
@ -228,7 +267,7 @@ describe Gitlab::Auth::AuthFinders do
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
end
it 'returns exception if token has no user' do
@ -252,19 +291,19 @@ describe Gitlab::Auth::AuthFinders do
end
it 'returns the user for RSS requests' do
env['SCRIPT_NAME'] = 'url.atom'
set_header('SCRIPT_NAME', 'url.atom')
expect(find_user_from_web_access_token(:rss)).to eq(user)
end
it 'returns the user for ICS requests' do
env['SCRIPT_NAME'] = 'url.ics'
set_header('SCRIPT_NAME', 'url.ics')
expect(find_user_from_web_access_token(:ics)).to eq(user)
end
it 'returns the user for API requests' do
env['SCRIPT_NAME'] = '/api/endpoint'
set_header('SCRIPT_NAME', '/api/endpoint')
expect(find_user_from_web_access_token(:api)).to eq(user)
end
@ -274,12 +313,12 @@ describe Gitlab::Auth::AuthFinders do
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env['SCRIPT_NAME'] = 'url.atom'
set_header('SCRIPT_NAME', 'url.atom')
end
context 'passed as header' do
it 'returns token if valid personal_access_token' do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token
set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
expect(find_personal_access_token).to eq personal_access_token
end
@ -298,7 +337,7 @@ describe Gitlab::Auth::AuthFinders do
end
it 'returns exception if invalid personal_access_token' do
env[described_class::PRIVATE_TOKEN_HEADER] = 'invalid_token'
set_header(described_class::PRIVATE_TOKEN_HEADER, 'invalid_token')
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
@ -310,7 +349,7 @@ describe Gitlab::Auth::AuthFinders do
context 'passed as header' do
it 'returns token if valid oauth_access_token' do
env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}"
set_header('HTTP_AUTHORIZATION', "Bearer #{token.token}")
expect(find_oauth_access_token.token).to eq token.token
end
@ -329,7 +368,7 @@ describe Gitlab::Auth::AuthFinders do
end
it 'returns exception if invalid oauth_access_token' do
env['HTTP_AUTHORIZATION'] = "Bearer invalid_token"
set_header('HTTP_AUTHORIZATION', "Bearer invalid_token")
expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
@ -337,7 +376,7 @@ describe Gitlab::Auth::AuthFinders do
describe '#find_personal_access_token_from_http_basic_auth' do
def auth_header_with(token)
env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('username', token)
set_header('HTTP_AUTHORIZATION', ActionController::HttpAuthentication::Basic.encode_credentials('username', token))
end
context 'access token is valid' do
@ -389,7 +428,7 @@ describe Gitlab::Auth::AuthFinders do
end
def set_auth(username, password)
env['HTTP_AUTHORIZATION'] = basic_http_auth(username, password)
set_header('HTTP_AUTHORIZATION', basic_http_auth(username, password))
end
subject { find_user_from_basic_auth_job }
@ -502,20 +541,20 @@ describe Gitlab::Auth::AuthFinders do
context 'when the job token is in the headers' do
it 'returns the user if valid job token' do
env[described_class::JOB_TOKEN_HEADER] = job.token
set_header(described_class::JOB_TOKEN_HEADER, job.token)
is_expected.to eq(user)
expect(@current_authenticated_job).to eq(job)
end
it 'returns nil without job token' do
env[described_class::JOB_TOKEN_HEADER] = ''
set_header(described_class::JOB_TOKEN_HEADER, '')
is_expected.to be_nil
end
it 'returns exception if invalid job token' do
env[described_class::JOB_TOKEN_HEADER] = 'invalid token'
set_header(described_class::JOB_TOKEN_HEADER, 'invalid token')
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
@ -524,7 +563,7 @@ describe Gitlab::Auth::AuthFinders do
let(:route_authentication_setting) { { job_token_allowed: false } }
it 'sets current_user to nil' do
env[described_class::JOB_TOKEN_HEADER] = job.token
set_header(described_class::JOB_TOKEN_HEADER, job.token)
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
@ -586,7 +625,7 @@ describe Gitlab::Auth::AuthFinders do
context 'with API requests' do
before do
env['SCRIPT_NAME'] = '/api/endpoint'
set_header('SCRIPT_NAME', '/api/endpoint')
end
it 'returns the runner if token is valid' do
@ -614,7 +653,7 @@ describe Gitlab::Auth::AuthFinders do
context 'without API requests' do
before do
env['SCRIPT_NAME'] = 'url.ics'
set_header('SCRIPT_NAME', 'url.ics')
end
it 'returns nil if token is valid' do

View File

@ -64,7 +64,7 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
shared_examples 'logging of relations creation' do
context 'when log_import_export_relation_creation feature flag is enabled' do
before do
stub_feature_flags(log_import_export_relation_creation: { enabled: true, thing: group })
stub_feature_flags(log_import_export_relation_creation: group)
end
it 'logs top-level relation creation' do
@ -79,7 +79,7 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
context 'when log_import_export_relation_creation feature flag is disabled' do
before do
stub_feature_flags(log_import_export_relation_creation: { enabled: false, thing: group })
stub_feature_flags(log_import_export_relation_creation: false)
end
it 'does not log top-level relation creation' do

View File

@ -37,7 +37,7 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
describe '#sample_memory_usage' do
it 'adds a metric containing the memory usage' do
expect(Gitlab::Metrics::System).to receive(:memory_usage)
expect(Gitlab::Metrics::System).to receive(:memory_usage_rss)
.and_return(9000)
expect(sampler).to receive(:add_metric)

View File

@ -21,7 +21,7 @@ describe Gitlab::Metrics::Samplers::RubySampler do
describe '#sample' do
it 'adds a metric containing the process resident memory bytes' do
expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
expect(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return(9000)
expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)

View File

@ -64,11 +64,11 @@ describe Gitlab::Metrics::System do
SNIP
end
describe '.memory_usage' do
describe '.memory_usage_rss' do
it "returns the process' resident set size (RSS) in bytes" do
mock_existing_proc_file('/proc/self/status', proc_status)
expect(described_class.memory_usage).to eq(2527232)
expect(described_class.memory_usage_rss).to eq(2527232)
end
end
@ -103,9 +103,9 @@ describe Gitlab::Metrics::System do
mock_missing_proc_file
end
describe '.memory_usage' do
describe '.memory_usage_rss' do
it 'returns 0' do
expect(described_class.memory_usage).to eq(0)
expect(described_class.memory_usage_rss).to eq(0)
end
end

View File

@ -28,17 +28,13 @@ describe Gitlab::Tracking do
end
it 'enables features using feature flags' do
stub_feature_flags(additional_snowplow_tracking: true)
allow(Feature).to receive(:enabled?).with(
:additional_snowplow_tracking,
'_group_'
).and_return(false)
stub_feature_flags(additional_snowplow_tracking: :__group__)
addition_feature_fields = {
formTracking: false,
linkClickTracking: false
}
expect(subject.snowplow_options('_group_')).to include(addition_feature_fields)
expect(subject.snowplow_options(:_group_)).to include(addition_feature_fields)
end
end

View File

@ -76,11 +76,7 @@ describe Gitlab::WikiPages::FrontMatterParser do
let(:raw_content) { with_front_matter }
before do
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => false)
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => {
enabled: true,
thing: gate
})
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => gate)
end
it do

View File

@ -3636,6 +3636,24 @@ describe Project do
expect(projects).to contain_exactly(public_project)
end
end
context 'with deploy token users' do
let_it_be(:private_project) { create(:project, :private) }
subject { described_class.all.public_or_visible_to_user(user) }
context 'deploy token user without project' do
let_it_be(:user) { create(:deploy_token) }
it { is_expected.to eq [] }
end
context 'deploy token user with project' do
let_it_be(:user) { create(:deploy_token, projects: [private_project]) }
it { is_expected.to include(private_project) }
end
end
end
describe '.ids_with_issuables_available_for' do

View File

@ -16,10 +16,7 @@ describe WikiPage do
end
def enable_front_matter_for(thing)
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => {
thing: thing,
enabled: true
})
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => thing)
end
describe '.group_by_directory' do

View File

@ -691,4 +691,28 @@ describe ProjectPolicy do
end
end
end
context 'deploy token access' do
let!(:project_deploy_token) do
create(:project_deploy_token, project: project, deploy_token: deploy_token)
end
subject { described_class.new(deploy_token, project) }
context 'a deploy token with read_package_registry scope' do
let(:deploy_token) { create(:deploy_token, read_package_registry: true) }
it { is_expected.to be_allowed(:read_package) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_disallowed(:create_package) }
end
context 'a deploy token with write_package_registry scope' do
let(:deploy_token) { create(:deploy_token, write_package_registry: true) }
it { is_expected.to be_allowed(:create_package) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_disallowed(:destroy_package) }
end
end
end

View File

@ -452,7 +452,7 @@ describe API::Internal::Base do
context 'when gitaly_upload_pack_filter feature flag is disabled' do
before do
stub_feature_flags(gitaly_upload_pack_filter: { enabled: false, thing: project })
stub_feature_flags(gitaly_upload_pack_filter: false)
end
it 'returns only maxInputSize and not partial clone git config' do
@ -481,7 +481,7 @@ describe API::Internal::Base do
context 'when gitaly_upload_pack_filter feature flag is disabled' do
before do
stub_feature_flags(gitaly_upload_pack_filter: { enabled: false, thing: project })
stub_feature_flags(gitaly_upload_pack_filter: false)
end
it 'returns an empty git config' do

View File

@ -107,7 +107,7 @@ describe API::RemoteMirrors do
context 'with the `keep_divergent_refs` feature enabled' do
before do
stub_feature_flags(keep_divergent_refs: { enabled: true, project: project })
stub_feature_flags(keep_divergent_refs: project)
end
it 'updates the `keep_divergent_refs` attribute' do

View File

@ -163,7 +163,7 @@ describe API::Search do
context 'when users search feature is disabled' do
before do
allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
stub_feature_flags(users_search: false)
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
end
@ -336,7 +336,7 @@ describe API::Search do
context 'when users search feature is disabled' do
before do
allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
stub_feature_flags(users_search: false)
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
end
@ -501,7 +501,7 @@ describe API::Search do
context 'when users search feature is disabled' do
before do
allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
stub_feature_flags(users_search: false)
get api(endpoint, user), params: { scope: 'users', search: 'billy' }
end

View File

@ -205,6 +205,20 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
it 'logs an auth warning' do
expect(Gitlab::AuthLogger).to receive(:warn).with(
message: 'Denied container registry permissions',
scope_type: 'repository',
requested_project_path: project.full_path,
requested_actions: ['*'],
authorized_actions: [],
user_id: current_user.id,
username: current_user.username
)
subject
end
end
context 'disallow developer to delete images since registry 2.7' do

View File

@ -264,7 +264,7 @@ describe Git::WikiPushService, services: true do
context 'but is enabled for a given project' do
before do
stub_feature_flags(wiki_events_on_git_push: { enabled: true, thing: project })
stub_feature_flags(wiki_events_on_git_push: project)
end
it 'creates events' do

View File

@ -30,21 +30,14 @@ describe Namespaces::CheckStorageSizeService, '#execute' do
expect(subject).to be_error
end
it 'is successful when disabled for the current group' do
stub_feature_flags(namespace_storage_limit: { enabled: false, thing: root_group })
expect(subject).to be_success
end
it 'is successful when feature flag is activated for another group' do
stub_feature_flags(namespace_storage_limit: false)
stub_feature_flags(namespace_storage_limit: { enabled: true, thing: create(:group) })
stub_feature_flags(namespace_storage_limit: create(:group))
expect(subject).to be_success
end
it 'errors when feature flag is activated for the current group' do
stub_feature_flags(namespace_storage_limit: { enabled: true, thing: root_group })
stub_feature_flags(namespace_storage_limit: root_group)
expect(subject).to be_error
end

View File

@ -173,21 +173,19 @@ RSpec.configure do |config|
# Enable all features by default for testing
allow(Feature).to receive(:enabled?) { true }
enabled = example.metadata[:enable_rugged].present?
enable_rugged = example.metadata[:enable_rugged].present?
# Disable Rugged features by default
Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
allow(Feature).to receive(:enabled?).with(flag).and_return(enabled)
stub_feature_flags(flag => enable_rugged)
end
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enabled)
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
# The following can be removed when we remove the staged rollout strategy
# and we can just enable it using instance wide settings
# (ie. ApplicationSetting#auto_devops_enabled)
allow(Feature).to receive(:enabled?)
.with(:force_autodevops_on_by_default, anything)
.and_return(false)
stub_feature_flags(force_autodevops_on_by_default: false)
# Enable Marginalia feature for all specs in the test suite.
allow(Gitlab::Marginalia).to receive(:cached_feature_enabled?).and_return(true)
@ -196,12 +194,8 @@ RSpec.configure do |config|
# is feature-complete and can be made default in place
# of older sidebar.
# See https://gitlab.com/groups/gitlab-org/-/epics/1863
allow(Feature).to receive(:enabled?)
.with(:vue_issuable_sidebar, anything)
.and_return(false)
allow(Feature).to receive(:enabled?)
.with(:vue_issuable_epic_sidebar, anything)
.and_return(false)
stub_feature_flags(vue_issuable_sidebar: false)
stub_feature_flags(vue_issuable_epic_sidebar: false)
# Stub these calls due to being expensive operations
# It can be reenabled for specific tests via:

View File

@ -9,23 +9,27 @@ module StubFeatureFlags
# Examples
# - `stub_feature_flags(ci_live_trace: false)` ... Disable `ci_live_trace`
# feature flag globally.
# - `stub_feature_flags(ci_live_trace: { enabled: false, thing: project })` ...
# Disable `ci_live_trace` feature flag on the specified project.
# - `stub_feature_flags(ci_live_trace: project)` ...
# - `stub_feature_flags(ci_live_trace: [project1, project2])` ...
# Enable `ci_live_trace` feature flag only on the specified projects.
def stub_feature_flags(features)
features.each do |feature_name, option|
if option.is_a?(Hash)
enabled, thing = option.values_at(:enabled, :thing)
else
enabled = option
thing = nil
end
features.each do |feature_name, actors|
allow(Feature).to receive(:enabled?).with(feature_name, any_args).and_return(false)
allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args).and_return(false)
if thing
allow(Feature).to receive(:enabled?).with(feature_name, thing, any_args) { enabled }
allow(Feature).to receive(:enabled?).with(feature_name.to_s, thing, any_args) { enabled }
else
allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled }
allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled }
Array(actors).each do |actor|
raise ArgumentError, "actor cannot be Hash" if actor.is_a?(Hash)
case actor
when false, true
allow(Feature).to receive(:enabled?).with(feature_name, any_args).and_return(actor)
allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args).and_return(actor)
when nil, ActiveRecord::Base, Symbol, RSpec::Mocks::Double
allow(Feature).to receive(:enabled?).with(feature_name, actor, any_args).and_return(true)
allow(Feature).to receive(:enabled?).with(feature_name.to_s, actor, any_args).and_return(true)
else
raise ArgumentError, "#stub_feature_flags accepts only `nil`, `true`, `false`, `ActiveRecord::Base` or `Symbol` as actors"
end
end
end
end

View File

@ -60,7 +60,7 @@ RSpec.shared_examples 'diff file entity' do
context 'when the `single_mr_diff_view` feature is disabled' do
before do
stub_feature_flags(single_mr_diff_view: { enabled: false, thing: project })
stub_feature_flags(single_mr_diff_view: false)
end
it 'contains both kinds of diffs' do

View File

@ -0,0 +1,130 @@
# frozen_string_literal: true
require 'spec_helper'
describe StubFeatureFlags do
before do
# reset stub introduced by `stub_feature_flags`
allow(Feature).to receive(:enabled?).and_call_original
end
context 'if not stubbed' do
it 'features are disabled by default' do
expect(Feature.enabled?(:test_feature)).to eq(false)
end
end
describe '#stub_feature_flags' do
using RSpec::Parameterized::TableSyntax
let(:feature_name) { :test_feature }
context 'when checking global state' do
where(:feature_actors, :expected_result) do
false | false
true | true
:A | false
%i[A] | false
%i[A B] | false
end
with_them do
before do
stub_feature_flags(feature_name => feature_actors)
end
it { expect(Feature.enabled?(feature_name)).to eq(expected_result) }
it { expect(Feature.disabled?(feature_name)).not_to eq(expected_result) }
context 'default_enabled does not impact feature state' do
it { expect(Feature.enabled?(feature_name, default_enabled: true)).to eq(expected_result) }
it { expect(Feature.disabled?(feature_name, default_enabled: true)).not_to eq(expected_result) }
end
end
end
context 'when checking scoped state' do
where(:feature_actors, :tested_actor, :expected_result) do
false | nil | false
true | nil | true
false | :A | false
true | :A | true
:A | nil | false
:A | :A | true
:A | :B | false
%i[A] | nil | false
%i[A] | :A | true
%i[A] | :B | false
%i[A B] | nil | false
%i[A B] | :A | true
%i[A B] | :B | true
end
with_them do
before do
stub_feature_flags(feature_name => feature_actors)
end
it { expect(Feature.enabled?(feature_name, tested_actor)).to eq(expected_result) }
it { expect(Feature.disabled?(feature_name, tested_actor)).not_to eq(expected_result) }
context 'default_enabled does not impact feature state' do
it { expect(Feature.enabled?(feature_name, tested_actor, default_enabled: true)).to eq(expected_result) }
it { expect(Feature.disabled?(feature_name, tested_actor, default_enabled: true)).not_to eq(expected_result) }
end
end
end
context 'type handling' do
context 'raises error' do
where(:feature_actors) do
['string', 1, 1.0, OpenStruct.new]
end
with_them do
subject { stub_feature_flags(feature_name => feature_actors) }
it { expect { subject }.to raise_error(ArgumentError, /accepts only/) }
end
end
context 'does not raise error' do
where(:feature_actors) do
[true, false, nil, :symbol, double, User.new]
end
with_them do
subject { stub_feature_flags(feature_name => feature_actors) }
it { expect { subject }.not_to raise_error }
end
end
end
it 'subsquent run changes state' do
# enable FF only on A
stub_feature_flags(test_feature: %i[A])
expect(Feature.enabled?(:test_feature)).to eq(false)
expect(Feature.enabled?(:test_feature, :A)).to eq(true)
expect(Feature.enabled?(:test_feature, :B)).to eq(false)
# enable FF only on B
stub_feature_flags(test_feature: %i[B])
expect(Feature.enabled?(:test_feature)).to eq(false)
expect(Feature.enabled?(:test_feature, :A)).to eq(false)
expect(Feature.enabled?(:test_feature, :B)).to eq(true)
# enable FF on all
stub_feature_flags(test_feature: true)
expect(Feature.enabled?(:test_feature)).to eq(true)
expect(Feature.enabled?(:test_feature, :A)).to eq(true)
expect(Feature.enabled?(:test_feature, :B)).to eq(true)
# disable FF on all
stub_feature_flags(test_feature: false)
expect(Feature.enabled?(:test_feature)).to eq(false)
expect(Feature.enabled?(:test_feature, :A)).to eq(false)
expect(Feature.enabled?(:test_feature, :B)).to eq(false)
end
end
end