Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-16 18:10:10 +00:00
parent 6364c14cc1
commit d755061465
70 changed files with 985 additions and 570 deletions

View file

@ -1,13 +1,9 @@
<script>
import { GlIcon, GlPopover } from '@gitlab/ui';
import { __, sprintf } from '../../../locale';
import popover from '../../../vue_shared/directives/popover';
import { MAX_TITLE_LENGTH, MAX_BODY_LENGTH } from '../../constants';
export default {
directives: {
popover,
},
components: {
GlIcon,
GlPopover,

View file

@ -1,14 +1,29 @@
<script>
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import {
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
GlFormInput,
GlIcon,
GlModal,
GlSprintf,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import lockState from '../graphql/mutations/lock_state.mutation.graphql';
import unlockState from '../graphql/mutations/unlock_state.mutation.graphql';
import removeState from '../graphql/mutations/remove_state.mutation.graphql';
export default {
components: {
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
GlFormInput,
GlIcon,
GlModal,
GlSprintf,
},
props: {
state: {
@ -19,20 +34,59 @@ export default {
data() {
return {
loading: false,
showRemoveModal: false,
removeConfirmText: '',
};
},
i18n: {
downloadJSON: s__('Terraform|Download JSON'),
lock: s__('Terraform|Lock'),
modalBody: s__(
'Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously will remain intact, only the state file with all its versions are to be removed. This action is non-revertible.',
),
modalCancel: s__('Terraform|Cancel'),
modalHeader: s__('Terraform|Are you sure you want to remove the Terraform State %{name}?'),
modalInputLabel: s__(
'Terraform|To remove the State file and its versions, type %{name} to confirm:',
),
modalRemove: s__('Terraform|Remove'),
remove: s__('Terraform|Remove state file and versions'),
unlock: s__('Terraform|Unlock'),
},
computed: {
cancelModalProps() {
return {
text: this.$options.i18n.modalCancel,
attributes: [],
};
},
disableModalSubmit() {
return this.removeConfirmText !== this.state.name;
},
primaryModalProps() {
return {
text: this.$options.i18n.modalRemove,
attributes: [{ disabled: this.disableModalSubmit }, { variant: 'danger' }],
};
},
},
methods: {
hideModal() {
this.showRemoveModal = false;
this.removeConfirmText = '';
},
lock() {
this.stateMutation(lockState);
},
unlock() {
this.stateMutation(unlockState);
},
remove() {
if (!this.disableModalSubmit) {
this.hideModal();
this.stateMutation(removeState);
}
},
stateMutation(mutation) {
this.loading = true;
this.$apollo
@ -83,6 +137,56 @@ export default {
<gl-dropdown-item v-else data-testid="terraform-state-lock" @click="lock">
{{ $options.i18n.lock }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item data-testid="terraform-state-remove" @click="showRemoveModal = true">
{{ $options.i18n.remove }}
</gl-dropdown-item>
</gl-dropdown>
<gl-modal
:modal-id="`terraform-state-actions-remove-modal-${state.name}`"
:visible="showRemoveModal"
:action-primary="primaryModalProps"
:action-cancel="cancelModalProps"
@ok="remove"
@cancel="hideModal"
@close="hideModal"
@hide="hideModal"
>
<template #modal-title>
<gl-sprintf :message="$options.i18n.modalHeader">
<template #name>
<span>{{ state.name }}</span>
</template>
</gl-sprintf>
</template>
<p>
<gl-sprintf :message="$options.i18n.modalBody">
<template #name>
<span>{{ state.name }}</span>
</template>
</gl-sprintf>
</p>
<gl-form-group>
<template #label>
<gl-sprintf :message="$options.i18n.modalInputLabel">
<template #name>
<code>{{ state.name }}</code>
</template>
</gl-sprintf>
</template>
<gl-form-input
:id="`terraform-state-remove-input-${state.name}`"
ref="input"
v-model="removeConfirmText"
type="text"
@keyup.enter="remove"
/>
</gl-form-group>
</gl-modal>
</div>
</template>

View file

@ -0,0 +1,5 @@
mutation removeState($stateID: TerraformStateID!) {
terraformStateDelete(input: { id: $stateID }) {
errors
}
}

View file

@ -1,22 +0,0 @@
import $ from 'jquery';
/**
* Helper to user bootstrap popover in vue.js.
* Follow docs for html attributes: https://getbootstrap.com/docs/3.3/javascript/#static-popover
*
* @example
* import popover from 'vue_shared/directives/popover.js';
* {
* directives: [popover]
* }
* <a v-popover="{options}">popover</a>
*/
export default {
bind(el, binding) {
$(el).popover(binding.value);
},
unbind(el) {
$(el).popover('dispose');
},
};

View file

@ -1,111 +0,0 @@
.popover {
max-width: $popover-max-width;
border: 1px solid $gray-100;
box-shadow: $popover-box-shadow;
font-size: $gl-font-size-small;
/**
* Blue popover variation
*/
&.blue {
background-color: $blue-600;
border-color: $blue-600;
.popover-body {
color: $white;
}
&.bs-popover-bottom {
.arrow::before,
.arrow::after {
border-bottom-color: $blue-600;
}
}
&.bs-popover-top {
.arrow::before,
.arrow::after {
border-top-color: $blue-600;
}
}
&.bs-popover-right {
.arrow::after,
.arrow::before {
border-right-color: $blue-600;
}
}
&.bs-popover-left {
.arrow::before,
.arrow::after {
border-left-color: $blue-600;
}
}
}
}
.bs-popover-top {
/* When popover position is top, the arrow is translated 1 pixel
* due to the box-shadow include in our custom styles.
*/
> .arrow::before {
border-top-color: $gray-100;
bottom: 1px;
}
> .arrow::after {
bottom: 2px;
}
}
.bs-popover-bottom {
> .arrow::before {
border-bottom-color: $gray-100;
}
> .popover-header::before {
border-color: $white;
}
}
.bs-popover-right > .arrow::before {
border-right-color: $gray-100;
}
.bs-popover-left > .arrow::before {
border-left-color: $gray-100;
}
.popover-header {
background-color: $white;
font-size: $gl-font-size-small;
}
.popover-body {
padding: $gl-padding $gl-padding-12;
> .popover-hr {
margin: $gl-padding 0;
}
}
/**
* mr_popover component
*/
.mr-popover {
.text-secondary {
font-size: 12px;
line-height: 1.33;
}
}
.suggest-gitlab-ci-yml {
margin-top: -1em;
.popover-header {
padding: $gl-padding;
display: flex;
align-items: center;
}
}

View file

@ -146,7 +146,11 @@
}
@mixin green-status-color {
@include status-color($green-100, $green-500, $green-700);
@include status-color(
var(--green-100, $green-100),
var(--green-500, $green-500),
var(--green-700, $green-700)
);
}
@mixin fade($gradient-direction, $gradient-color) {
@ -254,9 +258,9 @@
@mixin build-trace-bar($height) {
height: $height;
min-height: $height;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
background: var(--gray-50, $gray-50);
border: 1px solid var(--border-color, $border-color);
color: var(--gl-text-color, $gl-text-color);
padding: $grid-size;
}

View file

@ -4,7 +4,7 @@
position: absolute;
top: 48%;
left: -$length;
border-top: 2px solid $border-color;
border-top: 2px solid var(--border-color, $border-color);
width: $length;
height: 1px;
}
@ -14,14 +14,14 @@
display: inline-block;
padding: 8px 10px 9px;
width: 100%;
border: 1px solid $border-color;
border: 1px solid var(--border-color, $border-color);
border-radius: $border-radius;
background-color: $white;
background-color: var(--white, $white);
&:hover {
background-color: $gray-darker;
background-color: var(--gray-50, $gray-50);
border: 1px solid $dropdown-toggle-active-border-color;
color: $gl-text-color;
color: var(--gl-text-color, $gl-text-color);
}
}

View file

@ -61,7 +61,7 @@
}
.environment-information {
border: 1px solid $border-color;
border: 1px solid var(--border-color, $border-color);
padding: 8px $gl-padding 12px;
border-radius: $border-radius-default;
@ -219,9 +219,9 @@
}
.builds-container {
background-color: $white;
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
background-color: var(--white, $white);
border-top: 1px solid var(--border-color, $border-color);
border-bottom: 1px solid var(--border-color, $border-color);
max-height: 300px;
width: 289px;
overflow: auto;
@ -237,7 +237,7 @@
width: 270px;
&:hover {
color: $gl-text-color;
color: var(--gl-text-color, $gl-text-color);
}
}
@ -256,13 +256,13 @@
}
&:hover {
background-color: $gray-darker;
background-color: var(--gray-50, $gray-50);
}
}
}
.link-commit {
color: $blue-600;
color: var(--blue-600, $blue-600);
}
}

View file

@ -2,7 +2,7 @@
.ci-status {
padding: 2px 7px 4px;
border: 1px solid $gray-darker;
border: 1px solid var(--border-color, $border-color);
white-space: nowrap;
border-radius: 4px;
@ -18,7 +18,11 @@
}
&.ci-failed {
@include status-color($red-100, $red-500, $red-600);
@include status-color(
var(--red-100, $red-100),
var(--red-500, $red-500),
var(--red-600, $red-600)
);
}
&.ci-success {
@ -30,8 +34,8 @@
&.ci-disabled,
&.ci-scheduled,
&.ci-manual {
color: $gl-text-color;
border-color: $gl-text-color;
color: var(--gl-text-color, $gl-text-color);
border-color: currentColor;
&:not(span):hover {
background-color: rgba($gl-text-color, 0.07);
@ -39,25 +43,37 @@
}
&.ci-preparing {
@include status-color($gray-100, $gray-300, $gray-400);
@include status-color(
var(--gray-100, $gray-100),
var(--gray-300, $gray-300),
var(--gray-400, $gray-400)
);
}
&.ci-pending,
&.ci-waiting-for-resource,
&.ci-failed-with-warnings,
&.ci-success-with-warnings {
@include status-color($orange-50, $orange-500, $orange-700);
@include status-color(
var(--orange-50, $orange-50),
var(--orange-500, $orange-500),
var(--orange-700, $orange-700)
);
}
&.ci-info,
&.ci-running {
@include status-color($blue-100, $blue-500, $blue-600);
@include status-color(
var(--blue-100, $blue-100),
var(--blue-500, $blue-500),
var(--blue-600, $blue-600)
);
}
&.ci-created,
&.ci-skipped {
color: $gl-text-color-secondary;
border-color: $gl-text-color-secondary;
color: var(--gray-500, $gray-500);
border-color: currentColor;
&:not(span):hover {
background-color: rgba($gl-text-color-secondary, 0.07);

View file

@ -32,7 +32,7 @@
//// Copied from roadmaps.scss - adapted for on-call schedules
$header-item-height: 72px;
$item-height: 40px;
$details-cell-width: 150px;
$details-cell-width: 180px;
$timeline-cell-height: 32px;
$timeline-cell-width: 180px;
$border-style: 1px solid var(--gray-100, $gray-100);
@ -98,7 +98,7 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi
.item-label {
@include gl-py-4;
@include gl-pl-7;
@include gl-pl-4;
border-right: $border-style;
border-bottom: $border-style;
}
@ -147,7 +147,6 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi
.timeline-cell {
@include float-left;
height: $item-height;
border-bottom: $border-style;
}
.details-cell {
@ -181,3 +180,10 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi
transform: translateX(-50%);
}
}
.gl-token {
.gl-avatar-labeled-label {
@include gl-text-white;
@include gl-font-weight-normal;
}
}

View file

@ -33,7 +33,7 @@
}
.stage {
color: $gl-text-color-secondary;
color: var(--gray-500, $gray-500);
font-weight: $gl-font-weight-normal;
vertical-align: middle;
}
@ -62,7 +62,7 @@
a {
font-weight: $gl-font-weight-bold;
color: $gl-text-color;
color: var(--gl-text-color, $gl-text-color);
text-decoration: none;
&:focus,
@ -124,7 +124,7 @@
display: flex;
width: 100%;
min-height: $dropdown-max-height-lg;
background-color: $gray-light;
background-color: var(--gray-50, $gray-50);
padding: $gl-padding 0;
overflow: auto;
}
@ -177,7 +177,7 @@
a {
text-decoration: none;
color: $gl-text-color;
color: var(--gl-text-color, $gl-text-color);
}
svg {
@ -249,18 +249,18 @@
height: 25px;
position: absolute;
top: -31px;
border-top: 2px solid $border-color;
border-top: 2px solid var(--border-color, $border-color);
}
&::after {
left: -44px;
border-right: 2px solid $border-color;
border-right: 2px solid var(--border-color, $border-color);
border-radius: 0 20px;
}
&::before {
right: -44px;
border-left: 2px solid $border-color;
border-left: 2px solid var(--border-color, $border-color);
border-radius: 20px 0 0;
}
}
@ -316,7 +316,7 @@
a.build-content:hover,
button.build-content:hover {
background-color: $gray-darker;
background-color: var(--gray-100, $gray-100);
border: 1px solid $dropdown-toggle-active-border-color;
}
@ -327,7 +327,7 @@
position: absolute;
top: 48%;
right: -48px;
border-top: 2px solid $border-color;
border-top: 2px solid var(--border-color, $border-color);
width: 48px;
height: 1px;
}
@ -340,7 +340,7 @@
content: '';
top: -49px;
position: absolute;
border-bottom: 2px solid $border-color;
border-bottom: 2px solid var(--border-color, $border-color);
width: 25px;
height: 69px;
}
@ -348,14 +348,14 @@
// Right connecting curves
&::after {
right: -25px;
border-right: 2px solid $border-color;
border-right: 2px solid var(--border-color, $border-color);
border-radius: 0 0 20px;
}
// Left connecting curves
&::before {
left: -25px;
border-left: 2px solid $border-color;
border-left: 2px solid var(--border-color, $border-color);
border-radius: 0 0 0 20px;
}
}
@ -390,7 +390,7 @@
line-height: 0;
svg {
fill: $gl-text-color-secondary;
fill: var(--gray-500, $gray-500);
}
.spinner {
@ -488,13 +488,13 @@
left: -6px;
margin-top: 3px;
border-width: 7px 5px 7px 0;
border-right-color: $border-color;
border-right-color: var(--border-color, $border-color);
}
&::after {
left: -5px;
border-width: 10px 7px 10px 0;
border-right-color: $white;
border-right-color: var(--white, $white);
}
}
@ -519,5 +519,5 @@
}
.progress-bar.bg-primary {
background-color: $blue-500 !important;
background-color: var(--blue-500, $blue-500) !important;
}

