Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8e2f50b44d
commit
c9ebdf468d
21 changed files with 652 additions and 542 deletions
|
@ -273,6 +273,13 @@ export default {
|
|||
this.toggleIssueState();
|
||||
}
|
||||
},
|
||||
handleEnter() {
|
||||
if (this.hasDrafts) {
|
||||
this.handleSaveDraft();
|
||||
} else {
|
||||
this.handleSave();
|
||||
}
|
||||
},
|
||||
toggleIssueState() {
|
||||
if (this.isIssue) {
|
||||
// We want to invoke the close/reopen logic in the issue header
|
||||
|
@ -395,8 +402,8 @@ export default {
|
|||
:aria-label="$options.i18n.comment"
|
||||
:placeholder="$options.i18n.bodyPlaceholder"
|
||||
@keydown.up="editCurrentUserLastNote()"
|
||||
@keydown.meta.enter="handleSave()"
|
||||
@keydown.ctrl.enter="handleSave()"
|
||||
@keydown.meta.enter="handleEnter()"
|
||||
@keydown.ctrl.enter="handleEnter()"
|
||||
></textarea>
|
||||
</template>
|
||||
</markdown-field>
|
||||
|
|
|
@ -4,13 +4,14 @@ import createFlash from '~/flash';
|
|||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
import { scrollUp } from '~/lib/utils/scroll_utils';
|
||||
import { __ } from '~/locale';
|
||||
import { PAGE_SIZE } from '~/releases/constants';
|
||||
import { PAGE_SIZE, RELEASED_AT_DESC } from '~/releases/constants';
|
||||
import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
|
||||
import { convertAllReleasesGraphQLResponse } from '~/releases/util';
|
||||
import ReleaseBlock from './release_block.vue';
|
||||
import ReleaseSkeletonLoader from './release_skeleton_loader.vue';
|
||||
import ReleasesEmptyState from './releases_empty_state.vue';
|
||||
import ReleasesPaginationApolloClient from './releases_pagination_apollo_client.vue';
|
||||
import ReleasesSortApolloClient from './releases_sort_apollo_client.vue';
|
||||
|
||||
export default {
|
||||
name: 'ReleasesIndexApolloClientApp',
|
||||
|
@ -20,6 +21,7 @@ export default {
|
|||
ReleaseSkeletonLoader,
|
||||
ReleasesEmptyState,
|
||||
ReleasesPaginationApolloClient,
|
||||
ReleasesSortApolloClient,
|
||||
},
|
||||
inject: {
|
||||
projectPath: {
|
||||
|
@ -56,6 +58,7 @@ export default {
|
|||
before: getParameterByName('before'),
|
||||
after: getParameterByName('after'),
|
||||
},
|
||||
sort: RELEASED_AT_DESC,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -76,6 +79,7 @@ export default {
|
|||
return {
|
||||
fullPath: this.projectPath,
|
||||
...paginationParams,
|
||||
sort: this.sort,
|
||||
};
|
||||
},
|
||||
isLoading() {
|
||||
|
@ -124,6 +128,9 @@ export default {
|
|||
window.removeEventListener('popstate', this.updateQueryParamsFromUrl);
|
||||
},
|
||||
methods: {
|
||||
getReleaseKey(release, index) {
|
||||
return [release.tagNamerstrs, release.name, index].join('|');
|
||||
},
|
||||
updateQueryParamsFromUrl() {
|
||||
this.cursors.before = getParameterByName('before');
|
||||
this.cursors.after = getParameterByName('after');
|
||||
|
@ -148,6 +155,8 @@ export default {
|
|||
<template>
|
||||
<div class="flex flex-column mt-2">
|
||||
<div class="gl-align-self-end gl-mb-3">
|
||||
<releases-sort-apollo-client v-model="sort" class="gl-mr-2" />
|
||||
|
||||
<gl-button
|
||||
v-if="newReleasePath"
|
||||
:href="newReleasePath"
|
||||
|
@ -165,7 +174,7 @@ export default {
|
|||
<div v-else-if="shouldRenderSuccessState">
|
||||
<release-block
|
||||
v-for="(release, index) in releases"
|
||||
:key="index"
|
||||
:key="getReleaseKey(release, index)"
|
||||
:release="release"
|
||||
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlSorting, GlSortingItem } from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { ASCENDING_ODER, DESCENDING_ORDER, SORT_OPTIONS } from '../constants';
|
||||
import { ASCENDING_ORDER, DESCENDING_ORDER, SORT_OPTIONS } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ReleasesSort',
|
||||
|
@ -22,13 +22,13 @@ export default {
|
|||
return option.label;
|
||||
},
|
||||
isSortAscending() {
|
||||
return this.sort === ASCENDING_ODER;
|
||||
return this.sort === ASCENDING_ORDER;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('index', ['setSorting']),
|
||||
onDirectionChange() {
|
||||
const sort = this.isSortAscending ? DESCENDING_ORDER : ASCENDING_ODER;
|
||||
const sort = this.isSortAscending ? DESCENDING_ORDER : ASCENDING_ORDER;
|
||||
this.setSorting({ sort });
|
||||
this.$emit('sort:changed');
|
||||
},
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<script>
|
||||
import { GlSorting, GlSortingItem } from '@gitlab/ui';
|
||||
import {
|
||||
ASCENDING_ORDER,
|
||||
DESCENDING_ORDER,
|
||||
SORT_OPTIONS,
|
||||
RELEASED_AT,
|
||||
CREATED_AT,
|
||||
RELEASED_AT_ASC,
|
||||
RELEASED_AT_DESC,
|
||||
CREATED_ASC,
|
||||
ALL_SORTS,
|
||||
SORT_MAP,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ReleasesSortApolloclient',
|
||||
components: {
|
||||
GlSorting,
|
||||
GlSortingItem,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (sort) => ALL_SORTS.includes(sort),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
orderBy() {
|
||||
if (this.value === RELEASED_AT_ASC || this.value === RELEASED_AT_DESC) {
|
||||
return RELEASED_AT;
|
||||
}
|
||||
|
||||
return CREATED_AT;
|
||||
},
|
||||
direction() {
|
||||
if (this.value === RELEASED_AT_ASC || this.value === CREATED_ASC) {
|
||||
return ASCENDING_ORDER;
|
||||
}
|
||||
|
||||
return DESCENDING_ORDER;
|
||||
},
|
||||
sortOptions() {
|
||||
return SORT_OPTIONS;
|
||||
},
|
||||
sortText() {
|
||||
return this.sortOptions.find((s) => s.orderBy === this.orderBy).label;
|
||||
},
|
||||
isDirectionAscending() {
|
||||
return this.direction === ASCENDING_ORDER;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDirectionChange() {
|
||||
const direction = this.isDirectionAscending ? DESCENDING_ORDER : ASCENDING_ORDER;
|
||||
this.emitInputEventIfChanged(this.orderBy, direction);
|
||||
},
|
||||
onSortItemClick(item) {
|
||||
this.emitInputEventIfChanged(item.orderBy, this.direction);
|
||||
},
|
||||
isActiveSortItem(item) {
|
||||
return this.orderBy === item.orderBy;
|
||||
},
|
||||
emitInputEventIfChanged(orderBy, direction) {
|
||||
const newSort = SORT_MAP[orderBy][direction];
|
||||
if (newSort !== this.value) {
|
||||
this.$emit('input', SORT_MAP[orderBy][direction]);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-sorting
|
||||
:text="sortText"
|
||||
:is-ascending="isDirectionAscending"
|
||||
@sortDirectionChange="onDirectionChange"
|
||||
>
|
||||
<gl-sorting-item
|
||||
v-for="item of sortOptions"
|
||||
:key="item.orderBy"
|
||||
:active="isActiveSortItem(item)"
|
||||
@click="onSortItemClick(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</gl-sorting-item>
|
||||
</gl-sorting>
|
||||
</template>
|
|
@ -15,7 +15,7 @@ export const DEFAULT_ASSET_LINK_TYPE = ASSET_LINK_TYPE.OTHER;
|
|||
|
||||
export const PAGE_SIZE = 10;
|
||||
|
||||
export const ASCENDING_ODER = 'asc';
|
||||
export const ASCENDING_ORDER = 'asc';
|
||||
export const DESCENDING_ORDER = 'desc';
|
||||
export const RELEASED_AT = 'released_at';
|
||||
export const CREATED_AT = 'created_at';
|
||||
|
@ -30,3 +30,20 @@ export const SORT_OPTIONS = [
|
|||
label: __('Created date'),
|
||||
},
|
||||
];
|
||||
|
||||
export const RELEASED_AT_ASC = 'RELEASED_AT_ASC';
|
||||
export const RELEASED_AT_DESC = 'RELEASED_AT_DESC';
|
||||
export const CREATED_ASC = 'CREATED_ASC';
|
||||
export const CREATED_DESC = 'CREATED_DESC';
|
||||
export const ALL_SORTS = [RELEASED_AT_ASC, RELEASED_AT_DESC, CREATED_ASC, CREATED_DESC];
|
||||
|
||||
export const SORT_MAP = {
|
||||
[RELEASED_AT]: {
|
||||
[ASCENDING_ORDER]: RELEASED_AT_ASC,
|
||||
[DESCENDING_ORDER]: RELEASED_AT_DESC,
|
||||
},
|
||||
[CREATED_AT]: {
|
||||
[ASCENDING_ORDER]: CREATED_ASC,
|
||||
[DESCENDING_ORDER]: CREATED_DESC,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
body.gl-dark {
|
||||
--gray-50: #303030;
|
||||
--gray-100: #404040;
|
||||
--gray-200: #525252;
|
||||
--gray-950: #fff;
|
||||
--green-100: #0d532a;
|
||||
--green-400: #108548;
|
||||
|
@ -28,8 +27,7 @@ html {
|
|||
line-height: 1.15;
|
||||
}
|
||||
aside,
|
||||
header,
|
||||
nav {
|
||||
header {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
|
@ -96,35 +94,24 @@ button {
|
|||
cursor: pointer;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
[type="button"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
[type="button"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: #fafafa;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
font-size: 2.1875rem;
|
||||
}
|
||||
.list-unstyled {
|
||||
|
@ -246,9 +233,6 @@ h1,
|
|||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
.nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -322,17 +306,6 @@ h1,
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
background-color: #333;
|
||||
background-clip: border-box;
|
||||
border: 1px solid #404040;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25em 0.4em;
|
||||
|
@ -358,20 +331,6 @@ h1,
|
|||
padding-left: 0.6em;
|
||||
border-radius: 10rem;
|
||||
}
|
||||
.close {
|
||||
float: right;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 #333;
|
||||
opacity: 0.5;
|
||||
}
|
||||
button.close {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
.rounded-circle {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
@ -460,12 +419,10 @@ body,
|
|||
}
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="submit"],
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -546,38 +503,9 @@ body {
|
|||
color: #dbdbdb;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
img.emoji {
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
width: 20px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.chart {
|
||||
overflow: hidden;
|
||||
height: 220px;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.show.dropdown .dropdown-menu {
|
||||
transform: translateY(0);
|
||||
display: block;
|
||||
min-height: 40px;
|
||||
max-height: 312px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.show.dropdown .dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.show.dropdown .dropdown-menu-toggle,
|
||||
.show.dropdown .dropdown-menu-toggle {
|
||||
border-color: #c4c4c4;
|
||||
}
|
||||
.show.dropdown [data-toggle="dropdown"] {
|
||||
outline: 0;
|
||||
}
|
||||
.search-input-container .dropdown-menu {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
@ -945,15 +873,6 @@ input {
|
|||
float: none;
|
||||
}
|
||||
}
|
||||
.header-user.show .dropdown-menu {
|
||||
margin-top: 4px;
|
||||
color: var(--gl-text-color, #fafafa);
|
||||
left: auto;
|
||||
max-height: 445px;
|
||||
}
|
||||
.header-user.show .dropdown-menu svg {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.header-user-avatar {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
|
@ -984,9 +903,6 @@ input {
|
|||
.tanuki-logo .tanuki-right-cheek {
|
||||
fill: #fca326;
|
||||
}
|
||||
.card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.context-header {
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
|
@ -2008,9 +1924,6 @@ body.sidebar-refactoring
|
|||
.avatar.s32 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
#content-body {
|
||||
display: block;
|
||||
}
|
||||
body.gl-dark .navbar-gitlab {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
@ -2025,12 +1938,8 @@ body.gl-dark .navbar-gitlab .container-fluid .navbar-toggler svg {
|
|||
}
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav > li.active > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav > li.active > button,
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav > li.dropdown.show > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav > li.dropdown.show > button,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav > li.active > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav > li.active > button,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav > li.dropdown.show > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav > li.dropdown.show > button {
|
||||
body.gl-dark .navbar-gitlab .navbar-nav > li.active > button {
|
||||
color: #fafafa;
|
||||
background-color: #333;
|
||||
}
|
||||
|
@ -2059,13 +1968,11 @@ body.gl-dark
|
|||
.header-user-avatar {
|
||||
border-color: #fafafa;
|
||||
}
|
||||
body.gl-dark .navbar-gitlab .nav > li.active > a,
|
||||
body.gl-dark .navbar-gitlab .nav > li.dropdown.show > a {
|
||||
body.gl-dark .navbar-gitlab .nav > li.active > a {
|
||||
color: #fafafa;
|
||||
background-color: #333;
|
||||
}
|
||||
body.gl-dark .navbar-gitlab .nav > li.active > a .notification-dot,
|
||||
body.gl-dark .navbar-gitlab .nav > li.dropdown.show > a .notification-dot {
|
||||
body.gl-dark .navbar-gitlab .nav > li.active > a .notification-dot {
|
||||
border-color: #333;
|
||||
}
|
||||
body.gl-dark
|
||||
|
@ -2073,12 +1980,6 @@ body.gl-dark
|
|||
.nav
|
||||
> li.active
|
||||
> a.header-help-dropdown-toggle
|
||||
.notification-dot,
|
||||
body.gl-dark
|
||||
.navbar-gitlab
|
||||
.nav
|
||||
> li.dropdown.show
|
||||
> a.header-help-dropdown-toggle
|
||||
.notification-dot {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
@ -2116,12 +2017,8 @@ body.gl-dark .navbar-gitlab {
|
|||
}
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav li.active > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav li.active > button,
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav li.dropdown.show > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-sub-nav li.dropdown.show > button,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav li.active > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav li.active > button,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav li.dropdown.show > a,
|
||||
body.gl-dark .navbar-gitlab .navbar-nav li.dropdown.show > button {
|
||||
body.gl-dark .navbar-gitlab .navbar-nav li.active > button {
|
||||
color: var(--gl-text-color);
|
||||
background-color: var(--gray-200);
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@ html {
|
|||
line-height: 1.15;
|
||||
}
|
||||
aside,
|
||||
header,
|
||||
nav {
|
||||
header {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
|
@ -80,35 +79,24 @@ button {
|
|||
cursor: pointer;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
[type="button"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
[type="button"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: #303030;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
font-size: 2.1875rem;
|
||||
}
|
||||
.list-unstyled {
|
||||
|
@ -230,9 +218,6 @@ h1,
|
|||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
.nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -306,17 +291,6 @@ h1,
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
background-color: #fff;
|
||||
background-clip: border-box;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25em 0.4em;
|
||||
|
@ -342,20 +316,6 @@ h1,
|
|||
padding-left: 0.6em;
|
||||
border-radius: 10rem;
|
||||
}
|
||||
.close {
|
||||
float: right;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: #000;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
button.close {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
.rounded-circle {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
@ -444,12 +404,10 @@ body,
|
|||
}
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="submit"],
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -530,38 +488,9 @@ body {
|
|||
color: #525252;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
img.emoji {
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
width: 20px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.chart {
|
||||
overflow: hidden;
|
||||
height: 220px;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.show.dropdown .dropdown-menu {
|
||||
transform: translateY(0);
|
||||
display: block;
|
||||
min-height: 40px;
|
||||
max-height: 312px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.show.dropdown .dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.show.dropdown .dropdown-menu-toggle,
|
||||
.show.dropdown .dropdown-menu-toggle {
|
||||
border-color: #c4c4c4;
|
||||
}
|
||||
.show.dropdown [data-toggle="dropdown"] {
|
||||
outline: 0;
|
||||
}
|
||||
.search-input-container .dropdown-menu {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
@ -929,15 +858,6 @@ input {
|
|||
float: none;
|
||||
}
|
||||
}
|
||||
.header-user.show .dropdown-menu {
|
||||
margin-top: 4px;
|
||||
color: var(--gl-text-color, #303030);
|
||||
left: auto;
|
||||
max-height: 445px;
|
||||
}
|
||||
.header-user.show .dropdown-menu svg {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.header-user-avatar {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
|
@ -968,9 +888,6 @@ input {
|
|||
.tanuki-logo .tanuki-right-cheek {
|
||||
fill: #fca326;
|
||||
}
|
||||
.card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.context-header {
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
|
@ -1969,9 +1886,6 @@ body.sidebar-refactoring
|
|||
.avatar.s32 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
#content-body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-width-8 {
|
||||
-moz-tab-size: 8;
|
||||
|
|
|
@ -40,11 +40,6 @@ p {
|
|||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
|
@ -54,11 +49,6 @@ a:not([href]):not([class]) {
|
|||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
code {
|
||||
font-family: "Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",
|
||||
"Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
|
@ -93,28 +83,20 @@ fieldset {
|
|||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
h1,
|
||||
h3,
|
||||
.h1,
|
||||
.h3 {
|
||||
h3 {
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: #303030;
|
||||
}
|
||||
h1,
|
||||
.h1 {
|
||||
h1 {
|
||||
font-size: 2.1875rem;
|
||||
}
|
||||
h3,
|
||||
.h3 {
|
||||
h3 {
|
||||
font-size: 1.53125rem;
|
||||
}
|
||||
hr {
|
||||
|
@ -123,14 +105,6 @@ hr {
|
|||
border: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
code {
|
||||
font-size: 90%;
|
||||
color: #1f1f1f;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
|
@ -245,8 +219,7 @@ a > code {
|
|||
margin-right: -5px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
.form-row > .col,
|
||||
.form-row > [class*="col-"] {
|
||||
.form-row > .col {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
@ -290,21 +263,6 @@ fieldset:disabled a.btn {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
background-color: #fff;
|
||||
background-clip: border-box;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.card > hr {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
.d-block {
|
||||
display: block !important;
|
||||
}
|
||||
|
@ -449,9 +407,7 @@ body,
|
|||
cursor: pointer;
|
||||
}
|
||||
h1,
|
||||
.h1,
|
||||
h3,
|
||||
.h3 {
|
||||
h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -461,16 +417,6 @@ a {
|
|||
hr {
|
||||
overflow: hidden;
|
||||
}
|
||||
code {
|
||||
padding: 2px 4px;
|
||||
color: #1f1f1f;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.code > code {
|
||||
background-color: inherit;
|
||||
padding: unset;
|
||||
}
|
||||
.hidden {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
|
@ -478,13 +424,6 @@ code {
|
|||
.hide {
|
||||
display: none;
|
||||
}
|
||||
.label {
|
||||
padding: 4px 5px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
}
|
||||
svg {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
@ -509,11 +448,6 @@ body.navless {
|
|||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.container .container .title {
|
||||
padding-left: 15px !important;
|
||||
}
|
||||
}
|
||||
.navless-container {
|
||||
margin-top: 40px;
|
||||
padding-top: 32px;
|
||||
|
@ -627,9 +561,6 @@ label.label-bold {
|
|||
.tanuki-logo .tanuki-right-cheek {
|
||||
fill: #fca326;
|
||||
}
|
||||
.card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
input::-moz-placeholder {
|
||||
color: #868686;
|
||||
opacity: 1;
|
||||
|
|
|
@ -161,6 +161,34 @@ node, using `psql` which is installed by Omnibus GitLab.
|
|||
|
||||
The database used by Praefect is now configured.
|
||||
|
||||
If you see Praefect database errors after configuring PostgreSQL, see
|
||||
[troubleshooting steps below](#relation-does-not-exist-errors).
|
||||
|
||||
#### Relation does not exist errors
|
||||
|
||||
By default Praefect database tables are created automatically by `gitlab-ctl reconfigure` task.
|
||||
However, if the `gitlab-ctl reconfigure` command isn't executed or there are errors during the
|
||||
execution, the Praefect database tables are not created on initial reconfigure and can throw
|
||||
errors that relations do not exist.
|
||||
|
||||
For example:
|
||||
|
||||
- `ERROR: relation "node_status" does not exist at character 13`
|
||||
- `ERROR: relation "replication_queue_lock" does not exist at character 40`
|
||||
- This error:
|
||||
|
||||
```json
|
||||
{"level":"error","msg":"Error updating node: pq: relation \"node_status\" does not exist","pid":210882,"praefectName":"gitlab1x4m:0.0.0.0:2305","time":"2021-04-01T19:26:19.473Z","virtual_storage":"praefect-cluster-1"}
|
||||
```
|
||||
|
||||
To solve this, the database schema migration can be done using `sql-migrate` subcommand of
|
||||
the `praefect` command:
|
||||
|
||||
```shell
|
||||
$ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate
|
||||
praefect sql-migrate: OK (applied 21 migrations)
|
||||
```
|
||||
|
||||
#### PgBouncer
|
||||
|
||||
To reduce PostgreSQL resource consumption, we recommend setting up and configuring
|
||||
|
|
|
@ -17,8 +17,8 @@ together with Omnibus GitLab. This is recommended as part of our
|
|||
1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab
|
||||
package you want using *steps 1 and 2* from the GitLab downloads page.
|
||||
- Do not complete any other steps on the download page.
|
||||
1. Generate a password hash for PostgreSQL. This assumes you will use the default
|
||||
username of `gitlab` (recommended). The command will request a password
|
||||
1. Generate a password hash for PostgreSQL. This assumes you are using the default
|
||||
username of `gitlab` (recommended). The command requests a password
|
||||
and confirmation. Use the value that is output by this command in the next
|
||||
step as the value of `POSTGRESQL_PASSWORD_HASH`.
|
||||
|
||||
|
@ -31,7 +31,7 @@ together with Omnibus GitLab. This is recommended as part of our
|
|||
|
||||
- `POSTGRESQL_PASSWORD_HASH` - The value output from the previous step
|
||||
- `APPLICATION_SERVER_IP_BLOCKS` - A space delimited list of IP subnets or IP
|
||||
addresses of the GitLab application servers that will connect to the
|
||||
addresses of the GitLab application servers that connect to the
|
||||
database. Example: `%w(123.123.123.123/32 123.123.123.234/32)`
|
||||
|
||||
```ruby
|
||||
|
@ -64,7 +64,7 @@ together with Omnibus GitLab. This is recommended as part of our
|
|||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||
1. Note the PostgreSQL node's IP address or hostname, port, and
|
||||
plain text password. These will be necessary when configuring the GitLab
|
||||
plain text password. These are necessary when configuring the GitLab
|
||||
application servers later.
|
||||
1. [Enable monitoring](replication_and_failover.md#enable-monitoring)
|
||||
|
||||
|
|
|
@ -188,7 +188,7 @@ We recommend following these steps during renewal:
|
|||
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and select the **Renew** button beneath your existing subscription.
|
||||
|
||||
NOTE:
|
||||
If you need to change your [GitLab tier](https://about.gitlab.com/pricing/), contact our sales team via `renewals@gitlab.com` for assistance as this can't be done in the Customers Portal.
|
||||
If you need to change your [GitLab tier](https://about.gitlab.com/pricing/), contact our sales team via [the sales contact form](https://about.gitlab.com/sales/) for assistance as this can't be done in the Customers Portal.
|
||||
|
||||
1. In the first box, enter the total number of user licenses you'll need for the upcoming year. Be sure this number is at least **equal to, or greater than** the number of billable users in the system at the time of performing the renewal.
|
||||
1. Enter the number of [users over license](#users-over-license) in the second box for the user overage incurred in your previous subscription term.
|
||||
|
|
|
@ -27,11 +27,11 @@ Scanning a web application with both the browser-based crawler and GitLab DAST s
|
|||
|
||||
The browser-based crawler is an extension to the GitLab DAST product. DAST should be included in the CI/CD configuration and the browser-based crawler enabled using CI/CD variables:
|
||||
|
||||
1. Install the DAST [prerequisites](index.md#prerequisite).
|
||||
1. Ensure the DAST [prerequisites](index.md#prerequisites) are met.
|
||||
1. Include the [DAST CI template](index.md#include-the-dast-template).
|
||||
1. Set the target website using the `DAST_WEBSITE` CI/CD variable.
|
||||
1. Set the CI/CD variable `DAST_BROWSER_SCAN` to `true`.
|
||||
|
||||
|
||||
An example configuration might look like the following:
|
||||
|
||||
```yaml
|
||||
|
@ -48,7 +48,7 @@ dast:
|
|||
|
||||
The browser-based crawler can be configured using CI/CD variables.
|
||||
|
||||
| CI/CD variable | Type | Example | Description |
|
||||
| CI/CD variable | Type | Example | Description |
|
||||
|--------------------------------------| ----------------| --------------------------------- | ------------|
|
||||
| `DAST_WEBSITE` | URL | `http://www.site.com` | The URL of the website to scan. |
|
||||
| `DAST_BROWSER_SCAN` | boolean | `true` | Configures DAST to use the browser-based crawler engine. |
|
||||
|
@ -64,7 +64,7 @@ The browser-based crawler can be configured using CI/CD variables.
|
|||
| `DAST_USERNAME` | string | `user123` | The username to enter into the username field on the sign-in HTML form. |
|
||||
| `DAST_PASSWORD` | string | `p@55w0rd` | The password to enter into the password field on the sign-in HTML form. |
|
||||
| `DAST_USERNAME_FIELD` | selector | `id:user` | A selector describing the username field on the sign-in HTML form. |
|
||||
| `DAST_PASSWORD_FIELD` | selector | `css:.password-field` | A selector describing the password field on the sign-in HTML form. |
|
||||
| `DAST_PASSWORD_FIELD` | selector | `css:.password-field` | A selector describing the password field on the sign-in HTML form. |
|
||||
| `DAST_SUBMIT_FIELD` | selector | `xpath://input[@value='Login']` | A selector describing the element that when clicked submits the login form, or the password form of a multi-page login process. |
|
||||
| `DAST_FIRST_SUBMIT_FIELD` | selector | `.submit` | A selector describing the element that when clicked submits the username form of a multi-page login process. |
|
||||
| `DAST_BROWSER_AUTH_REPORT` | boolean | `true` | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
|
||||
|
@ -90,7 +90,7 @@ Selectors have the format `type`:`search string`. The crawler will search for th
|
|||
|
||||
##### Find selectors with Google Chrome
|
||||
|
||||
Chrome DevTools element selector tool is an effective way to find a selector.
|
||||
Chrome DevTools element selector tool is an effective way to find a selector.
|
||||
|
||||
1. Open Chrome and navigate to the page where you would like to find a selector, for example, the login page for your site.
|
||||
1. Open the `Elements` tab in Chrome DevTools with the keyboard shortcut `Command + Shift + c` in macOS or `Ctrl + Shift + c` in Windows.
|
||||
|
@ -105,7 +105,7 @@ In this example, the `id="user_login"` appears to be a good candidate. You can u
|
|||
|
||||
##### Choose the right selector
|
||||
|
||||
Judicious choice of selector leads to a scan that is resilient to the application changing.
|
||||
Judicious choice of selector leads to a scan that is resilient to the application changing.
|
||||
|
||||
In order of preference, it is recommended to choose as selectors:
|
||||
|
||||
|
@ -170,8 +170,8 @@ Login process:
|
|||
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
|
||||
1. If a form contains a username and password field, `DAST_USERNAME` and `DAST_PASSWORD` is inputted into the respective fields, the form submit button is clicked and the user is logged in.
|
||||
1. If a form contains only a username field, it is assumed that the login form is multi-step.
|
||||
1. The `DAST_USERNAME` is inputted into the username field and the form submit button is clicked.
|
||||
1. The subsequent pages loads where it is expected that a form exists and contains a password field. If found, `DAST_PASSWORD` is inputted, form submit button is clicked and the user is logged in.
|
||||
1. The `DAST_USERNAME` is inputted into the username field and the form submit button is clicked.
|
||||
1. The subsequent pages loads where it is expected that a form exists and contains a password field. If found, `DAST_PASSWORD` is inputted, form submit button is clicked and the user is logged in.
|
||||
|
||||
### Log in using explicit selection of the login form
|
||||
|
||||
|
@ -186,13 +186,13 @@ Login process:
|
|||
1. If the `DAST_FIRST_SUBMIT_FIELD` is defined, then it is assumed that the login form is multi-step.
|
||||
1. The `DAST_USERNAME` is inputted into the `DAST_USERNAME_FIELD` field and the `DAST_FIRST_SUBMIT_FIELD` is clicked.
|
||||
1. The subsequent pages loads where the `DAST_PASSWORD` is inputted into the `DAST_PASSWORD_FIELD` field, the `DAST_SUBMIT_FIELD` is clicked and the user is logged in.
|
||||
|
||||
|
||||
### Verifying successful login
|
||||
|
||||
Once the login form has been submitted, the browser-based crawler determines if the login was successful. Unsuccessful attempts at authentication cause the scan to halt.
|
||||
|
||||
Following the submission of the login form, authentication is determined to be unsuccessful when:
|
||||
|
||||
|
||||
- A `400` or `500` series HTTP response status code is returned.
|
||||
- A new cookie/browser storage value determined to be sufficiently random has not been set.
|
||||
|
||||
|
@ -229,7 +229,7 @@ For example:
|
|||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
dast:
|
||||
variables:
|
||||
DAST_WEBSITE: "https://example.com"
|
||||
DAST_BROWSER_SCAN: "true"
|
||||
|
|
|
@ -16,17 +16,6 @@ Dynamic Application Security Testing (DAST) examines applications for
|
|||
vulnerabilities like these in deployed environments. DAST uses the open source
|
||||
tool [OWASP Zed Attack Proxy](https://www.zaproxy.org/) for analysis.
|
||||
|
||||
NOTE:
|
||||
To learn how four of the top six attacks were application-based and how
|
||||
to protect your organization, download our
|
||||
["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
|
||||
whitepaper.
|
||||
|
||||
You can use DAST to examine your web applications:
|
||||
|
||||
- When initiated by a merge request, running as CI/CD pipeline job.
|
||||
- On demand, outside the CI/CD pipeline.
|
||||
|
||||
After DAST creates its report, GitLab evaluates it for discovered
|
||||
vulnerabilities between the source and target branches. Relevant
|
||||
findings are noted in the merge request.
|
||||
|
@ -35,21 +24,151 @@ The comparison logic uses only the latest pipeline executed for the target
|
|||
branch's base commit. Running the pipeline on other commits has no effect on
|
||||
the merge request.
|
||||
|
||||
## Prerequisite
|
||||
NOTE:
|
||||
To learn how four of the top six attacks were application-based and how
|
||||
to protect your organization, download our
|
||||
["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
|
||||
whitepaper.
|
||||
|
||||
To use DAST, ensure you're using GitLab Runner with the
|
||||
## DAST application analysis
|
||||
|
||||
DAST can analyze applications in two ways:
|
||||
|
||||
- Passive scan only (DAST default). DAST executes
|
||||
[ZAP's Baseline Scan](https://www.zaproxy.org/docs/docker/baseline-scan/) and doesn't
|
||||
actively attack your application.
|
||||
- Passive and active scan. DAST can be [configured](#full-scan) to also perform an active scan
|
||||
to attack your application and produce a more extensive security report. It can be very
|
||||
useful when combined with [Review Apps](../../../ci/review_apps/index.md).
|
||||
|
||||
NOTE:
|
||||
A pipeline may consist of multiple jobs, including SAST and DAST scanning. If any job
|
||||
fails to finish for any reason, the security dashboard doesn't show DAST scanner output. For
|
||||
example, if the DAST job finishes but the SAST job fails, the security dashboard doesn't show DAST
|
||||
results. On failure, the analyzer outputs an
|
||||
[exit code](../../../development/integrations/secure.md#exit-code).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [GitLab Runner](../../../ci/runners/README.md) available, with the
|
||||
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
|
||||
- Target application deployed. For more details, read [Deployment options](#deployment-options).
|
||||
|
||||
## Enable DAST
|
||||
### Deployment options
|
||||
|
||||
To enable DAST, either:
|
||||
Depending on the complexity of the target application, there are a few options as to how to deploy and configure
|
||||
the DAST template. We provided a set of example applications with their configurations in our
|
||||
[DAST demonstrations](https://gitlab.com/gitlab-org/security-products/demos/dast/) project.
|
||||
|
||||
#### Review Apps
|
||||
|
||||
Review Apps are the most involved method of deploying your DAST target application. To assist in the process,
|
||||
we created a Review App deployment using Google Kubernetes Engine (GKE). This example can be found in our
|
||||
[Review Apps - GKE](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke) project, along with detailed
|
||||
instructions in the [README.md](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke/-/blob/master/README.md)
|
||||
on how to configure Review Apps for DAST.
|
||||
|
||||
#### Docker Services
|
||||
|
||||
If your application utilizes Docker containers you have another option for deploying and scanning with DAST.
|
||||
After your Docker build job completes and your image is added to your container registry, you can use the image as a
|
||||
[service](../../../ci/services/index.md).
|
||||
|
||||
By using service definitions in your `gitlab-ci.yml`, you can scan services with the DAST analyzer.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- dast
|
||||
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
# Deploys the container to the GitLab container registry
|
||||
deploy:
|
||||
services:
|
||||
- name: docker:dind
|
||||
alias: dind
|
||||
image: docker:19.03.5
|
||||
stage: build
|
||||
script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker pull $CI_REGISTRY_IMAGE:latest || true
|
||||
- docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
|
||||
services: # use services to link your app container to the dast job
|
||||
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
alias: yourapp
|
||||
|
||||
variables:
|
||||
DAST_FULL_SCAN_ENABLED: "true" # do a full scan
|
||||
DAST_ZAP_USE_AJAX_SPIDER: "true" # use the ajax spider
|
||||
```
|
||||
|
||||
Most applications depend on multiple services such as databases or caching services. By default, services defined in the services fields cannot communicate
|
||||
with each another. To allow communication between services, enable the `FF_NETWORK_PER_BUILD` [feature flag](https://docs.gitlab.com/runner/configuration/feature-flags.html#available-feature-flags).
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
FF_NETWORK_PER_BUILD: "true" # enable network per build so all services can communicate on the same network
|
||||
|
||||
services: # use services to link the container to the dast job
|
||||
- name: mongo:latest
|
||||
alias: mongo
|
||||
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
alias: yourapp
|
||||
```
|
||||
|
||||
## DAST run options
|
||||
|
||||
You can use DAST to examine your web application:
|
||||
|
||||
- Automatically, initiated by a merge request.
|
||||
- Manually, initiated on demand.
|
||||
|
||||
Some of the differences between these run options:
|
||||
|
||||
| Automatic scan | On-demand scan |
|
||||
|:-----------------------------------------------------------------|:------------------------------|
|
||||
| DAST scan is initiated by a merge request. | DAST scan is initiated manually, outside the DevOps life cycle. |
|
||||
| CI/CD variables are sourced from `.gitlab-ci.yml`. | CI/CD variables are provided in the UI. |
|
||||
| All [DAST CI/CD variables](#available-cicd-variables) available. | Subset of [DAST CI/CD variables](#available-cicd-variables) available. |
|
||||
| `DAST.gitlab-ci.yml` template. | `DAST-On-Demand-Scan.gitlab-ci.yml` template. |
|
||||
|
||||
### Enable automatic DAST run
|
||||
|
||||
To enable DAST to run automatically, either:
|
||||
|
||||
- Enable [Auto DAST](../../../topics/autodevops/stages.md#auto-dast) (provided
|
||||
by [Auto DevOps](../../../topics/autodevops/index.md)).
|
||||
- Manually [include the DAST template](#include-the-dast-template) in your existing
|
||||
- [Include the DAST template](#include-the-dast-template) in your existing
|
||||
`.gitlab-ci.yml` file.
|
||||
|
||||
### Include the DAST template
|
||||
### DAST job order
|
||||
|
||||
When using the `DAST.gitlab-ci.yml` template, the `dast` stage is run last as shown in
|
||||
the example below. To ensure DAST scans the latest code, deploy your application
|
||||
in a stage before the `dast` stage.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
- dast
|
||||
```
|
||||
|
||||
Be aware that if your pipeline is configured to deploy to the same webserver in
|
||||
each run, running a pipeline while another is still running could cause a race condition
|
||||
where one pipeline overwrites the code from another pipeline. The site to be scanned
|
||||
should be excluded from changes for the duration of a DAST scan.
|
||||
The only changes to the site should be from the DAST scanner. Be aware that any
|
||||
changes that users, scheduled tasks, database changes, code changes, other pipelines, or other scanners make to
|
||||
the site during a scan could lead to inaccurate results.
|
||||
|
||||
#### Include the DAST template
|
||||
|
||||
If you want to manually add DAST to your application, the DAST job is defined
|
||||
in a CI/CD template file. Updates to the template are provided with GitLab
|
||||
|
@ -152,176 +271,6 @@ The browser-based crawler crawls websites by browsing web pages as a user would.
|
|||
|
||||
For more details, including setup instructions, see [DAST browser-based crawler](browser_based.md).
|
||||
|
||||
## Deployment options
|
||||
|
||||
Depending on the complexity of the target application, there are a few options as to how to deploy and configure
|
||||
the DAST template. A set of example applications with their configurations have been made available in our
|
||||
[DAST demonstrations](https://gitlab.com/gitlab-org/security-products/demos/dast/) project.
|
||||
|
||||
### Review Apps
|
||||
|
||||
Review Apps are the most involved method of deploying your DAST target application. To assist in the process,
|
||||
we created a Review App deployment using Google Kubernetes Engine (GKE). This example can be found in our
|
||||
[Review Apps - GKE](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke) project along with detailed
|
||||
instructions in the [README.md](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke/-/blob/master/README.md)
|
||||
on how to configure Review Apps for DAST.
|
||||
|
||||
### Docker Services
|
||||
|
||||
If your application utilizes Docker containers you have another option for deploying and scanning with DAST.
|
||||
After your Docker build job completes and your image is added to your container registry, you can utilize the image as a
|
||||
[service](../../../ci/services/index.md).
|
||||
|
||||
By using service definitions in your `gitlab-ci.yml`, you can scan services with the DAST analyzer.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- dast
|
||||
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
# Deploys the container to the GitLab container registry
|
||||
deploy:
|
||||
services:
|
||||
- name: docker:dind
|
||||
alias: dind
|
||||
image: docker:19.03.5
|
||||
stage: build
|
||||
script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker pull $CI_REGISTRY_IMAGE:latest || true
|
||||
- docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
|
||||
services: # use services to link your app container to the dast job
|
||||
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
alias: yourapp
|
||||
|
||||
variables:
|
||||
DAST_FULL_SCAN_ENABLED: "true" # do a full scan
|
||||
DAST_ZAP_USE_AJAX_SPIDER: "true" # use the ajax spider
|
||||
```
|
||||
|
||||
Most applications depend on multiple services such as databases or caching services. By default, services defined in the services fields cannot communicate
|
||||
with each another. To allow communication between services, enable the `FF_NETWORK_PER_BUILD` [feature flag](https://docs.gitlab.com/runner/configuration/feature-flags.html#available-feature-flags).
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
FF_NETWORK_PER_BUILD: "true" # enable network per build so all services can communicate on the same network
|
||||
|
||||
services: # use services to link the container to the dast job
|
||||
- name: mongo:latest
|
||||
alias: mongo
|
||||
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
alias: yourapp
|
||||
```
|
||||
|
||||
### DAST application analysis
|
||||
|
||||
DAST can analyze applications in two ways:
|
||||
|
||||
- Passive scan only (DAST default). DAST executes
|
||||
[ZAP's Baseline Scan](https://www.zaproxy.org/docs/docker/baseline-scan/) and doesn't
|
||||
actively attack your application.
|
||||
- Passive and active scan. DAST can be [configured](#full-scan) to also perform an active scan
|
||||
to attack your application and produce a more extensive security report. It can be very
|
||||
useful when combined with [Review Apps](../../../ci/review_apps/index.md).
|
||||
|
||||
Note that a pipeline may consist of multiple jobs, including SAST and DAST scanning. If any job
|
||||
fails to finish for any reason, the security dashboard doesn't show DAST scanner output. For
|
||||
example, if the DAST job finishes but the SAST job fails, the security dashboard doesn't show DAST
|
||||
results. On failure, the analyzer outputs an
|
||||
[exit code](../../../development/integrations/secure.md#exit-code).
|
||||
|
||||
#### DAST job order
|
||||
|
||||
When using the `DAST.gitlab-ci.yml` template, the `dast` job is run last as shown in
|
||||
the example below. To ensure DAST is scanning the latest code, your CI pipeline
|
||||
should deploy changes to the web server in one of the jobs preceding the `dast` job.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
- dast
|
||||
```
|
||||
|
||||
Be aware that if your pipeline is configured to deploy to the same webserver in
|
||||
each run, running a pipeline while another is still running could cause a race condition
|
||||
where one pipeline overwrites the code from another pipeline. The site to be scanned
|
||||
should be excluded from changes for the duration of a DAST scan.
|
||||
The only changes to the site should be from the DAST scanner. Be aware that any
|
||||
changes that users, scheduled tasks, database changes, code changes, other pipelines, or other scanners make to
|
||||
the site during a scan could lead to inaccurate results.
|
||||
|
||||
### Hide sensitive information
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36332) in GitLab 13.1.
|
||||
|
||||
HTTP request and response headers may contain sensitive information, including cookies and
|
||||
authorization credentials. By default, the following headers are masked:
|
||||
|
||||
- `Authorization`.
|
||||
- `Proxy-Authorization`.
|
||||
- `Set-Cookie` (values only).
|
||||
- `Cookie` (values only).
|
||||
|
||||
Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-cicd-variables), you can list the
|
||||
headers whose values you want masked. For details on how to mask headers, see
|
||||
[Customizing the DAST settings](#customizing-the-dast-settings).
|
||||
|
||||
### Authentication
|
||||
|
||||
It's also possible to authenticate the user before performing the DAST checks.
|
||||
|
||||
NOTE:
|
||||
We highly recommended that you configure the scanner to authenticate to the application,
|
||||
otherwise it cannot check most of the application for security risks, as most
|
||||
of your application is likely not accessible without authentication. It is also recommended
|
||||
that you periodically confirm the scanner's authentication is still working as this tends to break over
|
||||
time due to authentication changes to the application.
|
||||
|
||||
Create masked CI/CD variables to pass the credentials that DAST uses.
|
||||
To create masked variables for the username and password, see [Create a custom variable in the UI](../../../ci/variables/README.md#custom-cicd-variables).
|
||||
Note that the key of the username variable must be `DAST_USERNAME`
|
||||
and the key of the password variable must be `DAST_PASSWORD`.
|
||||
|
||||
After DAST has authenticated with the application, all cookies are collected from the web browser.
|
||||
For each cookie a matching session token is created for use by ZAP. This ensures ZAP is recognized
|
||||
by the application as correctly authenticated.
|
||||
|
||||
Other variables that are related to authenticated scans are:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DAST_WEBSITE: https://example.com
|
||||
DAST_AUTH_URL: https://example.com/sign-in
|
||||
DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
|
||||
DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
|
||||
DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process
|
||||
DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process
|
||||
DAST_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
|
||||
DAST_AUTH_VERIFICATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication
|
||||
```
|
||||
|
||||
The results are saved as a
|
||||
[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast)
|
||||
that you can later download and analyze.
|
||||
Due to implementation limitations, we always take the latest DAST artifact available.
|
||||
|
||||
WARNING:
|
||||
**NEVER** run an authenticated scan against a production server. When an authenticated
|
||||
scan is run, it may perform *any* function that the authenticated user can. This
|
||||
includes actions like modifying and deleting data, submitting forms, and following links.
|
||||
Only run an authenticated scan against a test server.
|
||||
|
||||
### Full scan
|
||||
|
||||
DAST can be configured to perform [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan), which
|
||||
|
@ -694,47 +643,108 @@ By default, several rules are disabled because they either take a long time to
|
|||
run or frequently generate false positives. The complete list of disabled rules
|
||||
can be found in [exclude_rules.yml](https://gitlab.com/gitlab-org/security-products/dast/-/blob/master/src/config/exclude_rules.yml).
|
||||
|
||||
### Hide sensitive information
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36332) in GitLab 13.1.
|
||||
|
||||
HTTP request and response headers may contain sensitive information, including cookies and
|
||||
authorization credentials. By default, the following headers are masked:
|
||||
|
||||
- `Authorization`.
|
||||
- `Proxy-Authorization`.
|
||||
- `Set-Cookie` (values only).
|
||||
- `Cookie` (values only).
|
||||
|
||||
Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-cicd-variables), you can list the
|
||||
headers whose values you want masked. For details on how to mask headers, see
|
||||
[Customizing the DAST settings](#customizing-the-dast-settings).
|
||||
|
||||
### Authentication
|
||||
|
||||
It's also possible to authenticate the user before performing the DAST checks.
|
||||
|
||||
NOTE:
|
||||
We highly recommend you configure the scanner to authenticate to the application. If you don't, it cannot check most of the application for security risks, as most
|
||||
of your application is likely not accessible without authentication. We also recommend
|
||||
you periodically confirm the scanner's authentication is still working, as this tends to break over
|
||||
time due to authentication changes to the application.
|
||||
|
||||
Create masked CI/CD variables to pass the credentials that DAST uses.
|
||||
To create masked variables for the username and password, see [Create a custom variable in the UI](../../../ci/variables/README.md#custom-cicd-variables).
|
||||
The key of the username variable must be `DAST_USERNAME`,
|
||||
and the key of the password variable must be `DAST_PASSWORD`.
|
||||
|
||||
After DAST has authenticated with the application, all cookies are collected from the web browser.
|
||||
For each cookie a matching session token is created for use by ZAP. This ensures ZAP is recognized
|
||||
by the application as correctly authenticated.
|
||||
|
||||
Other variables that are related to authenticated scans are:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DAST_WEBSITE: https://example.com
|
||||
DAST_AUTH_URL: https://example.com/sign-in
|
||||
DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
|
||||
DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
|
||||
DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process
|
||||
DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process
|
||||
DAST_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
|
||||
DAST_AUTH_VERIFICATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication
|
||||
```
|
||||
|
||||
WARNING:
|
||||
**NEVER** run an authenticated scan against a production server. When an authenticated
|
||||
scan is run, it may perform *any* function that the authenticated user can. This
|
||||
includes actions like modifying and deleting data, submitting forms, and following links.
|
||||
Only run an authenticated scan against a test server.
|
||||
|
||||
### Available CI/CD variables
|
||||
|
||||
DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
|
||||
|
||||
| CI/CD variable | Type | Description |
|
||||
|------------------------------| --------|-------------|
|
||||
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
|
||||
| `DAST_WEBSITE` | URL | The URL of the website to scan. `DAST_API_OPENAPI` must be specified if this is omitted. |
|
||||
| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_API_SPECIFICATION` | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
|
||||
| `DAST_AUTH_URL` | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. |
|
||||
| `DAST_AUTH_VERIFICATION_URL` | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST will exit if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8.
|
||||
| `DAST_USERNAME` | string | The username to authenticate to in the website. |
|
||||
| `DAST_PASSWORD` | string | The password to authenticate to in the website. |
|
||||
| `DAST_USERNAME_FIELD` | string | The name of username field at the sign-in HTML form. |
|
||||
| `DAST_PASSWORD_FIELD` | string | The name of password field at the sign-in HTML form. |
|
||||
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
|
||||
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
|
||||
| `DAST_EXCLUDE_URLS` | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
| `DAST_FULL_SCAN_ENABLED` | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
|
||||
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/293595) in GitLab 13.8, to be removed in 14.0. Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` |
|
||||
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
|
||||
| `DAST_API_HOST_OVERRIDE` | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080` |
|
||||
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
|
||||
| `DAST_ONLY_INCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to configure the scan to run only them. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). Cannot be used when `DAST_EXCLUDE_RULES` is set. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250651) in GitLab 13.12. |
|
||||
| `DAST_REQUEST_HEADERS` | string | Set to a comma-separated list of request header names and values. Headers are added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
|
||||
| `DAST_DEBUG` | boolean | Enable debug message output. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_SPIDER_MINS` | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_HTML_REPORT` | string | The filename of the HTML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_MARKDOWN_REPORT` | string | The filename of the Markdown report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1.|
|
||||
| `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | boolean | Set to `true` to include alpha passive and active scan rules. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_USE_AJAX_SPIDER` | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. |
|
||||
| `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. |
|
||||
| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the login form or the password form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the username form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` |
|
||||
| `DAST_AUTH_EXCLUDE_URLS` | URLs | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/289959) in GitLab 13.8, to be removed in 14.0, and replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
| CI/CD variable | Type | Description |
|
||||
|:--------------------------------------------|:--------------|:-----------------------------------|
|
||||
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
|
||||
| `DAST_WEBSITE` (**1**) | URL | The URL of the website to scan. `DAST_API_OPENAPI` must be specified if this is omitted. |
|
||||
| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_API_SPECIFICATION` (**1**) | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
|
||||
| `DAST_AUTH_URL` (**1**) | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. |
|
||||
| `DAST_AUTH_VERIFICATION_URL` (**1**) | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST exits if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
|
||||
| `DAST_USERNAME` (**1**) | string | The username to authenticate to in the website. |
|
||||
| `DAST_PASSWORD` (**1**) | string | The password to authenticate to in the website. |
|
||||
| `DAST_USERNAME_FIELD` (**1**) | string | The name of username field at the sign-in HTML form. |
|
||||
| `DAST_PASSWORD_FIELD` (**1**) | string | The name of password field at the sign-in HTML form. |
|
||||
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
|
||||
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
|
||||
| `DAST_EXCLUDE_URLS` (**1**) | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
| `DAST_FULL_SCAN_ENABLED` (**1**) | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
|
||||
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/293595) in GitLab 13.8, to be removed in 14.0. Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` |
|
||||
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
|
||||
| `DAST_API_HOST_OVERRIDE` (**1**) | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080` |
|
||||
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
|
||||
| `DAST_ONLY_INCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to configure the scan to run only them. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). Cannot be used when `DAST_EXCLUDE_RULES` is set. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250651) in GitLab 13.12. |
|
||||
| `DAST_REQUEST_HEADERS` (**1**) | string | Set to a comma-separated list of request header names and values. Headers are added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
|
||||
| `DAST_DEBUG` (**1**) | boolean | Enable debug message output. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_TARGET_AVAILABILITY_TIMEOUT` (**1**) | number | Time limit in seconds to wait for target availability.
|
||||
| `DAST_SPIDER_MINS` (**1**) | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_HTML_REPORT` | string | The filename of the HTML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_MARKDOWN_REPORT` | string | The filename of the Markdown report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | boolean | Set to `true` to include alpha passive and active scan rules. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_USE_AJAX_SPIDER` (**1**) | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. |
|
||||
| `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. |
|
||||
| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the login form or the password form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the username form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` |
|
||||
| `DAST_AUTH_EXCLUDE_URLS` | URLs | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/289959) in GitLab 13.8, to be removed in 14.0, and replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
|
||||
1. DAST CI/CD variable available to an on-demand scan.
|
||||
|
||||
### DAST command-line options
|
||||
|
||||
|
@ -836,7 +846,7 @@ successfully run. For more information, see [Offline environments](../offline_de
|
|||
|
||||
To use DAST in an offline environment, you need:
|
||||
|
||||
- GitLab Runner with the [`docker` or `kubernetes` executor](#prerequisite).
|
||||
- GitLab Runner with the [`docker` or `kubernetes` executor](#prerequisites).
|
||||
- Docker Container Registry with a locally available copy of the DAST
|
||||
[container image](https://gitlab.com/gitlab-org/security-products/dast), found in the
|
||||
[DAST container registry](https://gitlab.com/gitlab-org/security-products/dast/container_registry).
|
||||
|
@ -1010,7 +1020,7 @@ A site profile contains the following:
|
|||
- **Target URL**: The URL that DAST runs against.
|
||||
- **Excluded URLs**: A comma-separated list of URLs to exclude from the scan.
|
||||
- **Request headers**: A comma-separated list of HTTP request headers, including names and values. These headers are added to every request made by DAST.
|
||||
- **Authentication**:
|
||||
- **Authentication**:
|
||||
- **Authenticated URL**: The URL of the page containing the sign-in HTML form on the target website. The username and password are submitted with the login form to create an authenticated scan.
|
||||
- **Username**: The username used to authenticate to the website.
|
||||
- **Password**: The password used to authenticate to the website.
|
||||
|
|
|
@ -18595,6 +18595,9 @@ msgstr ""
|
|||
msgid "Iterations|Iteration scheduling will be handled automatically"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Move incomplete issues to the next iteration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|New iteration cadence"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18607,6 +18610,9 @@ msgstr ""
|
|||
msgid "Iterations|Number of future iterations you would like to have scheduled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Roll over issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Save cadence"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -246,6 +246,7 @@
|
|||
"postcss": "^7.0.14",
|
||||
"prettier": "2.2.1",
|
||||
"purgecss": "^4.0.3",
|
||||
"purgecss-from-html": "^4.0.3",
|
||||
"readdir-enhanced": "^2.2.4",
|
||||
"sass": "^1.32.12",
|
||||
"timezone-mock": "^1.0.8",
|
||||
|
|
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
|||
const cheerio = require('cheerio');
|
||||
const { mergeWith, isArray } = require('lodash');
|
||||
const { PurgeCSS } = require('purgecss');
|
||||
const purgeHtml = require('purgecss-from-html');
|
||||
const { cleanCSS } = require('./clean_css');
|
||||
const { HTML_TO_REMOVE } = require('./constants');
|
||||
const { die } = require('./utils');
|
||||
|
@ -51,6 +52,12 @@ const getStartupCSS = async ({ htmlPaths, cssPaths, purgeOptions }) => {
|
|||
},
|
||||
// By default, PurgeCSS ignores special characters, but our utilities use "!"
|
||||
defaultExtractor: (x) => x.match(/[\w-!]+/g),
|
||||
extractors: [
|
||||
{
|
||||
extractor: purgeHtml,
|
||||
extensions: ['html'],
|
||||
},
|
||||
],
|
||||
},
|
||||
purgeOptions,
|
||||
),
|
||||
|
|
|
@ -172,7 +172,7 @@ RSpec.describe 'File blob', :js do
|
|||
end
|
||||
end
|
||||
|
||||
context 'sucessfully change ref of similar name' do
|
||||
context 'successfully change ref of similar name' do
|
||||
before do
|
||||
project.repository.create_branch('dev')
|
||||
project.repository.create_branch('development')
|
||||
|
@ -182,14 +182,32 @@ RSpec.describe 'File blob', :js do
|
|||
visit_blob('files/js/application.js', ref: 'development')
|
||||
switch_ref_to('dev')
|
||||
|
||||
expect(page.find('.file-title-name').text).to eq('application.js')
|
||||
aggregate_failures do
|
||||
expect(page.find('.file-title-name').text).to eq('application.js')
|
||||
expect(page).not_to have_css('flash-container')
|
||||
end
|
||||
end
|
||||
|
||||
it 'switch ref from shorter to longer ref name' do
|
||||
visit_blob('files/js/application.js', ref: 'dev')
|
||||
switch_ref_to('development')
|
||||
|
||||
aggregate_failures do
|
||||
expect(page.find('.file-title-name').text).to eq('application.js')
|
||||
expect(page).not_to have_css('flash-container')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'successfully changes ref when the ref name matches the project name' do
|
||||
project.repository.create_branch(project.name)
|
||||
|
||||
visit_blob('files/js/application.js', ref: project.name)
|
||||
switch_ref_to('master')
|
||||
|
||||
aggregate_failures do
|
||||
expect(page.find('.file-title-name').text).to eq('application.js')
|
||||
expect(page).not_to have_css('flash-container')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -328,20 +328,45 @@ describe('issue_comment_form component', () => {
|
|||
mountComponent({ mountFunction: mount });
|
||||
});
|
||||
|
||||
it('should save note when cmd+enter is pressed', () => {
|
||||
jest.spyOn(wrapper.vm, 'handleSave');
|
||||
describe('when no draft exists', () => {
|
||||
it('should save note when cmd+enter is pressed', () => {
|
||||
jest.spyOn(wrapper.vm, 'handleSave');
|
||||
|
||||
findTextArea().trigger('keydown.enter', { metaKey: true });
|
||||
findTextArea().trigger('keydown.enter', { metaKey: true });
|
||||
|
||||
expect(wrapper.vm.handleSave).toHaveBeenCalled();
|
||||
expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should save note when ctrl+enter is pressed', () => {
|
||||
jest.spyOn(wrapper.vm, 'handleSave');
|
||||
|
||||
findTextArea().trigger('keydown.enter', { ctrlKey: true });
|
||||
|
||||
expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
it('should save note when ctrl+enter is pressed', () => {
|
||||
jest.spyOn(wrapper.vm, 'handleSave');
|
||||
describe('when a draft exists', () => {
|
||||
beforeEach(() => {
|
||||
store.registerModule('batchComments', batchComments());
|
||||
store.state.batchComments.drafts = [{ note: 'A' }];
|
||||
});
|
||||
|
||||
findTextArea().trigger('keydown.enter', { ctrlKey: true });
|
||||
it('should save note draft when cmd+enter is pressed', () => {
|
||||
jest.spyOn(wrapper.vm, 'handleSaveDraft');
|
||||
|
||||
expect(wrapper.vm.handleSave).toHaveBeenCalled();
|
||||
findTextArea().trigger('keydown.enter', { metaKey: true });
|
||||
|
||||
expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should save note draft when ctrl+enter is pressed', () => {
|
||||
jest.spyOn(wrapper.vm, 'handleSaveDraft');
|
||||
|
||||
findTextArea().trigger('keydown.enter', { ctrlKey: true });
|
||||
|
||||
expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,8 @@ import ReleaseBlock from '~/releases/components/release_block.vue';
|
|||
import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
|
||||
import ReleasesEmptyState from '~/releases/components/releases_empty_state.vue';
|
||||
import ReleasesPaginationApolloClient from '~/releases/components/releases_pagination_apollo_client.vue';
|
||||
import { PAGE_SIZE } from '~/releases/constants';
|
||||
import ReleasesSortApolloClient from '~/releases/components/releases_sort_apollo_client.vue';
|
||||
import { PAGE_SIZE, RELEASED_AT_DESC, CREATED_ASC } from '~/releases/constants';
|
||||
import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
@ -68,6 +69,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
wrapper.findByText(ReleasesIndexApolloClientApp.i18n.newRelease);
|
||||
const findAllReleaseBlocks = () => wrapper.findAllComponents(ReleaseBlock);
|
||||
const findPagination = () => wrapper.findComponent(ReleasesPaginationApolloClient);
|
||||
const findSort = () => wrapper.findComponent(ReleasesSortApolloClient);
|
||||
|
||||
// Expectations
|
||||
const expectLoadingIndicator = () => {
|
||||
|
@ -135,6 +137,12 @@ describe('app_index_apollo_client.vue', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const expectSort = () => {
|
||||
it('renders the sort controls', () => {
|
||||
expect(findSort().exists()).toBe(true);
|
||||
});
|
||||
};
|
||||
|
||||
// Tests
|
||||
describe('when the component is loading data', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -147,6 +155,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
expectNewReleaseButton();
|
||||
expectReleases(0);
|
||||
expectNoPagination();
|
||||
expectSort();
|
||||
});
|
||||
|
||||
describe('when the data has successfully loaded, but there are no releases', () => {
|
||||
|
@ -161,6 +170,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
expectNewReleaseButton();
|
||||
expectReleases(0);
|
||||
expectNoPagination();
|
||||
expectSort();
|
||||
});
|
||||
|
||||
describe('when an error occurs while loading data', () => {
|
||||
|
@ -174,6 +184,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
expectNewReleaseButton();
|
||||
expectReleases(0);
|
||||
expectNoPagination();
|
||||
expectSort();
|
||||
});
|
||||
|
||||
describe('when the data has successfully loaded with a single page of results', () => {
|
||||
|
@ -201,6 +212,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
expectNewReleaseButton();
|
||||
expectReleases(originalAllReleasesQueryResponse.data.project.releases.nodes.length);
|
||||
expectPagination();
|
||||
expectSort();
|
||||
});
|
||||
|
||||
describe('URL parameters', () => {
|
||||
|
@ -213,6 +225,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
expect(allReleasesQueryMock).toHaveBeenCalledWith({
|
||||
first: PAGE_SIZE,
|
||||
fullPath: projectPath,
|
||||
sort: RELEASED_AT_DESC,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -228,6 +241,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
before,
|
||||
last: PAGE_SIZE,
|
||||
fullPath: projectPath,
|
||||
sort: RELEASED_AT_DESC,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -243,6 +257,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
after,
|
||||
first: PAGE_SIZE,
|
||||
fullPath: projectPath,
|
||||
sort: RELEASED_AT_DESC,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -258,6 +273,7 @@ describe('app_index_apollo_client.vue', () => {
|
|||
after,
|
||||
first: PAGE_SIZE,
|
||||
fullPath: projectPath,
|
||||
sort: RELEASED_AT_DESC,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -298,4 +314,27 @@ describe('app_index_apollo_client.vue', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it(`sorts by ${RELEASED_AT_DESC} by default`, () => {
|
||||
expect(allReleasesQueryMock.mock.calls).toEqual([
|
||||
[expect.objectContaining({ sort: RELEASED_AT_DESC })],
|
||||
]);
|
||||
});
|
||||
|
||||
it('requeries the GraphQL endpoint when the sort is changed', async () => {
|
||||
findSort().vm.$emit('input', CREATED_ASC);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(allReleasesQueryMock.mock.calls).toEqual([
|
||||
[expect.objectContaining({ sort: RELEASED_AT_DESC })],
|
||||
[expect.objectContaining({ sort: CREATED_ASC })],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import { GlSorting, GlSortingItem } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ReleasesSortApolloClient from '~/releases/components/releases_sort_apollo_client.vue';
|
||||
import { RELEASED_AT_ASC, RELEASED_AT_DESC, CREATED_ASC, CREATED_DESC } from '~/releases/constants';
|
||||
|
||||
describe('releases_sort_apollo_client.vue', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (valueProp = RELEASED_AT_ASC) => {
|
||||
wrapper = shallowMountExtended(ReleasesSortApolloClient, {
|
||||
propsData: {
|
||||
value: valueProp,
|
||||
},
|
||||
stubs: {
|
||||
GlSortingItem,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findSorting = () => wrapper.findComponent(GlSorting);
|
||||
const findSortingItems = () => wrapper.findAllComponents(GlSortingItem);
|
||||
const findReleasedDateItem = () =>
|
||||
findSortingItems().wrappers.find((item) => item.text() === 'Released date');
|
||||
const findCreatedDateItem = () =>
|
||||
findSortingItems().wrappers.find((item) => item.text() === 'Created date');
|
||||
const getSortingItemsInfo = () =>
|
||||
findSortingItems().wrappers.map((item) => ({
|
||||
label: item.text(),
|
||||
active: item.attributes().active === 'true',
|
||||
}));
|
||||
|
||||
describe.each`
|
||||
valueProp | text | isAscending | items
|
||||
${RELEASED_AT_ASC} | ${'Released date'} | ${true} | ${[{ label: 'Released date', active: true }, { label: 'Created date', active: false }]}
|
||||
${RELEASED_AT_DESC} | ${'Released date'} | ${false} | ${[{ label: 'Released date', active: true }, { label: 'Created date', active: false }]}
|
||||
${CREATED_ASC} | ${'Created date'} | ${true} | ${[{ label: 'Released date', active: false }, { label: 'Created date', active: true }]}
|
||||
${CREATED_DESC} | ${'Created date'} | ${false} | ${[{ label: 'Released date', active: false }, { label: 'Created date', active: true }]}
|
||||
`('component states', ({ valueProp, text, isAscending, items }) => {
|
||||
beforeEach(() => {
|
||||
createComponent(valueProp);
|
||||
});
|
||||
|
||||
it(`when the sort is ${valueProp}, provides the GlSorting with the props text="${text}" and isAscending=${isAscending}`, () => {
|
||||
expect(findSorting().props()).toEqual(
|
||||
expect.objectContaining({
|
||||
text,
|
||||
isAscending,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it(`when the sort is ${valueProp}, renders the expected dropdown items`, () => {
|
||||
expect(getSortingItemsInfo()).toEqual(items);
|
||||
});
|
||||
});
|
||||
|
||||
const clickReleasedDateItem = () => findReleasedDateItem().vm.$emit('click');
|
||||
const clickCreatedDateItem = () => findCreatedDateItem().vm.$emit('click');
|
||||
const clickSortDirectionButton = () => findSorting().vm.$emit('sortDirectionChange');
|
||||
|
||||
const releasedAtDropdownItemDescription = 'released at dropdown item';
|
||||
const createdAtDropdownItemDescription = 'created at dropdown item';
|
||||
const sortDirectionButtonDescription = 'sort direction button';
|
||||
|
||||
describe.each`
|
||||
initialValueProp | itemClickFn | itemToClickDescription | emittedEvent
|
||||
${RELEASED_AT_ASC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${undefined}
|
||||
${RELEASED_AT_ASC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${CREATED_ASC}
|
||||
${RELEASED_AT_ASC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${RELEASED_AT_DESC}
|
||||
${RELEASED_AT_DESC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${undefined}
|
||||
${RELEASED_AT_DESC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${CREATED_DESC}
|
||||
${RELEASED_AT_DESC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${RELEASED_AT_ASC}
|
||||
${CREATED_ASC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${RELEASED_AT_ASC}
|
||||
${CREATED_ASC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${undefined}
|
||||
${CREATED_ASC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${CREATED_DESC}
|
||||
${CREATED_DESC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${RELEASED_AT_DESC}
|
||||
${CREATED_DESC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${undefined}
|
||||
${CREATED_DESC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${CREATED_ASC}
|
||||
`('input event', ({ initialValueProp, itemClickFn, itemToClickDescription, emittedEvent }) => {
|
||||
beforeEach(() => {
|
||||
createComponent(initialValueProp);
|
||||
itemClickFn();
|
||||
});
|
||||
|
||||
it(`emits ${
|
||||
emittedEvent || 'nothing'
|
||||
} when value prop is ${initialValueProp} and the ${itemToClickDescription} is clicked`, () => {
|
||||
expect(wrapper.emitted().input?.[0]?.[0]).toEqual(emittedEvent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prop validation', () => {
|
||||
it('validates that the `value` prop is one of the expected sort strings', () => {
|
||||
expect(() => {
|
||||
createComponent('not a valid value');
|
||||
}).toThrow('Invalid prop: custom validator check failed');
|
||||
});
|
||||
});
|
||||
});
|
12
yarn.lock
12
yarn.lock
|
@ -9239,14 +9239,14 @@ parse-passwd@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^6.0.1:
|
||||
parse5-htmlparser2-tree-adapter@^6.0.0, parse5-htmlparser2-tree-adapter@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
|
||||
dependencies:
|
||||
parse5 "^6.0.1"
|
||||
|
||||
"parse5@5 - 6", parse5@^6.0.1:
|
||||
"parse5@5 - 6", parse5@^6.0.0, parse5@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
|
@ -9877,6 +9877,14 @@ pupa@^2.0.1:
|
|||
dependencies:
|
||||
escape-goat "^2.0.0"
|
||||
|
||||
purgecss-from-html@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/purgecss-from-html/-/purgecss-from-html-4.0.3.tgz#28d86d3dc8292581c4ab529a77a57daf7c2dd940"
|
||||
integrity sha512-Ipv/kXSDRBlVTWDSRq5PZoiJdFjZjlL6r/3MH42waKM524NiicyvwLlyE9XedBSCPs+Ypek6SaTd8TTeiBgCMg==
|
||||
dependencies:
|
||||
parse5 "^6.0.0"
|
||||
parse5-htmlparser2-tree-adapter "^6.0.0"
|
||||
|
||||
purgecss@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-4.0.3.tgz#8147b429f9c09db719e05d64908ea8b672913742"
|
||||
|
|
Loading…
Reference in a new issue