Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-09 06:11:48 +00:00
parent fae5be5632
commit 0ac2fcc287
48 changed files with 391 additions and 316 deletions

View file

@ -177,3 +177,7 @@ overrides:
'@graphql-eslint/no-unused-variables': error
'@graphql-eslint/no-unused-fragments': error
'@graphql-eslint/no-duplicate-fields': error
- files:
- 'spec/contracts/consumer/**/*'
rules:
'@gitlab/require-i18n-strings': off

View file

@ -287,6 +287,7 @@ export default {
:is-scroll-top-disabled="isScrollTopDisabled"
:is-job-log-size-visible="isJobLogSizeVisible"
:is-scrolling-down="isScrollingDown"
:is-complete="isJobLogComplete"
:job-log="jobLog"
@scrollJobLogTop="scrollTop"
@scrollJobLogBottom="scrollBottom"

View file

@ -1,6 +1,6 @@
<script>
import { GlTooltipDirective, GlLink, GlButton, GlSearchBoxByClick } from '@gitlab/ui';
import { scrollToElement } from '~/lib/utils/common_utils';
import { scrollToElement, backOff } from '~/lib/utils/common_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __, s__, sprintf } from '~/locale';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
@ -10,6 +10,7 @@ export default {
i18n: {
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
scrollToTopButtonLabel: s__('Job|Scroll to top'),
scrollToNextFailureButtonLabel: s__('Job|Scroll to next failure'),
showRawButtonLabel: s__('Job|Show complete raw'),
searchPlaceholder: s__('Job|Search job log'),
noResults: s__('Job|No search results found'),
@ -55,6 +56,10 @@ export default {
type: Boolean,
required: true,
},
isComplete: {
type: Boolean,
required: true,
},
jobLog: {
type: Array,
required: true,
@ -64,6 +69,8 @@ export default {
return {
searchTerm: '',
searchResults: [],
failureCount: null,
failureIndex: 0,
};
},
computed: {
@ -75,13 +82,49 @@ export default {
showJobLogSearch() {
return this.glFeatures.jobLogSearch;
},
showJumpToFailures() {
return this.glFeatures.jobLogJumpToFailures;
},
hasFailures() {
return this.failureCount > 0;
},
shouldDisableJumpToFailures() {
return !this.hasFailures;
},
},
mounted() {
this.checkFailureCount();
},
methods: {
checkFailureCount() {
if (this.glFeatures.jobLogJumpToFailures) {
backOff((next, stop) => {
this.failureCount = document.querySelectorAll('.term-fg-l-red').length;
if (this.hasFailures || (this.isComplete && !this.hasFailures)) {
stop();
} else {
next();
}
});
}
},
handleScrollToNextFailure() {
const failures = document.querySelectorAll('.term-fg-l-red');
const nextFailure = failures[this.failureIndex];
if (nextFailure) {
nextFailure.scrollIntoView({ block: 'center' });
this.failureIndex = (this.failureIndex + 1) % failures.length;
}
},
handleScrollToTop() {
this.$emit('scrollJobLogTop');
this.failureIndex = 0;
},
handleScrollToBottom() {
this.$emit('scrollJobLogBottom');
this.failureIndex = 0;
},
searchJobLog() {
this.searchResults = [];
@ -135,10 +178,10 @@ export default {
};
</script>
<template>
<div class="top-bar">
<div class="top-bar gl-display-flex gl-justify-content-space-between">
<!-- truncate information -->
<div
class="truncated-info gl-display-none gl-sm-display-block gl-float-left"
class="truncated-info gl-display-none gl-sm-display-flex gl-flex-wrap gl-align-items-center"
data-testid="log-truncated-info"
>
<template v-if="isJobLogSizeVisible">
@ -154,7 +197,7 @@ export default {
</div>
<!-- eo truncate information -->
<div class="controllers gl-float-right">
<div class="controllers">
<template v-if="showJobLogSearch">
<gl-search-box-by-click
v-model="searchTerm"
@ -187,6 +230,18 @@ export default {
<!-- eo links -->
<!-- scroll buttons -->
<gl-button
v-if="showJumpToFailures"
v-gl-tooltip
:title="$options.i18n.scrollToNextFailureButtonLabel"
:aria-label="$options.i18n.scrollToNextFailureButtonLabel"
:disabled="shouldDisableJumpToFailures"
class="btn-scroll gl-ml-3"
data-testid="job-controller-scroll-to-failure"
icon="soft-wrap"
@click="handleScrollToNextFailure"
/>
<div v-gl-tooltip :title="$options.i18n.scrollToTopButtonLabel" class="gl-ml-3">
<gl-button
:disabled="isScrollTopDisabled"

View file

@ -427,10 +427,10 @@
padding-inline-start: 28px;
margin-inline-start: 0 !important;
> input.task-list-item-checkbox {
input.task-list-item-checkbox {
position: absolute;
inset-inline-start: 8px;
top: 5px;
inset-inline-start: $gl-padding-8;
inset-block-start: 5px;
}
}
}

View file

@ -15,17 +15,11 @@
$stroke-size: 1px;
.right-arrow {
@include gl-relative;
height: $stroke-size;
background-color: var(--gray-900, $gray-900);
min-width: $gl-spacing-scale-7;
&-head {
@include gl-absolute;
top: -2*$stroke-size;
left: calc(100% - #{5*$stroke-size});
@include gl-p-1;
@include gl-border-solid;
top: -2 * $stroke-size;
left: calc(100% - #{5 * $stroke-size});
border-width: 0 $stroke-size $stroke-size 0;
border-color: var(--gray-900, $gray-900);
transform: rotate(-45deg);
@ -41,14 +35,10 @@ $stroke-size: 1px;
.rule-condition {
@media (min-width: $breakpoint-lg) {
flex-basis: 25%;
flex-shrink: 0;
@include gl-flex-shrink-0;
}
@media (max-width: $breakpoint-lg) {
@include gl-w-full;
}
}
.rule-action {
min-width: 0;
}

View file

@ -1,77 +1,24 @@
@import 'mixins_and_variables_and_functions';
.description {
ul,
ol {
/* We're changing list-style-position to inside because the default of
* outside doesn't move negative margin to the left of the bullet. */
list-style-position: inside;
ul.task-list > li.task-list-item {
margin-inline-start: 0.5rem !important; /* Override typography.scss */
}
li {
position: relative;
/* In the browser, the li element comes after (to the right of) the bullet point, so hovering
* over the left of the bullet point doesn't trigger a row hover. To trigger hovering on the
* left, we're applying negative margin here to shift the li element left. */
margin-inline-start: -1rem;
padding-inline-start: 2.5rem;
margin-inline-start: 2.25rem;
&.task-list-item > .drag-icon {
inset-inline-start: -0.6rem;
}
.drag-icon {
position: absolute;
inset-block-start: 0.3rem;
inset-inline-start: 1rem;
}
/* The inside bullet aligns itself to the bottom, which we see when text to the right of
* a multi-line list item wraps. We fix this by aligning it to the top, and excluding
* other elements. Targeting ::marker doesn't seem to work, instead we exclude custom elements
* or anything with a class */
> *:not(gl-emoji, code, [class]) {
vertical-align: top;
}
/* The inside bullet is treated like an element inside the li element, so when we have a
* multi-paragraph list item, the text doesn't start on the right of the bullet because
* it is a block level p element. We make it inline to fix this. */
> p:first-of-type {
display: inline-block;
max-width: calc(100% - 1.5rem);
}
/* We fix the other paragraphs not indenting to the
* right of the bullet due to the inside bullet. */
p ~ a,
p ~ blockquote,
p ~ code,
p ~ details,
p ~ dl,
p ~ h1,
p ~ h2,
p ~ h3,
p ~ h4,
p ~ h5,
p ~ h6,
p ~ hr,
p ~ ol,
p ~ p,
p ~ table:not(.code), /* We need :not(.code) to override typography.scss */
p ~ ul,
p ~ .markdown-code-block {
margin-inline-start: 1rem;
}
}
ul.task-list {
> li.task-list-item {
/* We're using !important to override the same selector in typography.scss */
margin-inline-start: -1rem !important;
padding-inline-start: 2.5rem;
> input.task-list-item-checkbox {
position: static;
vertical-align: middle;
margin-block-start: -2px;
}
inset-inline-start: -2.3rem;
padding-inline-end: 1rem;
width: 2rem;
}
}
}

View file

@ -19,6 +19,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
before_action :push_job_log_search, only: [:show]
before_action :push_job_log_jump_to_failures, only: [:show]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
layout 'project'
@ -252,4 +253,8 @@ class Projects::JobsController < Projects::ApplicationController
def push_job_log_search
push_frontend_feature_flag(:job_log_search, @project)
end
def push_job_log_jump_to_failures
push_frontend_feature_flag(:job_log_jump_to_failures, @project)
end
end

View file

@ -0,0 +1,8 @@
---
name: job_log_jump_to_failures
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91098
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368633
milestone: '15.3'
type: development
group: group::pipeline execution
default_enabled: false

View file

@ -106,7 +106,7 @@ POST /groups/:id/protected_environments
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) maintained by the authenticated user. |
| `name` | string | yes | The deployment tier of the protected environment. One of `production`, `staging`, `testing`, `development`, or `other`. Read more about [deployment tiers](../ci/environments/index.md#deployment-tier-of-environments).|
| `deploy_access_levels` | array | yes | Array of access levels allowed to deploy, with each described by a hash. One of `user_id`, `group_id` or `access_level`. They take the form of `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}` respectively. |
| `required_approval_count` | integer | no | The number of approvals required to deploy to this environment. This is part of Deployment Approvals, which isn't yet available for use. For details, see [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/343864). |
| `required_approval_count` | integer | no | The number of approvals required to deploy to this environment. |
| `approval_rules` | array | no | Array of access levels allowed to approve, with each described by a hash. One of `user_id`, `group_id` or `access_level`. They take the form of `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}` respectively. You can also specify the number of required approvals from the specified entity with `required_approvals` field. See [Multiple approval rules](../ci/environments/deployment_approvals.md#multiple-approval-rules) for more information. |
The assignable `user_id` are the users who belong to the given group with the Maintainer role (or above).

View file

@ -709,7 +709,6 @@ args: {
security: {
authn_requests_signed: true, # enable signature on AuthNRequest
want_assertions_signed: true, # enable the requirement of signed assertion
embed_sign: true, # embedded signature or HTTP GET parameter signature
metadata_signed: false, # enable signature on Metadata
signature_method: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
digest_method: 'http://www.w3.org/2001/04/xmlenc#sha256',

View file

@ -601,7 +601,7 @@ The following variables allow configuration of global dependency scanning settin
| `ADDITIONAL_CA_CERT_BUNDLE` | Bundle of CA certs to trust. The bundle of certificates provided here is also used by other tools during the scanning process, such as `git`, `yarn`, or `npm`. See [Using a custom SSL CA certificate authority](#using-a-custom-ssl-ca-certificate-authority) for more details. |
| `DS_EXCLUDED_ANALYZERS` | Specify the analyzers (by name) to exclude from Dependency Scanning. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
| `DS_DEFAULT_ANALYZERS` | This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/287691) in GitLab 14.0 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/333299) in 15.0. Use `DS_EXCLUDED_ANALYZERS` instead. |
| `DS_EXCLUDED_PATHS` | Exclude files and directories from the scan based on the paths. A comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
| `DS_EXCLUDED_PATHS` | Exclude files and directories from the scan based on the paths. A comma-separated list of patterns. Patterns can be globs (see [`doublestar.Match`](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4@v4.0.2#Match) for supported patterns), or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
| `DS_IMAGE_SUFFIX` | Suffix added to the image name. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354796) in GitLab 14.10.) Automatically set to `"-fips"` when FIPS mode is enabled. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357922) in GitLab 15.0.) |
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SECURE_LOG_LEVEL` | Set the minimum logging level. Messages of this logging level or higher are output. From highest to lowest severity, the logging levels are: `fatal`, `error`, `warn`, `info`, `debug`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10880) in GitLab 13.1. Default: `info`. |

View file

@ -0,0 +1,34 @@
---
stage: DevSecOps
group: Technical writing
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Get started with GitLab application security **(ULTIMATE)**
Complete the following steps to get the most from GitLab application security tools.
1. Enable [Secret Detection](secret_detection/index.md) scanning for your default branch.
1. Enable [Dependency Scanning](dependency_scanning/index.md) for your default branch so you can start identifying existing
vulnerable packages in your codebase.
1. Add security scans to feature branch pipelines. The same scans should be enabled as are running
on your default branch. Subsequent scans will show only new vulnerabilities by comparing the feature branch to the default branch results.
1. Let your team get comfortable with [vulnerability reports](vulnerability_report/index.md) and
establish a vulnerability triage workflow.
1. Consider creating [labels](../project/labels.md) and [issue boards](../project/issue_board.md) to
help manage issues created from vulnerabilities. Issue boards allow all stakeholders to have a
common view of all issues.
1. Create a [scan result policy](policies/index.md) to limit new vulnerabilities from being merged
into your default branch.
1. Monitor the [Security Dashboard](security_dashboard/index.md) trends to gauge success in
remediating existing vulnerabilities and preventing the introduction of new ones.
1. Enable other scan types such as [SAST](sast/index.md), [DAST](dast/index.md),
[Fuzz testing](coverage_fuzzing/index.md), or [Container Scanning](container_scanning/index.md).
Be sure to add the same scan types to both feature pipelines and default branch pipelines.
1. Use [Compliance Pipelines](../../user/project/settings/index.md#compliance-pipeline-configuration)
or [Scan Execution Policies](policies/scan-execution-policies.md) to enforce required scan types
and ensure separation of duties between security and engineering.
1. Consider enabling [Review Apps](../../development/testing_guide/review_apps.md) to allow for DAST
and [Web API fuzzing](api_fuzzing/index.md) on ephemeral test environments.
1. Enable [operational container scanning](../../user/clusters/agent/vulnerabilities.md) to scan
container images in your production cluster for security vulnerabilities.

View file

@ -4,31 +4,30 @@ return if Rails.env.production?
require 'pact/tasks/verification_task'
contracts = File.expand_path('../../../spec/contracts', __dir__)
provider = File.expand_path('provider', contracts)
contracts = File.expand_path('../../../spec/contracts/contracts/project/merge_request', __dir__)
provider = File.expand_path('../../../spec/contracts/provider', __dir__)
# rubocop:disable Rails/RakeEnvironment
namespace :contracts do
namespace :merge_requests do
Pact::VerificationTask.new(:diffs_batch) do |pact|
pact.uri(
"#{contracts}/contracts/project/merge_request/show/mergerequest#show-merge_request_diffs_batch_endpoint.json",
pact_helper: "#{provider}/pact_helpers/project/merge_request/diffs_batch_helper.rb"
"#{contracts}/show/mergerequest#show-merge_request_diffs_batch_endpoint.json",
pact_helper: "#{provider}/pact_helpers/project/merge_request/show/diffs_batch_helper.rb"
)
end
Pact::VerificationTask.new(:diffs_metadata) do |pact|
pact.uri(
"#{contracts}/contracts/project/merge_request/show/" \
"mergerequest#show-merge_request_diffs_metadata_endpoint.json",
pact_helper: "#{provider}/pact_helpers/project/merge_request/diffs_metadata_helper.rb"
"#{contracts}/show/mergerequest#show-merge_request_diffs_metadata_endpoint.json",
pact_helper: "#{provider}/pact_helpers/project/merge_request/show/diffs_metadata_helper.rb"
)
end
Pact::VerificationTask.new(:discussions) do |pact|
pact.uri(
"#{contracts}/contracts/project/merge_request/show/mergerequest#show-merge_request_discussions_endpoint.json",
pact_helper: "#{provider}/pact_helpers/project/merge_request/discussions_helper.rb"
"#{contracts}/show/mergerequest#show-merge_request_discussions_endpoint.json",
pact_helper: "#{provider}/pact_helpers/project/merge_request/show/discussions_helper.rb"
)
end

View file

@ -5,7 +5,7 @@ return if Rails.env.production?
require 'pact/tasks/verification_task'
contracts = File.expand_path('../../../spec/contracts/contracts/project/pipeline_schedule', __dir__)
provider = File.expand_path('../../../provider', contracts)
provider = File.expand_path('../../../spec/contracts/provider', __dir__)
# rubocop:disable Rails/RakeEnvironment
namespace :contracts do

View file

@ -4,36 +4,36 @@ return if Rails.env.production?
require 'pact/tasks/verification_task'
contracts = File.expand_path('../../../spec/contracts', __dir__)
provider = File.expand_path('provider', contracts)
contracts = File.expand_path('../../../spec/contracts/contracts/project/pipeline', __dir__)
provider = File.expand_path('../../../spec/contracts/provider', __dir__)
# rubocop:disable Rails/RakeEnvironment
namespace :contracts do
namespace :pipelines do
Pact::VerificationTask.new(:create_a_new_pipeline) do |pact|
pact.uri(
"#{contracts}/contracts/project/pipeline/new/pipelines#new-post_create_a_new_pipeline.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/create_a_new_pipeline_helper.rb"
"#{contracts}/new/pipelines#new-post_create_a_new_pipeline.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/index/create_a_new_pipeline_helper.rb"
)
end
Pact::VerificationTask.new(:get_list_project_pipelines) do |pact|
pact.uri(
"#{contracts}/contracts/project/pipeline/index/pipelines#index-get_list_project_pipelines.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/get_list_project_pipelines_helper.rb"
"#{contracts}/index/pipelines#index-get_list_project_pipelines.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/index/get_list_project_pipelines_helper.rb"
)
end
Pact::VerificationTask.new(:get_pipeline_header_data) do |pact|
pact.uri(
"#{contracts}/contracts/project/pipeline/show/pipelines#show-get_pipeline_header_data.json",
"#{contracts}/show/pipelines#show-get_pipeline_header_data.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/show/get_pipeline_header_data_helper.rb"
)
end
Pact::VerificationTask.new(:delete_pipeline) do |pact|
pact.uri(
"#{contracts}/contracts/project/pipeline/show/pipelines#show-delete_pipeline.json",
"#{contracts}/show/pipelines#show-delete_pipeline.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/show/delete_pipeline_helper.rb"
)
end

View file

@ -22656,6 +22656,9 @@ msgstr ""
msgid "Job|Scroll to bottom"
msgstr ""
msgid "Job|Scroll to next failure"
msgstr ""
msgid "Job|Scroll to top"
msgstr ""

View file

@ -1,16 +0,0 @@
import { request } from 'axios';
export function getProjectPipelines(endpoint) {
const { url } = endpoint;
return request({
method: 'GET',
baseURL: url,
url: '/gitlab-org/gitlab-qa/-/pipelines.json',
headers: { Accept: '*/*' },
params: {
scope: 'all',
page: 1,
},
}).then((response) => response.data);
}

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
const body = {
@ -73,8 +71,12 @@ const DiffsBatch = {
body,
},
request: {
scenario: {
state: 'a merge request with diffs exists',
uponReceiving: 'a request for diff lines',
},
request: {
withRequest: {
method: 'GET',
path: '/gitlab-org/gitlab-qa/-/merge_requests/1/diffs_batch.json',
@ -87,5 +89,3 @@ const DiffsBatch = {
};
export { DiffsBatch };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
const body = {
@ -81,8 +79,12 @@ const DiffsMetadata = {
body,
},
scenario: {
state: 'a merge request exists',
uponReceiving: 'a request for diffs metadata',
},
request: {
uponReceiving: 'a request for Diffs Metadata',
withRequest: {
method: 'GET',
path: '/gitlab-org/gitlab-qa/-/merge_requests/1/diffs_metadata.json',
@ -94,5 +96,3 @@ const DiffsMetadata = {
};
export { DiffsMetadata };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
const body = Matchers.eachLike({
@ -70,8 +68,12 @@ const Discussions = {
body,
},
request: {
scenario: {
state: 'a merge request with discussions exists',
uponReceiving: 'a request for discussions',
},
request: {
withRequest: {
method: 'GET',
path: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
@ -83,5 +85,3 @@ const Discussions = {
};
export { Discussions };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
import { REDIRECT_HTML } from '../../../helpers/common_regex_patterns';
@ -39,5 +37,3 @@ const NewProjectPipeline = {
};
export { NewProjectPipeline };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -6,6 +6,11 @@ const DeletePipeline = {
},
},
scenario: {
state: 'a pipeline for a project exists',
uponReceiving: 'a request to delete the pipeline',
},
request: {
method: 'POST',
path: '/api/graphql',

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
import {
URL,
@ -225,8 +223,12 @@ const ProjectPipelines = {
body,
},
request: {
scenario: {
state: 'a few pipelines for a project exists',
uponReceiving: 'a request for a list of project pipelines',
},
request: {
withRequest: {
method: 'GET',
path: '/gitlab-org/gitlab-qa/-/pipelines.json',
@ -239,5 +241,3 @@ const ProjectPipelines = {
};
export { ProjectPipelines };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
import {
JOB_STATUSES,
@ -83,6 +81,11 @@ const PipelineHeaderData = {
body,
},
scenario: {
state: 'a pipeline for a project exists',
uponReceiving: 'a request for the pipeline header data',
},
request: {
method: 'POST',
path: '/api/graphql',
@ -95,5 +98,3 @@ const PipelineHeaderData = {
};
export { PipelineHeaderData };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
import { REDIRECT_HTML } from '../../../helpers/common_regex_patterns';
@ -44,5 +42,3 @@ const UpdatePipelineSchedule = {
};
export { UpdatePipelineSchedule };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,9 +1,9 @@
import { request } from 'axios';
import axios from 'axios';
export function getDiffsMetadata(endpoint) {
export async function getDiffsMetadata(endpoint) {
const { url } = endpoint;
return request({
return axios({
method: 'GET',
baseURL: url,
url: '/gitlab-org/gitlab-qa/-/merge_requests/1/diffs_metadata.json',
@ -11,10 +11,10 @@ export function getDiffsMetadata(endpoint) {
}).then((response) => response.data);
}
export function getDiscussions(endpoint) {
export async function getDiscussions(endpoint) {
const { url } = endpoint;
return request({
return axios({
method: 'GET',
baseURL: url,
url: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
@ -22,10 +22,10 @@ export function getDiscussions(endpoint) {
}).then((response) => response.data);
}
export function getDiffsBatch(endpoint) {
export async function getDiffsBatch(endpoint) {
const { url } = endpoint;
return request({
return axios({
method: 'GET',
baseURL: url,
url: '/gitlab-org/gitlab-qa/-/merge_requests/1/diffs_batch.json?page=0',

View file

@ -1,5 +1,20 @@
import axios from 'axios';
export async function getProjectPipelines(endpoint) {
const { url } = endpoint;
return axios({
method: 'GET',
baseURL: url,
url: '/gitlab-org/gitlab-qa/-/pipelines.json',
headers: { Accept: '*/*' },
params: {
scope: 'all',
page: 1,
},
}).then((response) => response.data);
}
export async function postProjectPipelines(endpoint) {
const { url } = endpoint;

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { pactWith } from 'jest-pact';
import { DiffsBatch } from '../../../fixtures/project/merge_request/diffs_batch.fixture';
@ -9,7 +7,7 @@ import {
getDiffsBatch,
getDiffsMetadata,
getDiscussions,
} from '../../../endpoints/project/merge_requests';
} from '../../../resources/api/project/merge_requests';
const CONSUMER_NAME = 'MergeRequest#show';
const CONSUMER_LOG = '../logs/consumer.log';
@ -31,19 +29,19 @@ pactWith(
describe(DIFFS_BATCH_PROVIDER_NAME, () => {
beforeEach(() => {
const interaction = {
state: 'a merge request with diffs exists',
...DiffsBatch.scenario,
...DiffsBatch.request,
willRespondWith: DiffsBatch.success,
};
provider.addInteraction(interaction);
});
it('returns a successful body', () => {
return getDiffsBatch({
it('returns a successful body', async () => {
const diffsBatch = await getDiffsBatch({
url: provider.mockService.baseUrl,
}).then((diffsBatch) => {
expect(diffsBatch).toEqual(DiffsBatch.body);
});
expect(diffsBatch).toEqual(DiffsBatch.body);
});
});
},
@ -61,19 +59,19 @@ pactWith(
describe(DISCUSSIONS_PROVIDER_NAME, () => {
beforeEach(() => {
const interaction = {
state: 'a merge request with discussions exists',
...Discussions.scenario,
...Discussions.request,
willRespondWith: Discussions.success,
};
provider.addInteraction(interaction);
});
it('return a successful body', () => {
return getDiscussions({
it('return a successful body', async () => {
const discussions = await getDiscussions({
url: provider.mockService.baseUrl,
}).then((discussions) => {
expect(discussions).toEqual(Discussions.body);
});
expect(discussions).toEqual(Discussions.body);
});
});
},
@ -91,22 +89,20 @@ pactWith(
describe(DIFFS_METADATA_PROVIDER_NAME, () => {
beforeEach(() => {
const interaction = {
state: 'a merge request exists',
...DiffsMetadata.scenario,
...DiffsMetadata.request,
willRespondWith: DiffsMetadata.success,
};
provider.addInteraction(interaction);
});
it('return a successful body', () => {
return getDiffsMetadata({
it('return a successful body', async () => {
const diffsMetadata = await getDiffsMetadata({
url: provider.mockService.baseUrl,
}).then((diffsMetadata) => {
expect(diffsMetadata).toEqual(DiffsMetadata.body);
});
expect(diffsMetadata).toEqual(DiffsMetadata.body);
});
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,9 +1,7 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { pactWith } from 'jest-pact';
import { ProjectPipelines } from '../../../fixtures/project/pipeline/get_list_project_pipelines.fixture';
import { getProjectPipelines } from '../../../endpoints/project/pipelines';
import { getProjectPipelines } from '../../../resources/api/project/pipelines';
const CONSUMER_NAME = 'Pipelines#index';
const CONSUMER_LOG = '../logs/consumer.log';
@ -23,22 +21,20 @@ pactWith(
describe(PROVIDER_NAME, () => {
beforeEach(() => {
const interaction = {
state: 'a few pipelines for a project exists',
...ProjectPipelines.scenario,
...ProjectPipelines.request,
willRespondWith: ProjectPipelines.success,
};
provider.addInteraction(interaction);
});
it('returns a successful body', () => {
return getProjectPipelines({
it('returns a successful body', async () => {
const pipelines = await getProjectPipelines({
url: provider.mockService.baseUrl,
}).then((pipelines) => {
expect(pipelines).toEqual(ProjectPipelines.body);
});
expect(pipelines).toEqual(ProjectPipelines.body);
});
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { pactWith } from 'jest-pact';
import { NewProjectPipeline } from '../../../fixtures/project/pipeline/create_a_new_pipeline.fixture';
@ -41,5 +39,3 @@ pactWith(
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { pactWith } from 'jest-pact';
import { GraphQLInteraction } from '@pact-foundation/pact';
@ -32,8 +30,8 @@ pactWith(
'app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql',
);
const graphqlQuery = new GraphQLInteraction()
.given('a pipeline for a project exists')
.uponReceiving('a request for the pipeline header data')
.given(PipelineHeaderData.scenario.state)
.uponReceiving(PipelineHeaderData.scenario.uponReceiving)
.withQuery(query)
.withRequest(PipelineHeaderData.request)
.withVariables(PipelineHeaderData.variables)
@ -69,8 +67,8 @@ pactWith(
'app/assets/javascripts/pipelines/graphql/mutations/delete_pipeline.mutation.graphql',
);
const graphqlQuery = new GraphQLInteraction()
.given('a pipeline for a project exists')
.uponReceiving('a request to delete the pipeline')
.given(DeletePipeline.scenario.state)
.uponReceiving(DeletePipeline.scenario.uponReceiving)
.withQuery(query)
.withRequest(DeletePipeline.request)
.withVariables(DeletePipeline.variables)
@ -89,5 +87,3 @@ pactWith(
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -1,5 +1,3 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { pactWith } from 'jest-pact';
import { UpdatePipelineSchedule } from '../../../fixtures/project/pipeline_schedule/update_pipeline_schedule.fixture';
@ -41,5 +39,3 @@ pactWith(
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -7,7 +7,7 @@
},
"interactions": [
{
"description": "a request for Diffs Metadata",
"description": "a request for diffs metadata",
"providerState": "a merge request exists",
"request": {
"method": "GET",
@ -220,4 +220,4 @@
"version": "2.0.0"
}
}
}
}

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../spec_helper'
require_relative '../../../states/project/merge_request/diffs_batch_state'
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/merge_request/show_state'
module Provider
module DiffsBatchHelper

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../spec_helper'
require_relative '../../../states/project/merge_request/diffs_metadata_state'
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/merge_request/show_state'
module Provider
module DiffsMetadataHelper

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../spec_helper'
require_relative '../../../states/project/merge_request/discussions_state'
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/merge_request/show_state'
module Provider
module DiscussionsHelper

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../spec_helper'
require_relative '../../../states/project/pipeline/new_state'
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/pipeline/new_state'
module Provider
module CreateNewPipelineHelper

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../spec_helper'
require_relative '../../../states/project/pipeline/pipelines_state'
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/pipeline/index_state'
module Provider
module GetListProjectPipelinesHelper

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/pipeline/pipeline_state'
require_relative '../../../../states/project/pipeline/show_state'
module Provider
module GetPipelinesHeaderDataHelper

View file

@ -1,18 +0,0 @@
# frozen_string_literal: true
Pact.provider_states_for "MergeRequest#show" do
provider_state "a merge request with diffs exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, :custom_repo, name: 'gitlab-qa', namespace: namespace, files: {})
project.add_maintainer(user)
merge_request = create(:merge_request_with_multiple_diffs, source_project: project)
merge_request_diff = create(:merge_request_diff, merge_request: merge_request)
create(:merge_request_diff_file, :new_file, merge_request_diff: merge_request_diff)
end
end
end

View file

@ -1,18 +0,0 @@
# frozen_string_literal: true
Pact.provider_states_for "MergeRequest#show" do
provider_state "a merge request exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, :custom_repo, name: 'gitlab-qa', namespace: namespace, files: {})
project.add_maintainer(user)
merge_request = create(:merge_request, source_project: project)
merge_request_diff = create(:merge_request_diff, merge_request: merge_request)
create(:merge_request_diff_file, :new_file, merge_request_diff: merge_request_diff)
end
end
end

View file

@ -1,17 +0,0 @@
# frozen_string_literal: true
Pact.provider_states_for "MergeRequest#show" do
provider_state "a merge request with discussions exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, name: 'gitlab-qa', namespace: namespace)
project.add_maintainer(user)
merge_request = create(:merge_request_with_diffs, source_project: project, author: user)
create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: user)
end
end
end

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
Pact.provider_states_for "MergeRequest#show" do
provider_state "a merge request with diffs exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, :custom_repo, name: 'gitlab-qa', namespace: namespace, files: {})
project.add_maintainer(user)
merge_request = create(:merge_request_with_multiple_diffs, source_project: project)
merge_request_diff = create(:merge_request_diff, merge_request: merge_request)
create(:merge_request_diff_file, :new_file, merge_request_diff: merge_request_diff)
end
end
provider_state "a merge request exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, :custom_repo, name: 'gitlab-qa', namespace: namespace, files: {})
project.add_maintainer(user)
merge_request = create(:merge_request, source_project: project)
merge_request_diff = create(:merge_request_diff, merge_request: merge_request)
create(:merge_request_diff_file, :new_file, merge_request_diff: merge_request_diff)
end
end
provider_state "a merge request with discussions exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, name: 'gitlab-qa', namespace: namespace)
project.add_maintainer(user)
merge_request = create(:merge_request_with_diffs, source_project: project, author: user)
create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: user)
end
end
end

View file

@ -1,27 +0,0 @@
# frozen_string_literal: true
Pact.provider_states_for "Pipelines#show" do
provider_state "a pipeline for a project exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, :repository, name: 'gitlab-qa', namespace: namespace, creator: user)
scheduled_job = create(:ci_build, :scheduled)
manual_job = create(:ci_build, :manual)
project.add_maintainer(user)
create(
:ci_pipeline,
:with_job,
:success,
iid: 1,
project: project,
user: user,
duration: 10,
finished_at: '2022-06-01T02:47:31.432Z',
builds: [scheduled_job, manual_job]
)
end
end
end

View file

@ -268,7 +268,7 @@ describe('IDE store getters', () => {
currentProject: undefined,
};
expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
expect(getters.isOnDefaultBranch({}, localGetters)).toBe(undefined);
});
it("returns true when project's default branch matches current branch", () => {
@ -279,7 +279,7 @@ describe('IDE store getters', () => {
branchName: 'main',
};
expect(getters.isOnDefaultBranch({}, localGetters)).toBeTruthy();
expect(getters.isOnDefaultBranch({}, localGetters)).toBe(true);
});
it("returns false when project's default branch doesn't match current branch", () => {
@ -290,7 +290,7 @@ describe('IDE store getters', () => {
branchName: 'feature',
};
expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
expect(getters.isOnDefaultBranch({}, localGetters)).toBe(false);
});
});

View file

@ -1,8 +1,9 @@
import { GlSearchBoxByClick } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import JobLogControllers from '~/jobs/components/job_log_controllers.vue';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { backoffMockImplementation } from 'helpers/backoff_helper';
import * as commonUtils from '~/lib/utils/common_utils';
import { mockJobLog } from '../mock_data';
const mockToastShow = jest.fn();
@ -10,10 +11,15 @@ const mockToastShow = jest.fn();
describe('Job log controllers', () => {
let wrapper;
beforeEach(() => {
jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation);
});
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
}
commonUtils.backOff.mockReset();
});
const defaultProps = {
@ -24,10 +30,11 @@ describe('Job log controllers', () => {
isScrollBottomDisabled: false,
isScrollingDown: true,
isJobLogSizeVisible: true,
isComplete: true,
jobLog: mockJobLog,
};
const createWrapper = (props, jobLogSearch = false) => {
const createWrapper = (props, { jobLogSearch = false, jobLogJumpToFailures = false } = {}) => {
wrapper = mount(JobLogControllers, {
propsData: {
...defaultProps,
@ -36,6 +43,7 @@ describe('Job log controllers', () => {
provide: {
glFeatures: {
jobLogSearch,
jobLogJumpToFailures,
},
},
data() {
@ -58,6 +66,7 @@ describe('Job log controllers', () => {
const findScrollBottom = () => wrapper.find('[data-testid="job-controller-scroll-bottom"]');
const findJobLogSearch = () => wrapper.findComponent(GlSearchBoxByClick);
const findSearchHelp = () => wrapper.findComponent(HelpPopover);
const findScrollFailure = () => wrapper.find('[data-testid="job-controller-scroll-to-failure"]');
describe('Truncate information', () => {
describe('with isJobLogSizeVisible', () => {
@ -109,9 +118,7 @@ describe('Job log controllers', () => {
});
it('emits scrollJobLogTop event on click', async () => {
findScrollTop().trigger('click');
await nextTick();
await findScrollTop().trigger('click');
expect(wrapper.emitted().scrollJobLogTop).toHaveLength(1);
});
@ -131,9 +138,7 @@ describe('Job log controllers', () => {
});
it('does not emit scrollJobLogTop event on click', async () => {
findScrollTop().trigger('click');
await nextTick();
await findScrollTop().trigger('click');
expect(wrapper.emitted().scrollJobLogTop).toBeUndefined();
});
@ -147,9 +152,7 @@ describe('Job log controllers', () => {
});
it('emits scrollJobLogBottom event on click', async () => {
findScrollBottom().trigger('click');
await nextTick();
await findScrollBottom().trigger('click');
expect(wrapper.emitted().scrollJobLogBottom).toHaveLength(1);
});
@ -169,9 +172,7 @@ describe('Job log controllers', () => {
});
it('does not emit scrollJobLogBottom event on click', async () => {
findScrollBottom().trigger('click');
await nextTick();
await findScrollBottom().trigger('click');
expect(wrapper.emitted().scrollJobLogBottom).toBeUndefined();
});
@ -201,6 +202,91 @@ describe('Job log controllers', () => {
});
});
});
describe('scroll to failure button', () => {
describe('with feature flag disabled', () => {
it('does not display button', () => {
createWrapper();
expect(findScrollFailure().exists()).toBe(false);
});
});
describe('with red text failures on the page', () => {
let firstFailure;
let secondFailure;
beforeEach(() => {
jest.spyOn(document, 'querySelectorAll').mockReturnValueOnce(['mock-element']);
createWrapper({}, { jobLogJumpToFailures: true });
firstFailure = document.createElement('div');
firstFailure.className = 'term-fg-l-red';
document.body.appendChild(firstFailure);
secondFailure = document.createElement('div');
secondFailure.className = 'term-fg-l-red';
document.body.appendChild(secondFailure);
});
afterEach(() => {
if (firstFailure) {
firstFailure.remove();
firstFailure = null;
}
if (secondFailure) {
secondFailure.remove();
secondFailure = null;
}
});
it('is enabled', () => {
expect(findScrollFailure().props('disabled')).toBe(false);
});
it('scrolls to each failure', async () => {
jest.spyOn(firstFailure, 'scrollIntoView');
await findScrollFailure().trigger('click');
expect(firstFailure.scrollIntoView).toHaveBeenCalled();
await findScrollFailure().trigger('click');
expect(secondFailure.scrollIntoView).toHaveBeenCalled();
await findScrollFailure().trigger('click');
expect(firstFailure.scrollIntoView).toHaveBeenCalled();
});
});
describe('with no red text failures on the page', () => {
beforeEach(() => {
jest.spyOn(document, 'querySelectorAll').mockReturnValueOnce([]);
createWrapper({}, { jobLogJumpToFailures: true });
});
it('is disabled', () => {
expect(findScrollFailure().props('disabled')).toBe(true);
});
});
describe('when the job log is not complete', () => {
beforeEach(() => {
jest.spyOn(document, 'querySelectorAll').mockReturnValueOnce(['mock-element']);
createWrapper({ isComplete: false }, { jobLogJumpToFailures: true });
});
it('is enabled', () => {
expect(findScrollFailure().props('disabled')).toBe(false);
});
});
});
});
describe('Job log search', () => {

View file

@ -316,7 +316,7 @@ describe('Actions menu', () => {
});
it('is not disabled', () => {
expect(findStarDashboardItem().attributes('disabled')).toBeFalsy();
expect(findStarDashboardItem().attributes('disabled')).toBeUndefined();
});
it('is disabled when starring is taking place', async () => {