View file

@ -22,7 +22,7 @@
min-width: 170px; //Guarantees buttons don't break in several lines.
.btn-default {
color: $gl-text-color-secondary;
color: var(--gray-500, $gray-500);
}
.btn.btn-retry:hover,
@ -32,7 +32,7 @@
}
svg path {
fill: $gl-text-color-secondary;
fill: var(--gray-500, $gray-500);
}
.dropdown-menu {
@ -42,7 +42,7 @@
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
color: var(--gray-500, $gray-500);
}
.btn-group.open .btn-default {

View file

@ -2,7 +2,6 @@
class Profiles::GpgKeysController < Profiles::ApplicationController
before_action :set_gpg_key, only: [:destroy, :revoke]
skip_before_action :authenticate_user!, only: [:get_keys]
feature_category :users
@ -40,24 +39,6 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
end
end
# Get all gpg keys of a user(params[:username]) in a text format
def get_keys
if params[:username].present?
begin
user = UserFinder.new(params[:username]).find_by_username
if user.present?
render plain: user.gpg_keys.select(&:verified?).map(&:key).join("\n")
else
render_404
end
rescue => e
render html: e.message
end
else
render_404
end
end
private
def gpg_key_params

View file

@ -31,7 +31,7 @@ module Projects
end
def notify_service
notify_service_class.new(project, current_user, notification_payload)
notify_service_class.new(project, notification_payload)
end
def notify_service_class

View file

@ -73,7 +73,7 @@ module Projects
def notify_service
Projects::Prometheus::Alerts::NotifyService
.new(project, current_user, params.permit!)
.new(project, params.permit!)
end
def create_service

View file

@ -58,6 +58,11 @@ class UsersController < ApplicationController
end
end
# Get all gpg keys of a user(params[:username]) in a text format
def gpg_keys
render plain: user.gpg_keys.select(&:verified?).map(&:key).join("\n")
end
def groups
load_groups

View file

@ -40,7 +40,7 @@ module Ci
date: start_date..end_date
}
if ref_path
if ref_path.present?
params[:ref_path] = ref_path
else
params[:default_branch] = true

View file

@ -167,6 +167,8 @@ module Types
description: 'Indicates if the merge request is mergeable'
field :commits_without_merge_commits, Types::CommitType.connection_type, null: true,
calls_gitaly: true, description: 'Merge request commits excluding merge commits'
field :security_auto_fix, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if the merge request is created by @GitLab-Security-Bot.'
def approved_by
object.approved_by_users
@ -229,6 +231,10 @@ module Types
def commits_without_merge_commits
object.recent_commits.without_merge_commits
end
def security_auto_fix
object.author == User.security_bot
end
end
end

View file

@ -1,10 +1,16 @@
# frozen_string_literal: true
module AlertManagement
class ProcessPrometheusAlertService < BaseService
class ProcessPrometheusAlertService
include BaseServiceUtility
include Gitlab::Utils::StrongMemoize
include ::IncidentManagement::Settings
def initialize(project, payload)
@project = project
@payload = payload
end
def execute
return bad_request unless incoming_payload.has_required_attributes?
@ -19,6 +25,8 @@ module AlertManagement
private
attr_reader :project, :payload
def process_alert_management_alert
if incoming_payload.resolved?
process_resolved_alert_management_alert
@ -127,7 +135,7 @@ module AlertManagement
strong_memoize(:incoming_payload) do
Gitlab::AlertManagement::Payload.parse(
project,
params,
payload,
monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
)
end

View file

@ -65,7 +65,7 @@ module Clusters
notification_payload = build_notification_payload(project)
integration = project.alert_management_http_integrations.active.first
Projects::Alerting::NotifyService.new(project, nil, notification_payload).execute(integration&.token, integration)
Projects::Alerting::NotifyService.new(project, notification_payload).execute(integration&.token, integration)
@logger.info(message: 'Successfully notified of Prometheus newly unhealthy', cluster_id: @cluster.id, project_id: project.id)
end

