Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-04 00:10:03 +00:00
parent 8e2f50b44d
commit c9ebdf468d
21 changed files with 652 additions and 542 deletions

View file

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

View file

@ -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 }"
/>

View file

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

View file

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

View file

@ -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,
},
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 })],
]);
});
});
});

View file

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

View file

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