View file

@ -2,10 +2,16 @@
module Projects
module Alerting
class NotifyService < BaseService
class NotifyService
include BaseServiceUtility
include Gitlab::Utils::StrongMemoize
include ::IncidentManagement::Settings
def initialize(project, payload)
@project = project
@payload = payload
end
def execute(token, integration = nil)
@integration = integration
@ -24,7 +30,7 @@ module Projects
private
attr_reader :integration
attr_reader :project, :payload, :integration
def process_alert
if alert.persisted?
@ -101,7 +107,7 @@ module Projects
def incoming_payload
strong_memoize(:incoming_payload) do
Gitlab::AlertManagement::Payload.parse(project, params.to_h)
Gitlab::AlertManagement::Payload.parse(project, payload.to_h)
end
end
@ -110,7 +116,7 @@ module Projects
end
def valid_payload_size?
Gitlab::Utils::DeepSize.new(params).valid?
Gitlab::Utils::DeepSize.new(payload).valid?
end
def active_integration?

View file

@ -3,7 +3,7 @@
module Projects
module Prometheus
module Alerts
class NotifyService < BaseService
class NotifyService
include Gitlab::Utils::StrongMemoize
include ::IncidentManagement::Settings
@ -17,9 +17,14 @@ module Projects
SUPPORTED_VERSION = '4'
def initialize(project, payload)
@project = project
@payload = payload
end
def execute(token, integration = nil)
return bad_request unless valid_payload_size?
return unprocessable_entity unless self.class.processable?(params)
return unprocessable_entity unless self.class.processable?(payload)
return unauthorized unless valid_alert_manager_token?(token, integration)
process_prometheus_alerts
@ -27,18 +32,20 @@ module Projects
ServiceResponse.success
end
def self.processable?(params)
def self.processable?(payload)
# Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/220496
return false unless params
return false unless payload
REQUIRED_PAYLOAD_KEYS.subset?(params.keys.to_set) &&
params['version'] == SUPPORTED_VERSION
REQUIRED_PAYLOAD_KEYS.subset?(payload.keys.to_set) &&
payload['version'] == SUPPORTED_VERSION
end
private
attr_reader :project, :payload
def valid_payload_size?
Gitlab::Utils::DeepSize.new(params).valid?
Gitlab::Utils::DeepSize.new(payload).valid?
end
def firings
@ -50,7 +57,7 @@ module Projects
end
def alerts
params['alerts']
payload['alerts']
end
def valid_alert_manager_token?(token, integration)
@ -121,7 +128,7 @@ module Projects
def process_prometheus_alerts
alerts.each do |alert|
AlertManagement::ProcessPrometheusAlertService
.new(project, nil, alert.to_h)
.new(project, alert.to_h)
.execute
end
end

View file

@ -15,4 +15,4 @@
%p
%h4= _("Release notes:")
= markdown_field(@release, :description)
= markdown(@release.description, pipeline: :email, author: @release.author)

View file

@ -0,0 +1,5 @@
---
title: Add MergeRequest to VulnerabilityType in GraphQL
merge_request: 50082
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Add delete button to terraform list vue
merge_request: 48485
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Move profiles/gpg_keys#get_keys to users#gpg_keys
merge_request: 49448
author: Takuya Noguchi
type: other

View file

@ -0,0 +1,5 @@
---
title: Fix pipeline page in dark mode
merge_request: 49214
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix Markdown attachments in Releases not rendering with full URL
merge_request: 50146
author:
type: fixed

View file

@ -58,7 +58,7 @@ constraints(::Constraints::UserUrlConstrainer.new) do
get ':username.keys' => 'users#ssh_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
# Get all GPG keys of user
get ':username.gpg' => 'profiles/gpg_keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
get ':username.gpg' => 'users#gpg_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
scope(path: ':username',
as: :user,

View file

@ -0,0 +1,112 @@
---
stage: Enablement
group: Geo
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
type: howto
---
# Geo Glossary
NOTE:
We are updating the Geo documentation, user interface and commands to reflect these changes. Not all pages comply with
these definitions yet.
These are the defined terms to describe all aspects of Geo. Using a set of clearly
defined terms helps us to communicate efficiently and avoids confusion. The language
on this page aims to be [ubiquitous](https://about.gitlab.com/handbook/communication/#ubiquitous-language)
and [as simple as possible](https://about.gitlab.com/handbook/communication/#simple-language).
We provide example diagrams and statements to demonstrate correct usage of terms.
| Term | Definition | Scope | Discouraged synonyms |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|-------------------------------------------------|
| Node | An individual server that runs GitLab either with a specific role or as a whole (e.g. a Rails application node). In a cloud context this can be a specific machine type. | GitLab | instance, server |
| Site | One or a collection of nodes running a single GitLab application. A site can be single-node or multi-node. | GitLab | deployment, installation instance |
| Single-node site | A specific configuration of GitLab that uses exactly one node. | GitLab | single-server, single-instance
| Multi-node site | A specific configuration of GitLab that uses more than one node. | GitLab | multi-server, multi-instance, high availability |
| Primary site | A GitLab site that is configured to be read and writable. There can only be a single primary site. | Geo-specific | Geo deployment, Primary node |
| Secondary site(s) | GitLab site that is configured to be read-only. There can be one or more secondary sites. | Geo-specific | Geo deployment, Secondary node |
| Geo deployment | A collection of two or more GitLab sites with exactly one primary site being replicated by one or more secondary sites. | Geo-specific | |
| Reference architecture(s) | A [specified configuration of GitLab for a number of users](../reference_architectures/index.md), possibly including multiple nodes and multiple sites. | GitLab | |
| Promoting | Changing the role of a site from secondary to primary. | Geo-specific | |
| Demoting | Changing the role of a site from primary to secondary. | Geo-specific | |
| Failover | The entire process that shifts users from a primary Site to a secondary site. This includes promoting a secondary, but contains other parts as well e.g. scheduling maintenance. | Geo-specific | |
## Examples
### Single-node site
```mermaid
graph TD
subgraph S-Site[Single-node site]
Node_3[GitLab node]
end
```
### Multi-node site
```mermaid
graph TD
subgraph MN-Site[Multi-node site]
Node_1[Application node]
Node_2[Database node]
Node_3[Gitaly node]
end
```
### Geo deployment - Single-node sites
This Geo deployment has a single-node primary site, a single-node secondary site:
```mermaid
graph TD
subgraph Geo deployment
subgraph Primary[Primary site, single-node]
Node_1[GitLab node]
end
subgraph Secondary1[Secondary site 1, single-node]
Node_2[GitLab node]
end
end
```
### Geo deployment - Multi-node sites
This Geo deployment has a multi-node primary site, a multi-node secondary site:
```mermaid
graph TD
subgraph Geo deployment
subgraph Primary[Primary site, multi-node]
Node_1[Application node]
Node_2[Database node]
end
subgraph Secondary1[Secondary site 1, multi-node]
Node_5[Application node]
Node_6[Database node]
end
end
```
### Geo deployment - Mixed sites
This Geo deployment has a multi-node primary site, a multi-node secondary site and another single-node secondary site:
```mermaid
graph TD
subgraph Geo deployment
subgraph Primary[Primary site, multi-node]
Node_1[Application node]
Node_2[Database node]
Node_3[Gitaly node]
end
subgraph Secondary1[Secondary site 1, multi-node]
Node_5[Application node]
Node_6[Database node]
end
subgraph Secondary2[Secondary site 2, single-node]
Node_7[Single GitLab node]
end
end
```

View file

@ -1021,7 +1021,7 @@ previously listed components, it's helpful to simultaneously gather multiple log
from a GitLab instance.
NOTE:
GitLab Support will often ask for one of these, and maintains the required tools.
GitLab Support often asks for one of these, and maintains the required tools.
### Briefly tail the main logs

View file

@ -13456,6 +13456,11 @@ type MergeRequest implements CurrentUserTodos & Noteable {
full: Boolean = false
): String!
"""
Indicates if the merge request is created by @GitLab-Security-Bot.
"""
securityAutoFix: Boolean
"""
Indicates if the merge request will be rebased
"""
@ -25001,6 +25006,11 @@ type Vulnerability implements Noteable {
"""
location: VulnerabilityLocation
"""
Merge request that fixes the vulnerability.
"""
mergeRequest: MergeRequest
"""
All notes on this noteable
"""

View file

@ -37121,6 +37121,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "securityAutoFix",
"description": "Indicates if the merge request is created by @GitLab-Security-Bot.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "shouldBeRebased",
"description": "Indicates if the merge request will be rebased",
@ -72824,6 +72838,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "mergeRequest",
"description": "Merge request that fixes the vulnerability.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "MergeRequest",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "notes",
"description": "All notes on this noteable",

View file

@ -2088,6 +2088,7 @@ Autogenerated return type of MarkAsSpamSnippet.
| `rebaseCommitSha` | String | Rebase commit SHA of the merge request |
| `rebaseInProgress` | Boolean! | Indicates if there is a rebase currently in progress for the merge request |
| `reference` | String! | Internal reference of the merge request. Returned in shortened format by default |
| `securityAutoFix` | Boolean | Indicates if the merge request is created by @GitLab-Security-Bot. |
| `shouldBeRebased` | Boolean! | Indicates if the merge request will be rebased |
| `shouldRemoveSourceBranch` | Boolean | Indicates if the source branch of the merge request will be deleted after merge |
| `sourceBranch` | String! | Source branch of the merge request |
@ -3772,6 +3773,7 @@ Represents a vulnerability.
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
| `issueLinks` | VulnerabilityIssueLinkConnection! | List of issue links related to the vulnerability |
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
| `mergeRequest` | MergeRequest | Merge request that fixes the vulnerability. |
| `notes` | NoteConnection! | All notes on this noteable |
| `primaryIdentifier` | VulnerabilityIdentifier | Primary identifier of the vulnerability. |
| `project` | Project | The project on which the vulnerability was found |

View file

@ -72,8 +72,8 @@ Complementary reads:
### Development guidelines review
When you submit a change to GitLab's development guidelines, the people
you ask for reviews from depend on the level of change, as described below.
When you submit a change to GitLab's development guidelines, who
you ask for reviews depends on the level of change.
#### Wording, style, or link changes
@ -88,7 +88,7 @@ maintainer or Technical Writer. These can include:
#### Specific changes
If the MR proposes changes limited to a particular stage, group, or team,
If the MR proposes changes that are limited to a particular stage, group, or team,
request a review and approval from an experienced GitLab Team Member in that
group. For example, if you're documenting a new internal API used exclusively by
a given group, request an engineering review from one of the group's members.

View file

@ -803,6 +803,32 @@ overhead. If you are writing:
- A `Mutation`, feel free to lookup objects directly.
- A `Resolver` or methods on a `BaseObject`, then you want to allow for batching.
### Error handling
Resolvers may raise errors, which will be converted to top-level errors as
appropriate. All anticipated errors should be caught and transformed to an
appropriate GraphQL error (see
[`Gitlab::Graphql::Errors`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/graphql/errors.rb)).
Any uncaught errors will be suppressed and the client will receive the message
`Internal service error`.
The one special case is permission errors. In the REST API we return
`404 Not Found` for any resources that the user does not have permission to
access. The equivalent behavior in GraphQL is for us to return `null` for
all absent or unauthorized resources.
Query resolvers **should not raise errors for unauthorized resources**.
The rationale for this is that clients must not be able to distinguish between
the absence of a record and the presence of one they do not have access to. To
do so is a security vulnerability, since it leaks information we want to keep
hidden.
In most cases you don't need to worry about this - this is handled correctly by
the resolver field authorization we declare with the `authorize` DSL calls. If
you need to do something more custom however, remember, if you encounter an
object the `current_user` does not have access to when resolving a field, then
the entire field should resolve to `null`.
### Deriving resolvers (`BaseResolver.single` and `BaseResolver.last`)
For some simple use cases, we can derive resolvers from others.

View file

@ -4,28 +4,63 @@ group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Chatops on GitLab.com
# ChatOps on GitLab.com
ChatOps on GitLab.com allows GitLab team members to run various automation tasks on GitLab.com using Slack.
## Requesting access
GitLab team-members may need access to Chatops on GitLab.com for administration
GitLab team-members may need access to ChatOps on GitLab.com for administration
tasks such as:
- Configuring feature flags.
- Running `EXPLAIN` queries against the GitLab.com production replica.
- Get deployment status of all of our environments or for a specific commit: `/chatops run auto_deploy status [commit_sha]`
To request access to Chatops on GitLab.com:
To request access to ChatOps on GitLab.com:
1. Log into <https://ops.gitlab.net/users/sign_in> **using the same username** as for GitLab.com (you may have to rename it).
1. You could also use the "Sign in with" Google button to sign in, with your GitLab.com email address.
1. Ask one of your team members to add you to the `chatops` project in Ops. They can do it by running `/chatops run member add <username> gitlab-com/chatops --ops` command in the `#chat-ops-test` Slack channel.
1. If you had to change your username for GitLab.com on the first step, make sure [to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page) on [the team page](https://about.gitlab.com/company/team/).
1. Sign in to [Internal GitLab for Operations](https://ops.gitlab.net/users/sign_in)
with one of the following methods:
- The same username you use on GitLab.com. You may have to choose a different
username later.
- Clicking the **Sign in with Google** button to sign in with your GitLab.com email address.
1. Confirm that your username in [Internal GitLab for Operations](https://ops.gitlab.net/)
is the same as your username in [GitLab.com](https://gitlab.com/). If the usernames
don't match, update the username at [Internal GitLab for Operations](https://ops.gitlab.net/).
1. Comment in your onboarding issue, and tag your onboarding buddy and your manager.
Request they add you to the `ops` ChatOps project by running this command
in the `#chat-ops-test` Slack channel, replacing `<username>` with your username:
`/chatops run member add <username> gitlab-com/chatops --ops`
<!-- vale gitlab.FirstPerson = NO -->
> Hi `__BUDDY_HANDLE__` and `__MANAGER_HANDLE__`, could you please add me to
> the ChatOps project in Ops by running this command:
> `/chatops run member add <username> gitlab-com/chatops --ops` in the
> `#chat-ops-test` Slack channel? Thanks in advance.
<!-- vale gitlab.FirstPerson = YES -->
1. Ensure you've set up two-factor authentication.
1. After you're added to the ChatOps project, run this command to check your user
status and ensure you can execute commands in the `#chat-ops-test` Slack channel:
```plaintext
/chatops run user find <username>
```
The bot guides you through the process of allowing your user to execute
commands in the `#chat-ops-test` Slack channel.
1. If you had to change your username for GitLab.com on the first step, make sure
[to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page)
on [the team page](https://about.gitlab.com/company/team/).
## See also
- [Chatops Usage](../ci/chatops/README.md)
- [ChatOps Usage](../ci/chatops/README.md)
- [Understanding EXPLAIN plans](understanding_explain_plans.md)
- [Feature Groups](feature_flags/development.md#feature-groups)

View file

@ -6,6 +6,31 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
# Feature flags in development of GitLab
## When to use feature flags
Starting with GitLab 11.4, developers are required to use feature flags for
non-trivial changes. Such changes include:
- New features (e.g. a new merge request widget, epics, etc).
- Complex performance improvements that may require additional testing in
production, such as rewriting complex queries.
- Invasive changes to the user interface, such as a new navigation bar or the
removal of a sidebar.
- Adding support for importing projects from a third-party service.
- Risk of data loss
In all cases, those working on the changes can best decide if a feature flag is
necessary. For example, changing the color of a button doesn't need a feature
flag, while changing the navigation bar definitely needs one. In case you are
uncertain if a feature flag is necessary, simply ask about this in the merge
request, and those reviewing the changes will likely provide you with an answer.
When using a feature flag for UI elements, make sure to _also_ use a feature
flag for the underlying backend code, if there is any. This ensures there is
absolutely no way to use the feature until it is enabled.
## How to use Feature Flags
Feature flags can be used to gradually deploy changes, regardless of whether
they are new features or performance improvements. By using feature flags,
you can determine the impact of GitLab-directed changes, while still being able

View file

@ -53,27 +53,6 @@ problems, such as outages.
Please also read the [development guide for feature flags](development.md).
### When to use feature flags
Starting with GitLab 11.4, developers are required to use feature flags for
non-trivial changes. Such changes include:
- New features (e.g. a new merge request widget, epics, etc).
- Complex performance improvements that may require additional testing in
production, such as rewriting complex queries.
- Invasive changes to the user interface, such as a new navigation bar or the
removal of a sidebar.
- Adding support for importing projects from a third-party service.
In all cases, those working on the changes can best decide if a feature flag is
necessary. For example, changing the color of a button doesn't need a feature
flag, while changing the navigation bar definitely needs one. In case you are
uncertain if a feature flag is necessary, simply ask about this in the merge
request, and those reviewing the changes will likely provide you with an answer.
When using a feature flag for UI elements, make sure to _also_ use a feature
flag for the underlying backend code, if there is any. This ensures there is
absolutely no way to use the feature until it is enabled.
### Including a feature behind feature flag in the final release

View file

@ -661,9 +661,10 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
#### `if:` conditions
<!-- vale gitlab.Substitutions = NO -->
| `if:` conditions | Description | Notes |
|------------------|-------------|-------|
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success\|manual`), or **not** create a job for forks (by using `when: never`). |
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success|manual`), or **not** create a job for forks (by using `when: never`). |
| `if-not-ee` | Matches if the project isn't EE (i.e. project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
| `if-not-foss` | Matches if the project isn't FOSS (i.e. project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
| `if-default-refs` | Matches if the pipeline is for `master`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
@ -691,6 +692,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
| `if-rspec-fail-fast-disabled` | Limits jobs to pipelines with `$RSPEC_FAIL_FAST_ENABLED` variable not set to `"true"`. | |
| `if-rspec-fail-fast-skipped` | Matches if the pipeline is for a merge request and the MR title includes "SKIP RSPEC FAIL-FAST". | |
| `if-security-pipeline-merge-result` | Matches if the pipeline is for a security merge request triggered by `@gitlab-release-tools-bot`. | |
<!-- vale gitlab.Substitutions = YES -->
#### `changes:` patterns

View file

@ -385,48 +385,48 @@ The following table lists variables used to disable jobs.
| **Job Name** | **Variable** | **GitLab version** | **Description** |
|----------------------------------------|---------------------------------|-----------------------|-----------------|
| `.fuzz_base` | `COVFUZZ_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34984) | [Read more](../../user/application_security/coverage_fuzzing/) about how `.fuzz_base` provide capability for your own jobs. If the variable is present, your jobs won't be created. |
| `apifuzzer_fuzz` | `API_FUZZING_DISABLED` | [From GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39135) | If the variable is present, the job won't be created. |
| `bandit-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `brakeman-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `bundler-audit-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
| `.fuzz_base` | `COVFUZZ_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34984) | [Read more](../../user/application_security/coverage_fuzzing/) about how `.fuzz_base` provide capability for your own jobs. If the variable is present, your jobs aren't created. |
| `apifuzzer_fuzz` | `API_FUZZING_DISABLED` | [From GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39135) | If the variable is present, the job isn't created. |
| `bandit-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `brakeman-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `bundler-audit-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
| `canary` | `CANARY_ENABLED` | | This manual job is created if the variable is present. |
| `code_intelligence` | `CODE_INTELLIGENCE_DISABLED` | From GitLab 13.6 | If the variable is present, the job isn't created. |
| `codequality` | `CODE_QUALITY_DISABLED` | Until GitLab 11.0 | If the variable is present, the job won't be created. |
| `code_quality` | `CODE_QUALITY_DISABLED` | [From GitLab 11.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5773) | If the variable is present, the job won't be created. |
| `container_scanning` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `dast` | `DAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `dast_environment_deploy` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job won't be created. |
| `dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `eslint-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `flawfinder-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `gemnasium-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
| `gemnasium-maven-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
| `gemnasium-python-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
| `gosec-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `kubesec-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `license_management` | `LICENSE_MANAGEMENT_DISABLED` | GitLab 11.0 to 12.7 | If the variable is present, the job won't be created. Job deprecated [from GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) |
| `license_scanning` | `LICENSE_MANAGEMENT_DISABLED` | [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job won't be created. |
| `load_performance` | `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2 | If the variable is present, the job won't be created. |
| `nodejs-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `performance` | `PERFORMANCE_DISABLED` | From GitLab 11.0 | Browser performance. If the variable is present, the job won't be created. |
| `phpcs-security-audit-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `pmd-apex-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `retire-js-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
| `review` | `REVIEW_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `review:stop` | `REVIEW_DISABLED` | From GitLab 11.0 | Manual job. If the variable is present, the job won't be created. |
| `sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `sast:container` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `secret_detection` | `SECRET_DETECTION_DISABLED` | From GitLab 13.1 | If the variable is present, the job won't be created. |
| `secret_detection_default_branch` | `SECRET_DETECTION_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job won't be created. |
| `security-code-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `secrets-sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `sobelaw-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `stop_dast_environment` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job won't be created. |
| `spotbugs-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
| `test` | `TEST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
| `codequality` | `CODE_QUALITY_DISABLED` | Until GitLab 11.0 | If the variable is present, the job isn't created. |
| `code_quality` | `CODE_QUALITY_DISABLED` | [From GitLab 11.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5773) | If the variable is present, the job isn't created. |
| `container_scanning` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `dast` | `DAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `dast_environment_deploy` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job isn't created. |
| `dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `eslint-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `flawfinder-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `gemnasium-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
| `gemnasium-maven-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
| `gemnasium-python-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
| `gosec-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `kubesec-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `license_management` | `LICENSE_MANAGEMENT_DISABLED` | GitLab 11.0 to 12.7 | If the variable is present, the job isn't created. Job deprecated [from GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) |
| `license_scanning` | `LICENSE_MANAGEMENT_DISABLED` | [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. |
| `load_performance` | `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2 | If the variable is present, the job isn't created. |
| `nodejs-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `performance` | `PERFORMANCE_DISABLED` | From GitLab 11.0 | Browser performance. If the variable is present, the job isn't created. |
| `phpcs-security-audit-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `pmd-apex-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `retire-js-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
| `review` | `REVIEW_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `review:stop` | `REVIEW_DISABLED` | From GitLab 11.0 | Manual job. If the variable is present, the job isn't created. |
| `sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `sast:container` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `secret_detection` | `SECRET_DETECTION_DISABLED` | From GitLab 13.1 | If the variable is present, the job isn't created. |
| `secret_detection_default_branch` | `SECRET_DETECTION_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. |
| `security-code-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `secrets-sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `sobelaw-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `stop_dast_environment` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job isn't created. |
| `spotbugs-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `test` | `TEST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
| `staging` | `STAGING_ENABLED` | | The job is created if the variable is present. |
| `stop_review` | `REVIEW_DISABLED` | | If the variable is present, the job won't be created. |
| `stop_review` | `REVIEW_DISABLED` | | If the variable is present, the job isn't created. |
### Application secret variables

View file

@ -720,45 +720,43 @@ Repeat this configuration for each profile as needed.
## Running your first scan
When configured correctly, a CI/CD pipeline contains a `Fuzz` stage and a `apifuzzer_fuzz` job. The
job only fails when an invalid configuration is provided. During normal operation, the job always
succeeds even if faults are identified during fuzz testing.
When configured correctly, a CI/CD pipeline contains a `fuzz` stage and an `apifuzzer_fuzz` or
`apifuzzer_fuzz_dnd` job. The job only fails when an invalid configuration is provided. During
normal operation, the job always succeeds even if faults are identified during fuzz testing.
Faults are displayed on the **Tests** pipeline tab with the suite name **API-Fuzzing**. The **Name**
field on the **Tests** page includes the fuzz-tested operation and parameter. The **Trace** field
contains a writeup of the identified fault. This writeup contains information on what the fuzzer
tested and how it detected something wrong.
Faults are displayed on the **Security** pipeline tab with the suite name. When testing against the
repositories default branch, the fuzzing faults are also shown on the Security & Compliance's
Vulnerability Report page.
To prevent an excessive number of reported faults, the API fuzzing scanner limits the number of
faults it reports to one per parameter.
faults it reports.
### Fault Writeup
## Viewing fuzzing faults
The faults that API fuzzing finds aren't associated with a specific vulnerability type. They require
investigation to determine what type of issue they are and if they should be fixed. See
[handling false positives](#handling-false-positives) for information about configuration changes
you can make to limit the number of false positives reported.
The API Fuzzing analyzer produces a JSON report that is collected and used
[to populate the faults into GitLab's vulnerability screens](../index.md#view-details-of-an-api-fuzzing-vulnerability).
Fuzzing faults show up as vulnerabilities with a severity of Unknown.
This table contains a description of fields in an API fuzzing fault writeup.
The faults that API fuzzing finds require manual investigation and aren't associated with a specific
vulnerability type. They require investigation to determine if they are a security issue, and if
they should be fixed. See [handling false positives](#handling-false-positives)
for information about configuration changes you can make to limit the number of false positives
reported.
| Writeup Item | Description |
|:-------------|:------------|
| Operation | The operation tested. |
| Parameter | The field modified. This can be a path segment, header, query string, or body element. |
| Endpoint | The endpoint being tested. |
| Check | Check module producing the test. Checks can be turned on and off. |
| Assert | Assert module that detected a failure. Assertions can be configured and turned on and off. |
| CWE | Fuzzing faults always have the same CWE. |
| OWASP | Fuzzing faults always have the same OWASP ID. |
| Exploitability | Fuzzing faults always have an `unknown` exploitability. |
| Impact | Fuzzing faults always have an `unknown` risk impact. |
| Description | Verbose description of what the check did. Includes the original parameter value and the modified (mutated) value. |
| Detection | Why a failure was detected and reported. This is related to the Assert that was used. |
| Original Request | The original, unmodified HTTP request. Useful when reviewing the actual request to see what changes were made. |
| Actual Request | The request that produced the failure. This request has been modified in some way by the Check logic. |
| Actual Response | The response to the actual request. |
| Recorded Request | An unmodified request. |
| Recorded Response | The response to the unmodified request. You can compare this with the actual request when triaging this fault. |
For additional information, see
[View details of an API Fuzzing vulnerability](../index.md#view-details-of-an-api-fuzzing-vulnerability).
### Security Dashboard
Fuzzing faults show up as vulnerabilities with a severity of Unknown. The Security Dashboard is a
good place to get an overview of all the security vulnerabilities in your groups, projects and
pipelines. For more information, see the [Security Dashboard documentation](../security_dashboard/index.md).
### Interacting with the vulnerabilities
Fuzzing faults show up as vulnerabilities with a severity of Unknown.
Once a fault is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Handling False Positives

View file

@ -201,6 +201,43 @@ authorization credentials. By default, content of specific headers are masked in
reports. You can specify the list of all headers to be masked. For details, see
[Hide sensitive information](dast/index.md#hide-sensitive-information).
### View details of an API Fuzzing vulnerability
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.7.
Faults detected by API Fuzzing occur in the live web application, and require manual investigation
to determine if they are vulnerabilities. Fuzzing faults are included as vulnerabilities with a
severity of Unknown. To facilitate investigation of the fuzzing faults, detailed information is
provided about the HTTP messages sent and received along with a description of the modification(s)
made.
Follow these steps to view details of a fuzzing fault:
1. You can view faults in a project, or a merge request:
- In a project, go to the project's **{shield}** **Security & Compliance > Vulnerability Report**
page. This page shows all vulnerabilities from the default branch only.
- In a merge request, go the merge request's **Security** section and click the **Expand**
button. API Fuzzing faults are available in a section labeled
**API Fuzzing detected N potential vulnerabilities**. Click the title to display the fault
details.
1. Click the fault's title to display the fault's details. The table below describes these details.
| Field | Description |
|:-----------------|:------------------------------------------------------------------ |
| Description | Description of the fault including what was modified. |
| Project | Namespace and project in which the vulnerability was detected. |
| Method | HTTP method used to detect the vulnerability. |
| URL | URL at which the vulnerability was detected. |
| Request | The HTTP request that caused the fault. |
| Unmodified Response | Response from an unmodified request. This is what a normal working response looks like. |
| Actual Response | Response received from fuzzed request. |
| Evidence | How we determined a fault occurred. |
| Identifiers | The fuzzing check used to find this fault. |
| Severity | Severity of the finding is always Unknown. |
| Scanner Type | Scanner used to perform testing. |
### Dismissing a vulnerability
To dismiss a vulnerability, you must set its status to Dismissed. This dismisses the vulnerability

View file

@ -509,50 +509,38 @@ NOTE:
See [Rate limits](../../security/rate_limits.md) for administrator
documentation.
IP blocks usually happen when GitLab.com receives unusual traffic from a single
IP address that the system views as potentially malicious based on rate limit
settings. After the unusual traffic ceases, the IP address is automatically
released depending on the type of block, as described below.
When a request is rate limited, GitLab responds with a `429` status
code. The client should wait before attempting the request again. There
are also informational headers with this response detailed in [rate
limiting responses](#rate-limiting-responses).
If you receive a `403 Forbidden` error for all requests to GitLab.com, please
check for any automated processes that may be triggering a block. For
assistance, contact [GitLab Support](https://support.gitlab.com/hc/en-us)
with details, such as the affected IP address.
The following table describes the rate limits for GitLab.com, both before and
after the limits change in January, 2021:
### HAProxy API throttle
| Rate limit | Before 2021-01-18 | From 2021-01-18 |
|:--------------------------------------------------------------------------|:----------------------------|:------------------------------|
| **Protected paths** (for a given **IP address**) | **10** requests per minute | **10** requests per minute |
| **Raw endpoint** traffic (for a given **project, commit, and file path**) | **300** requests per minute | **300** requests per minute |
| **Unauthenticated** traffic (from a given **IP address**) | No specific limit | **500** requests per minute |
| **Authenticated** API traffic (for a given **user**) | No specific limit | **2,000** requests per minute |
| **Authenticated** non-API HTTP traffic (for a given **user**) | No specific limit | **1,000** requests per minute |
| **All** traffic (from a given **IP address**) | **600** requests per minute | **2,000** requests per minute |
GitLab.com responds with HTTP status code `429` to API requests that exceed 10
requests
per second per IP address.
More details are available on the rate limits for [protected
paths](#protected-paths-throttle) and [raw
endpoints](../../user/admin_area/settings/rate_limits_on_raw_endpoints.md).
The following example headers are included for all API requests:
### Rate limiting responses
```plaintext
RateLimit-Limit: 600
RateLimit-Observed: 6
RateLimit-Remaining: 594
RateLimit-Reset: 1563325137
RateLimit-ResetTime: Wed, 17 Jul 2019 00:58:57 GMT
```
The [`Retry-After`
header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
indicates when the client should retry.
Source:
Rate limits applied by HAProxy (instead of Cloudflare or the
GitLab application) have `RateLimit-Reset` and `RateLimit-ResetTime`
headers.
- Search for `rate_limit_http_rate_per_minute` and `rate_limit_sessions_per_second` in [GitLab.com's current HAProxy settings](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy/blob/master/attributes/default.rb).
### Pagination response headers
For performance reasons, if a query returns more than 10,000 records, GitLab
doesn't return the following headers:
- `x-total`.
- `x-total-pages`.
- `rel="last"` `link`.
### Rack Attack initializer
Details of rate limits enforced by [Rack Attack](../../security/rack_attack.md).
#### Protected paths throttle
### Protected paths throttle
GitLab.com responds with HTTP status code `429` to POST requests at protected
paths that exceed 10 requests per **minute** per IP address.
@ -568,6 +556,18 @@ Retry-After: 60
See [Protected Paths](../admin_area/settings/protected_paths.md) for more details.
### IP blocks
IP blocks can occur when GitLab.com receives unusual traffic from a single
IP address that the system views as potentially malicious, based on rate limit
settings. After the unusual traffic ceases, the IP address is automatically
released depending on the type of block, as described in a following section.
If you receive a `403 Forbidden` error for all requests to GitLab.com,
check for any automated processes that may be triggering a block. For
assistance, contact [GitLab Support](https://support.gitlab.com/hc/en-us)
with details, such as the affected IP address.
#### Git and container registry failed authentication ban
GitLab.com responds with HTTP status code `403` for 1 hour, if 30 failed
@ -585,13 +585,14 @@ This limit:
No response headers are provided.
### Admin Area settings
### Pagination response headers
GitLab.com:
For performance reasons, if a query returns more than 10,000 records, GitLab
doesn't return the following headers:
- Has [rate limits on raw endpoints](../../user/admin_area/settings/rate_limits_on_raw_endpoints.md)
set to the default.
- Does not have the user and IP rate limits settings enabled.
- `x-total`.
- `x-total-pages`.
- `rel="last"` `link`.
### Visibility settings

View file

@ -57,8 +57,6 @@ You can use GitLab as a source for your Docker images.
Prerequisites:
- Your images must be stored on [Docker Hub](https://hub.docker.com/).
- Docker Hub must be available. Follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/241639)
for progress on accessing images when Docker Hub is down.
### Authenticate with the Dependency Proxy
@ -119,6 +117,12 @@ dependency-proxy-pull-master:
- docker pull "$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX"/alpine:latest
```
`CI_DEPENDENCY_PROXY_SERVER` and `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` include the server port. So if you use `CI_DEPENDENCY_PROXY_SERVER` to log in, for example, you must explicitly include the port in your pull command and vice-versa:
```shell
docker pull gitlab.example.com:443/my-group/dependency_proxy/containers/alpine:latest
```
You can also use [custom environment variables](../../../ci/variables/README.md#custom-environment-variables) to store and access your personal access token or other valid credentials.
##### Authenticate with `DOCKER_AUTH_CONFIG`
@ -157,11 +161,23 @@ named `DOCKER_AUTH_CONFIG` with a value of:
}
```
To use `$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` when referencing images, you must explicitly include the port in your `DOCKER_AUTH_CONFIG` value:
```json
{
"auths": {
"https://gitlab.example.com:443": {
"auth": "(Base64 content from above)"
}
}
}
```
1. Now reference the Dependency Proxy in your base image:
```yaml
# .gitlab-ci.yml
image: "$CI_SERVER_HOST":"$CI_SERVER_PORT"/groupname/dependency_proxy/containers/node:latest
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:latest
...
```

View file

@ -10581,6 +10581,9 @@ msgstr ""
msgid "End Time"
msgstr ""
msgid "Ends at %{endsAt}"
msgstr ""
msgid "Ends at (UTC)"
msgstr ""
@ -19349,9 +19352,15 @@ msgstr ""
msgid "OnCallSchedules|Create on-call schedules in GitLab"
msgstr ""
msgid "OnCallSchedules|Delete rotation"
msgstr ""
msgid "OnCallSchedules|Delete schedule"
msgstr ""
msgid "OnCallSchedules|Edit rotation"
msgstr ""
msgid "OnCallSchedules|Edit schedule"
msgstr ""
@ -19367,7 +19376,7 @@ msgstr ""
msgid "OnCallSchedules|On-call schedule"
msgstr ""
msgid "OnCallSchedules|On-call schedule for the %{tzShort}"
msgid "OnCallSchedules|On-call schedule for the %{timezone}"
msgstr ""
msgid "OnCallSchedules|Rotation length"
@ -19720,9 +19729,6 @@ msgstr ""
msgid "Owner"
msgstr ""
msgid "PST"
msgstr ""
msgid "Package Registry"
msgstr ""
@ -26399,6 +26405,9 @@ msgstr ""
msgid "Starts %{startsIn}"
msgstr ""
msgid "Starts at %{startsAt}"
msgstr ""
msgid "Starts at (UTC)"
msgstr ""
@ -27201,6 +27210,12 @@ msgstr ""
msgid "Terraform|An error occurred while loading your Terraform States"
msgstr ""
msgid "Terraform|Are you sure you want to remove the Terraform State %{name}?"
msgstr ""
msgid "Terraform|Cancel"
msgstr ""
msgid "Terraform|Details"
msgstr ""
@ -27234,6 +27249,12 @@ msgstr ""
msgid "Terraform|Pipeline"
msgstr ""
msgid "Terraform|Remove"
msgstr ""
msgid "Terraform|Remove state file and versions"
msgstr ""
msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
msgstr ""
@ -27246,12 +27267,18 @@ msgstr ""
msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
msgstr ""
msgid "Terraform|To remove the State file and its versions, type %{name} to confirm:"
msgstr ""
msgid "Terraform|Unknown User"
msgstr ""
msgid "Terraform|Unlock"
msgstr ""
msgid "Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously\twill remain intact, only the state file with all its versions are to be removed. This action is non-revertible."
msgstr ""
msgid "Test"
msgstr ""

View file

@ -16,121 +16,4 @@ RSpec.describe Profiles::GpgKeysController do
end.to change { GpgKey.count }.by(1)
end
end
describe "#get_keys" do
describe "non existent user" do
it "does not generally work" do
get :get_keys, params: { username: 'not-existent' }
expect(response).not_to be_successful
end
end
describe "user with no keys" do
it "does generally work" do
get :get_keys, params: { username: user.username }
expect(response).to be_successful
end
it "renders all keys separated with a new line" do
get :get_keys, params: { username: user.username }
expect(response.body).to eq("")
end
it "responds with text/plain content type" do
get :get_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
describe "user with keys" do
let!(:gpg_key) { create(:gpg_key, user: user) }
let!(:another_gpg_key) { create(:another_gpg_key, user: user) }
describe "while signed in" do
before do
sign_in(user)
end
it "does generally work" do
get :get_keys, params: { username: user.username }
expect(response).to be_successful
end
it "renders all verified keys separated with a new line" do
get :get_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n"))
expect(response.body).to include(gpg_key.key)
expect(response.body).to include(another_gpg_key.key)
end
it "responds with text/plain content type" do
get :get_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
describe 'when logged out' do
before do
sign_out(user)
end
it "still does generally work" do
get :get_keys, params: { username: user.username }
expect(response).to be_successful
end
it "renders all verified keys separated with a new line" do
get :get_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to eq(user.gpg_keys.map(&:key).join("\n"))
expect(response.body).to include(gpg_key.key)
expect(response.body).to include(another_gpg_key.key)
end
it "responds with text/plain content type" do
get :get_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
describe 'when revoked' do
before do
sign_in(user)
another_gpg_key.revoke
end
it "doesn't render revoked keys" do
get :get_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to include(gpg_key.key)
expect(response.body).not_to include(another_gpg_key.key)
end
it "doesn't render revoked keys for non-authorized users" do
sign_out(user)
get :get_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to include(gpg_key.key)
expect(response.body).not_to include(another_gpg_key.key)
end
end
end
end
end

View file

@ -38,7 +38,7 @@ RSpec.describe Projects::Alerting::NotificationsController do
expect(notify_service_class)
.to have_received(:new)
.with(project, nil, permitted_params)
.with(project, permitted_params)
end
end

View file

@ -168,7 +168,7 @@ RSpec.describe Projects::Prometheus::AlertsController do
expect(Projects::Prometheus::Alerts::NotifyService)
.to receive(:new)
.with(project, nil, duck_type(:permitted?))
.with(project, duck_type(:permitted?))
.and_return(notify_service)
end

View file

@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe UsersController do
let(:user) { create(:user) }
# This user should have the same e-mail address associated with the GPG key prepared for tests
let(:user) { create(:user, email: GpgHelpers::User1.emails[0]) }
let(:private_user) { create(:user, private_profile: true) }
let(:public_user) { create(:user) }
@ -326,6 +327,123 @@ RSpec.describe UsersController do
end
end
describe "#gpg_keys" do
describe "non existent user" do
it "does not generally work" do
get :gpg_keys, params: { username: 'not-existent' }
expect(response).not_to be_successful
end
end
describe "user with no keys" do
it "does generally work" do
get :gpg_keys, params: { username: user.username }
expect(response).to be_successful
end
it "renders all keys separated with a new line" do
get :gpg_keys, params: { username: user.username }
expect(response.body).to eq("")
end
it "responds with text/plain content type" do
get :gpg_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
describe "user with keys" do
let!(:gpg_key) { create(:gpg_key, user: user) }
let!(:another_gpg_key) { create(:another_gpg_key, user: user) }
describe "while signed in" do
before do
sign_in(user)
end
it "does generally work" do
get :gpg_keys, params: { username: user.username }
expect(response).to be_successful
end
it "renders all verified keys separated with a new line" do
get :gpg_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n"))
expect(response.body).to include(gpg_key.key)
expect(response.body).to include(another_gpg_key.key)
end
it "responds with text/plain content type" do
get :gpg_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
describe 'when logged out' do
before do
sign_out(user)
end
it "still does generally work" do
get :gpg_keys, params: { username: user.username }
expect(response).to be_successful
end
it "renders all verified keys separated with a new line" do
get :gpg_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to eq(user.gpg_keys.map(&:key).join("\n"))
expect(response.body).to include(gpg_key.key)
expect(response.body).to include(another_gpg_key.key)
end
it "responds with text/plain content type" do
get :gpg_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
describe 'when revoked' do
before do
sign_in(user)
another_gpg_key.revoke
end
it "doesn't render revoked keys" do
get :gpg_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to include(gpg_key.key)
expect(response.body).not_to include(another_gpg_key.key)
end
it "doesn't render revoked keys for non-authorized users" do
sign_out(user)
get :gpg_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to include(gpg_key.key)
expect(response.body).not_to include(another_gpg_key.key)
end
end
end
end
describe 'GET #calendar' do
context 'for user' do
let(:project) { create(:project) }

View file

@ -54,6 +54,24 @@ RSpec.describe 'Terraform', :js do
expect(page).to have_content(terraform_state.name)
end
end
context 'when clicking on the delete button' do
let(:additional_state) { create(:terraform_state, project: project) }
it 'removes the state', :aggregate_failures do
visit project_terraform_index_path(project)
expect(page).to have_content(additional_state.name)
find("[data-testid='terraform-state-actions-#{additional_state.name}']").click
find('[data-testid="terraform-state-remove"]').click
fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name
click_button 'Remove'
expect(page).not_to have_content(additional_state.name)
expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end
end

View file

@ -0,0 +1,12 @@
export function stubComponent(Component, options = {}) {
return {
props: Component.props,
model: Component.model,
// Do not render any slots/scoped slots except default
// This differs from VTU behavior which renders all slots
template: '<div><slot></slot></div>',
// allows wrapper.find(Component) to work for stub
$_vueTestUtils_original: Component,
...options,
};
}

View file

@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlTokenSelector } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import { stubComponent } from 'helpers/stub_component';
import Api from '~/api';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
@ -17,6 +18,9 @@ const createComponent = () => {
ariaLabelledby: label,
placeholder,
},
stubs: {
GlTokenSelector: stubComponent(GlTokenSelector),
},
});
};

View file

@ -1,13 +1,9 @@
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import { metricStates } from '~/monitoring/constants';
const MockGlEmptyState = {
props: GlEmptyState.props,
template: '<div><slot name="description"></slot></div>',
};
function createComponent(props) {
return shallowMount(GroupEmptyState, {
propsData: {
@ -17,7 +13,9 @@ function createComponent(props) {
svgPath: '/path/to/empty-group-illustration.svg',
},
stubs: {
GlEmptyState: MockGlEmptyState,
GlEmptyState: stubComponent(GlEmptyState, {
template: '<div><slot name="description"></slot></div>',
}),
},
});
}
@ -47,7 +45,7 @@ describe('GroupEmptyState', () => {
});
it('passes the expected props to GlEmptyState', () => {
expect(wrapper.find(MockGlEmptyState).props()).toMatchSnapshot();
expect(wrapper.find(GlEmptyState).props()).toMatchSnapshot();
});
});
});

View file

@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { stubComponent } from 'helpers/stub_component';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
@ -21,10 +22,9 @@ describe('Package History', () => {
wrapper = shallowMount(component, {
propsData: { ...defaultProps, ...props },
stubs: {
HistoryItem: {
props: HistoryItem.props,
HistoryItem: stubComponent(HistoryItem, {
template: '<div data-testid="history-element"><slot></slot></div>',
},
}),
GlSprintf,
},
});

View file

@ -1,18 +1,12 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue';
describe('Pipeline Status Token', () => {
let wrapper;
const stubs = {
GlFilteredSearchToken: {
props: GlFilteredSearchToken.props,
template: `<div><slot name="suggestions"></slot></div>`,
},
};
const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken);
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
const findAllGlIcons = () => wrapper.findAll(GlIcon);
@ -33,7 +27,11 @@ describe('Pipeline Status Token', () => {
propsData: {
...defaultProps,
},
stubs,
stubs: {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
template: `<div><slot name="suggestions"></slot></div>`,
}),
},
});
};

View file

@ -1,4 +1,5 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { stubComponent } from 'helpers/stub_component';
import { shallowMount } from '@vue/test-utils';
import Api from '~/api';
import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
@ -7,14 +8,7 @@ import { users } from '../mock_data';
describe('Pipeline Trigger Author Token', () => {
let wrapper;
const stubs = {
GlFilteredSearchToken: {
props: GlFilteredSearchToken.props,
template: `<div><slot name="suggestions"></slot></div>`,
},
};
const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken);
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
@ -42,7 +36,11 @@ describe('Pipeline Trigger Author Token', () => {
...data,
};
},
stubs,
stubs: {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
template: `<div><slot name="suggestions"></slot></div>`,
}),
},
});
};

View file

@ -2,9 +2,7 @@
exports[`TagsLoader component has the correct markup 1`] = `
<div>
<div
preserve-aspect-ratio="xMinYMax meet"
>
<div>
<rect
height="15"
rx="4"

View file

@ -1,10 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Registry Group Empty state to match the default snapshot 1`] = `
<div
svg-path="foo"
title="There are no container images available in this group"
>
<div>
<p>
With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here.
<gl-link-stub

View file

@ -1,10 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Registry Project Empty state to match the default snapshot 1`] = `
<div
svg-path="bazFoo"
title="There are no container images stored for this project"
>
<div>
<p>
With the Container Registry, every project can have its own space to store its Docker images.
<gl-link-stub

View file

@ -135,13 +135,13 @@ describe('List Page', () => {
it('empty state should have an svg-path', () => {
mountComponent({ config });
expect(findEmptyState().attributes('svg-path')).toBe(config.containersErrorImage);
expect(findEmptyState().props('svgPath')).toBe(config.containersErrorImage);
});
it('empty state should have a description', () => {
mountComponent({ config });
expect(findEmptyState().html()).toContain('connection error');
expect(findEmptyState().props('title')).toContain('connection error');
});
it('should not show the loading or default state', () => {

View file

@ -1,35 +1,33 @@
import {
GlModal as RealGlModal,
GlEmptyState as RealGlEmptyState,
GlSkeletonLoader as RealGlSkeletonLoader,
} from '@gitlab/ui';
import { RouterLinkStub } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
import RealListItem from '~/vue_shared/components/registry/list_item.vue';
export const GlModal = {
export const GlModal = stubComponent(RealGlModal, {
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
methods: {
show: jest.fn(),
},
};
});
export const GlEmptyState = {
export const GlEmptyState = stubComponent(RealGlEmptyState, {
template: '<div><slot name="description"></slot></div>',
name: 'GlEmptyStateSTub',
};
});
export const RouterLink = {
template: `<div><slot></slot></div>`,
props: ['to'],
};
export const RouterLink = RouterLinkStub;
export const DeleteModal = {
template: '<div></div>',
export const DeleteModal = stubComponent(RealDeleteModal, {
methods: {
show: jest.fn(),
},
props: RealDeleteModal.props,
};
});
export const GlSkeletonLoader = {
template: `<div><slot></slot></div>`,
props: ['width', 'height'],
};
export const GlSkeletonLoader = stubComponent(RealGlSkeletonLoader);
export const ListItem = {
...RealListItem,

View file

@ -1,9 +1,10 @@
import { GlDropdown } from '@gitlab/ui';
import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo';
import StateActions from '~/terraform/components/states_table_actions.vue';
import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql';
import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql';
import unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql';
const localVue = createLocalVue();
@ -11,6 +12,7 @@ localVue.use(VueApollo);
describe('StatesTableActions', () => {
let lockResponse;
let removeResponse;
let unlockResponse;
let wrapper;
@ -26,12 +28,17 @@ describe('StatesTableActions', () => {
const createMockApolloProvider = () => {
lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } });
removeResponse = jest
.fn()
.mockResolvedValue({ data: { terraformStateDelete: { errors: [] } } });
unlockResponse = jest
.fn()
.mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } });
return createMockApollo([
[lockStateMutation, lockResponse],
[removeStateMutation, removeResponse],
[unlockStateMutation, unlockResponse],
]);
};
@ -43,7 +50,7 @@ describe('StatesTableActions', () => {
apolloProvider,
localVue,
propsData,
stubs: { GlDropdown },
stubs: { GlDropdown, GlModal, GlSprintf },
});
return wrapper.vm.$nextTick();
@ -52,6 +59,8 @@ describe('StatesTableActions', () => {
const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]');
const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]');
const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
const findRemoveBtn = () => wrapper.find('[data-testid="terraform-state-remove"]');
const findRemoveModal = () => wrapper.find(GlModal);
beforeEach(() => {
return createComponent();
@ -59,6 +68,7 @@ describe('StatesTableActions', () => {
afterEach(() => {
lockResponse = null;
removeResponse = null;
unlockResponse = null;
wrapper.destroy();
});
@ -137,4 +147,43 @@ describe('StatesTableActions', () => {
});
});
});
describe('remove button', () => {
it('displays a remove button', () => {
expect(findRemoveBtn().text()).toBe(StateActions.i18n.remove);
});
describe('when clicking the remove button', () => {
beforeEach(() => {
findRemoveBtn().vm.$emit('click');
return wrapper.vm.$nextTick();
});
it('displays a remove modal', () => {
expect(findRemoveModal().text()).toContain(
`You are about to remove the State file ${defaultProps.state.name}`,
);
});
describe('when submitting the remove modal', () => {
it('does not call the remove mutation when state name is missing', async () => {
findRemoveModal().vm.$emit('ok');
await wrapper.vm.$nextTick();
expect(removeResponse).not.toHaveBeenCalledWith();
});
it('calls the remove mutation when state name is present', async () => {
await wrapper.setData({ removeConfirmText: defaultProps.state.name });
findRemoveModal().vm.$emit('ok');
await wrapper.vm.$nextTick();
expect(removeResponse).toHaveBeenCalledWith({
stateID: defaultProps.state.id,
});
});
});
});
});
});

View file

@ -29,7 +29,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
total_time_spent reference author merged_at commit_count current_user_todos
conflicts auto_merge_enabled approved_by source_branch_protected
default_merge_commit_message_with_description squash_on_merge available_auto_merge_strategies
has_ci mergeable commits_without_merge_commits
has_ci mergeable commits_without_merge_commits security_auto_fix
]
if Gitlab.ee?

View file

@ -49,5 +49,14 @@ RSpec.describe Emails::Releases do
is_expected.to have_body_text('Release notes:')
is_expected.to have_body_text(release.description)
end
context 'release notes with attachment' do
let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
let(:release) { create(:release, project: project, description: "Attachment: [Test file](#{upload_path})") }
it 'renders absolute links' do
is_expected.to have_body_text(%Q(<a href="#{project.web_url}#{upload_path}" data-link="true" class="gfm">Test file</a>))
end
end
end
end

View file

@ -4,6 +4,7 @@ require 'spec_helper'
# user GET /:username
# user_ssh_keys GET /:username.keys
# user_gpg_keys GET /:username.gpg
# user_groups GET /users/:username/groups(.:format)
# user_projects GET /users/:username/projects(.:format)
# user_contributed_projects GET /users/:username/contributed(.:format)
@ -17,6 +18,12 @@ RSpec.describe UsersController, "routing" do
expect(get("/User")).to route_to('users#show', username: 'User')
end
it "to #gpg_keys" do
allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
expect(get("/User.gpg")).to route_to('users#gpg_keys', username: 'User')
end
it "to #groups" do
expect(get("/users/User/groups")).to route_to('users#groups', username: 'User')
end
@ -197,12 +204,6 @@ RSpec.describe Profiles::GpgKeysController, "routing" do
it "to #destroy" do
expect(delete("/profile/gpg_keys/1")).to route_to('profiles/gpg_keys#destroy', id: '1')
end
it "to #get_keys" do
allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
expect(get("/foo.gpg")).to route_to('profiles/gpg_keys#get_keys', username: 'foo')
end
end
# emails GET /emails(.:format) emails#index

View file

@ -10,7 +10,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
end
describe '#execute' do
let(:service) { described_class.new(project, nil, payload) }
let(:service) { described_class.new(project, payload) }
let(:auto_close_incident) { true }
let(:create_issue) { true }
let(:send_email) { true }

View file

@ -13,7 +13,7 @@ RSpec.describe Projects::Alerting::NotifyService do
let(:token) { 'invalid-token' }
let(:starts_at) { Time.current.change(usec: 0) }
let(:fingerprint) { 'testing' }
let(:service) { described_class.new(project, nil, payload) }
let(:service) { described_class.new(project, payload) }
let_it_be(:environment) { create(:environment, project: project) }
let(:environment) { create(:environment, project: project) }
let(:ended_at) { nil }

View file

@ -7,7 +7,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
let_it_be(:project, reload: true) { create(:project) }
let(:service) { described_class.new(project, nil, payload) }
let(:service) { described_class.new(project, payload) }
let(:token_input) { 'token' }
let!(:setting) do
@ -218,7 +218,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
it 'processes Prometheus alerts' do
expect(AlertManagement::ProcessPrometheusAlertService)
.to receive(:new)
.with(project, nil, kind_of(Hash))
.with(project, kind_of(Hash))
.exactly(3).times
.and_return(process_service)
expect(process_service).to receive(:execute).exactly(3).times

View file

@ -125,6 +125,9 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do
where(:description, :path) do
'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch'
'request to git-upload-pack' | '/root/rouge.git/git-upload-pack'
'user sign out' | '/users/sign_out'
'admin session' | '/admin/session'
'admin session destroy' | '/admin/session/destroy'
end
with_them do