Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-05 00:08:41 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-05 00:08:41 +0300
commit9e19896fb55fe053414109dcfb6e4f3142918e08 (patch)
treeea488213c66a9adc11496e63cdb658e075af48d0
parentb3555357704e2776fc0c960eaf931b0e9b0f0ddf (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue9
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js22
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue3
-rw-r--r--app/assets/javascripts/environments/index.js21
-rw-r--r--app/assets/javascripts/environments/mixins/canary_callout_mixin.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue49
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js1
-rw-r--r--app/controllers/invites_controller.rb6
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/member.rb4
-rw-r--r--app/services/snippets/base_service.rb2
-rw-r--r--app/views/projects/environments/folder.html.haml2
-rw-r--r--app/views/projects/environments/index.html.haml3
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml2
-rw-r--r--changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml5
-rw-r--r--changelogs/unreleased/220540-drop-ds-sast-dind.yml5
-rw-r--r--changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml5
-rw-r--r--changelogs/unreleased/vij-snippet-status-codes.yml5
-rw-r--r--doc/install/aws/index.md48
-rw-r--r--doc/user/application_security/img/adding_a_dismissal_reason_v13_0.pngbin35841 -> 0 bytes
-rw-r--r--doc/user/application_security/img/adding_a_dismissal_reason_v13_4.pngbin0 -> 25574 bytes
-rw-r--r--doc/user/application_security/img/create_issue_with_list_hover.pngbin36833 -> 0 bytes
-rw-r--r--doc/user/application_security/img/create_mr_from_vulnerability_v13_4.pngbin0 -> 33743 bytes
-rw-r--r--doc/user/application_security/index.md40
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml77
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml53
-rw-r--r--locale/gitlab.pot13
-rw-r--r--spec/controllers/invites_controller_spec.rb148
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js19
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js1
-rw-r--r--spec/models/member_spec.rb8
-rw-r--r--spec/requests/api/snippets_spec.rb2
-rw-r--r--spec/services/snippets/create_service_spec.rb2
-rw-r--r--spec/services/snippets/update_service_spec.rb2
-rw-r--r--spec/support/shared_examples/services/snippets_shared_examples.rb17
36 files changed, 348 insertions, 233 deletions
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 1bf705dcda2..c06ab265915 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -14,6 +14,7 @@ export default {
DeployBoard: () => import('ee_component/environments/components/deploy_board_component.vue'),
CanaryDeploymentCallout: () =>
import('ee_component/environments/components/canary_deployment_callout.vue'),
+ EnvironmentAlert: () => import('ee_component/environments/components/environment_alert.vue'),
},
props: {
environments: {
@@ -111,6 +112,9 @@ export default {
shouldShowCanaryCallout(env) {
return env.showCanaryCallout && this.showCanaryDeploymentCallout;
},
+ shouldRenderAlert(env) {
+ return env?.has_opened_alert;
+ },
sortEnvironments(environments) {
/*
* The sorting algorithm should sort in the following priorities:
@@ -185,6 +189,11 @@ export default {
/>
</div>
</div>
+ <environment-alert
+ v-if="shouldRenderAlert(model)"
+ :key="`alert-row-${i}`"
+ :environment="model"
+ />
<template v-if="shouldRenderFolderContent(model)">
<div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`">
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 56896ac4d43..6c547c3713a 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,20 +1,33 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import canaryCalloutMixin from '../mixins/canary_callout_mixin';
import environmentsFolderApp from './environments_folder_view.vue';
import { parseBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
+import createDefaultClient from '~/lib/graphql';
Vue.use(Translate);
+Vue.use(VueApollo);
-export default () =>
- new Vue({
- el: '#environments-folder-list-view',
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export default () => {
+ const el = document.getElementById('environments-folder-list-view');
+
+ return new Vue({
+ el,
components: {
environmentsFolderApp,
},
mixins: [canaryCalloutMixin],
+ apolloProvider,
+ provide: {
+ projectPath: el.dataset.projectPath,
+ },
data() {
- const environmentsData = document.querySelector(this.$options.el).dataset;
+ const environmentsData = el.dataset;
return {
endpoint: environmentsData.environmentsDataEndpoint,
@@ -35,3 +48,4 @@ export default () =>
});
},
});
+};
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index e1e356a977f..16d25615779 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -23,7 +23,8 @@ export default {
},
cssContainerClass: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
canReadEnvironment: {
type: Boolean,
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index 4848cb0f13d..8e8af3f32f7 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -1,20 +1,32 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import canaryCalloutMixin from './mixins/canary_callout_mixin';
import environmentsComponent from './components/environments_app.vue';
import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
+import createDefaultClient from '~/lib/graphql';
Vue.use(Translate);
+Vue.use(VueApollo);
-export default () =>
- new Vue({
- el: '#environments-list-view',
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export default () => {
+ const el = document.getElementById('environments-list-view');
+ return new Vue({
+ el,
components: {
environmentsComponent,
},
mixins: [canaryCalloutMixin],
+ apolloProvider,
+ provide: {
+ projectPath: el.dataset.projectPath,
+ },
data() {
- const environmentsData = document.querySelector(this.$options.el).dataset;
+ const environmentsData = el.dataset;
return {
endpoint: environmentsData.environmentsDataEndpoint,
@@ -39,3 +51,4 @@ export default () =>
});
},
});
+};
diff --git a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
index 398576a31cb..e9f1a144cb3 100644
--- a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
+++ b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
@@ -2,7 +2,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
export default {
data() {
- const data = document.querySelector(this.$options.el).dataset;
+ const data = this.$options.el.dataset;
return {
canaryDeploymentFeatureId: data.environmentsDataCanaryDeploymentFeatureId,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 3e2c119fa7c..5066a88b52b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -1,8 +1,15 @@
<script>
/* eslint-disable vue/require-default-prop, vue/no-v-html */
-import { GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import {
+ GlIcon,
+ GlLink,
+ GlLoadingIcon,
+ GlSprintf,
+ GlTooltip,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
-import { s__ } from '~/locale';
+import { s__, n__ } from '~/locale';
import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
@@ -15,6 +22,7 @@ export default {
GlLoadingIcon,
GlIcon,
GlSprintf,
+ GlTooltip,
PipelineStage,
TooltipOnTruncate,
LinkedPipelinesMiniList: () =>
@@ -33,6 +41,11 @@ export default {
type: String,
required: false,
},
+ buildsWithCoverage: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
// This prop needs to be camelCase, html attributes are case insensive
// https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
hasCi: {
@@ -100,6 +113,16 @@ export default {
}
return '';
},
+ pipelineCoverageJobNumberText() {
+ return n__('from %d job', 'from %d jobs', this.buildsWithCoverage.length);
+ },
+ pipelineCoverageTooltipDescription() {
+ return n__(
+ 'Coverage value for this pipeline was calculated by the coverage value of %d job.',
+ 'Coverage value for this pipeline was calculated by averaging the resulting coverage values of %d jobs.',
+ this.buildsWithCoverage.length,
+ );
+ },
},
errorText: s__(
'Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}.',
@@ -139,7 +162,7 @@ export default {
>
<gl-icon
name="question"
- :small="12"
+ :size="12"
tabindex="0"
role="text"
:aria-label="__('Link to go to GitLab pipeline documentation')"
@@ -189,14 +212,30 @@ export default {
</div>
<div v-if="pipeline.coverage" class="coverage" data-testid="pipeline-coverage">
{{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
-
<span
v-if="pipelineCoverageDelta"
:class="coverageDeltaClass"
data-testid="pipeline-coverage-delta"
+ >({{ pipelineCoverageDelta }}%)</span
>
- ({{ pipelineCoverageDelta }}%)
+
+ {{ pipelineCoverageJobNumberText }}
+ <span ref="pipelineCoverageQuestion">
+ <gl-icon name="question" :size="12" />
</span>
+ <gl-tooltip
+ :target="() => $refs.pipelineCoverageQuestion"
+ data-testid="pipeline-coverage-tooltip"
+ >
+ {{ pipelineCoverageTooltipDescription }}
+ <div
+ v-for="(build, index) in buildsWithCoverage"
+ :key="`${build.name}-${index}`"
+ class="gl-mt-3 gl-text-left gl-px-4"
+ >
+ {{ build.name }} ({{ build.coverage }}%)
+ </div>
+ </gl-tooltip>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 5c307b5ff0c..55efd7e7d3b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -77,6 +77,7 @@ export default {
<mr-widget-pipeline
:pipeline="pipeline"
:pipeline-coverage-delta="mr.pipelineCoverageDelta"
+ :builds-with-coverage="mr.buildsWithCoverage"
:ci-status="mr.ciStatus"
:has-ci="mr.hasCI"
:pipeline-must-succeed="mr.onlyAllowMergeIfPipelineSucceeds"
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 8c98ba1b023..5695e02bbab 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -52,6 +52,7 @@ export default class MergeRequestStore {
this.divergedCommitsCount = data.diverged_commits_count;
this.pipeline = data.pipeline || {};
this.pipelineCoverageDelta = data.pipeline_coverage_delta;
+ this.buildsWithCoverage = data.builds_with_coverage;
this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
this.postMergeDeployments = this.postMergeDeployments || [];
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index aa02bc132f9..e65b27db3ae 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -76,8 +76,12 @@ class InvitesController < ApplicationController
notice << "or create an account" if Gitlab::CurrentSettings.allow_signup?
notice = notice.join(' ') + "."
+ initial_member = Member.find_by_invite_token(params[:id])
+ redirect_params = initial_member ? { invite_email: member.invite_email } : {}
+
store_location_for :user, request.fullpath
- redirect_to new_user_session_path(invite_email: member.invite_email), notice: notice
+
+ redirect_to new_user_session_path(redirect_params), notice: notice
end
def invite_details
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index a6986029f0d..78161a3b446 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -177,10 +177,6 @@ module Issuable
assignees.count > 1
end
- def supports_weight?
- false
- end
-
def supports_time_tracking?
is_a?(TimeTrackable) && !incident?
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 1913df61614..55bb78b7f94 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -161,8 +161,8 @@ class Member < ApplicationRecord
where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
end
- def find_by_invite_token(invite_token)
- invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
+ def find_by_invite_token(raw_invite_token)
+ invite_token = Devise.token_generator.digest(self, :invite_token, raw_invite_token)
find_by(invite_token: invite_token)
end
diff --git a/app/services/snippets/base_service.rb b/app/services/snippets/base_service.rb
index d9e8326f159..53a04e5a398 100644
--- a/app/services/snippets/base_service.rb
+++ b/app/services/snippets/base_service.rb
@@ -46,7 +46,7 @@ module Snippets
snippet.errors.add(:snippet_actions, 'have invalid data')
end
- snippet_error_response(snippet, 403)
+ snippet_error_response(snippet, 422)
end
def snippet_error_response(snippet, http_status)
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
index cd24c30e46f..554cb4323f7 100644
--- a/app/views/projects/environments/folder.html.haml
+++ b/app/views/projects/environments/folder.html.haml
@@ -2,4 +2,4 @@
- breadcrumb_title _("Folder/%{name}") % { name: @folder }
- page_title _("Environments in %{name}") % { name: @folder }
-#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data } }
+#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data, project_path: @project.full_path } }
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 445196ed449..9abc1a5a925 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -5,4 +5,5 @@
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments/index.md"),
- "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } }
+ "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards"),
+ "project-path" => @project.full_path } }
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index 82e435fe0d3..79e6f043b64 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -30,7 +30,7 @@
= render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form
- - if has_due_date || issuable.supports_weight?
+ - if has_due_date
.col-lg-6
- if @issue[:issue_type] != 'incident'
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
diff --git a/changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml b/changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml
new file mode 100644
index 00000000000..3dbbd22e002
--- /dev/null
+++ b/changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml
@@ -0,0 +1,5 @@
+---
+title: Show multiple jobs contributing to code coverage
+merge_request: 41217
+author:
+type: added
diff --git a/changelogs/unreleased/220540-drop-ds-sast-dind.yml b/changelogs/unreleased/220540-drop-ds-sast-dind.yml
new file mode 100644
index 00000000000..d1c2818aa94
--- /dev/null
+++ b/changelogs/unreleased/220540-drop-ds-sast-dind.yml
@@ -0,0 +1,5 @@
+---
+title: Drop Docker-in-Docker mode for SAST and Dependency Scanning
+merge_request: 41260
+author:
+type: removed
diff --git a/changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml b/changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml
new file mode 100644
index 00000000000..36c5c819f00
--- /dev/null
+++ b/changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml
@@ -0,0 +1,5 @@
+---
+title: 'Resolve NoMethodError: undefined method invite_email'
+merge_request: 41587
+author:
+type: fixed
diff --git a/changelogs/unreleased/vij-snippet-status-codes.yml b/changelogs/unreleased/vij-snippet-status-codes.yml
new file mode 100644
index 00000000000..49ce9a00e33
--- /dev/null
+++ b/changelogs/unreleased/vij-snippet-status-codes.yml
@@ -0,0 +1,5 @@
+---
+title: Change invalid Snippet params status code from 403 to 422
+merge_request: 40619
+author:
+type: changed
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 92a4ce860c3..797721aca0d 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -68,28 +68,32 @@ As we'll be using [Amazon S3 object storage](#amazon-s3-object-storage), our EC2
1. Click **Create policy**, select the `JSON` tab, and add a policy. We want to [follow security best practices and grant _least privilege_](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege), giving our role only the permissions needed to perform the required actions.
1. Assuming you prefix the S3 bucket names with `gl-` as shown in the diagram, add the following policy:
-```json
-{
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:AbortMultipartUpload",
- "s3:CompleteMultipartUpload",
- "s3:ListBucket",
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:PutObjectAcl"
- ],
- "Resource": [
- "arn:aws:s3:::gl-*/*"
- ]
- }
- ]
-}
-```
+ ```json
+ { "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:PutObject",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ "s3:PutObjectAcl"
+ ],
+ "Resource": "arn:aws:s3:::gl-*/*"
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:ListBucket",
+ "s3:AbortMultipartUpload",
+ "s3:ListMultipartUploadParts",
+ "s3:ListBucketMultipartUploads"
+ ],
+ "Resource": "arn:aws:s3:::gl-*"
+ }
+ ]
+ }
+ ```
1. Click **Review policy**, give your policy a name (we'll use `gl-s3-policy`), and click **Create policy**.
diff --git a/doc/user/application_security/img/adding_a_dismissal_reason_v13_0.png b/doc/user/application_security/img/adding_a_dismissal_reason_v13_0.png
deleted file mode 100644
index cb8911b14b1..00000000000
--- a/doc/user/application_security/img/adding_a_dismissal_reason_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/img/adding_a_dismissal_reason_v13_4.png b/doc/user/application_security/img/adding_a_dismissal_reason_v13_4.png
new file mode 100644
index 00000000000..8e7bcf09428
--- /dev/null
+++ b/doc/user/application_security/img/adding_a_dismissal_reason_v13_4.png
Binary files differ
diff --git a/doc/user/application_security/img/create_issue_with_list_hover.png b/doc/user/application_security/img/create_issue_with_list_hover.png
deleted file mode 100644
index 4c38862e68f..00000000000
--- a/doc/user/application_security/img/create_issue_with_list_hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/img/create_mr_from_vulnerability_v13_4.png b/doc/user/application_security/img/create_mr_from_vulnerability_v13_4.png
new file mode 100644
index 00000000000..a914c2996f7
--- /dev/null
+++ b/doc/user/application_security/img/create_mr_from_vulnerability_v13_4.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 7d0b14a8326..da348cfc9c4 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -182,7 +182,7 @@ vulnerability's status to Dismissed, a text box appears for you to add a comment
dismissal. Once added, you can edit or delete it. This allows you to add and update context for a
vulnerability as you learn more over time.
-![Dismissed vulnerability comment](img/adding_a_dismissal_reason_v13_0.png)
+![Dismissed vulnerability comment](img/adding_a_dismissal_reason_v13_4.png)
#### Dismissing multiple vulnerabilities
@@ -197,23 +197,6 @@ Pressing the "Dismiss Selected" button will dismiss all the selected vulnerabili
![Multiple vulnerability dismissal](img/multi_select_v12_9.png)
-### Creating an issue for a vulnerability
-
-You can create an issue for a vulnerability by visiting the vulnerability's page and clicking
-**Create issue**, which you can find in the **Related issues** section.
-
-![Create issue from vulnerability](img/create_issue_from_vulnerability_v13_3.png)
-
-This creates a [confidential issue](../project/issues/confidential_issues.md) in the project the
-vulnerability came from, and pre-populates it with some useful information taken from the vulnerability
-report. Once the issue is created, you are redirected to it so you can edit, assign, or comment on
-it.
-
-Upon returning to the group security dashboard, the vulnerability now has an associated issue next
-to the name.
-
-![Linked issue in the group security dashboard](img/issue.png)
-
### Solutions for vulnerabilities (auto-remediation)
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5656) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.7.
@@ -248,10 +231,27 @@ vulnerability. Any vulnerability that has a
[solution](#solutions-for-vulnerabilities-auto-remediation) can have a merge
request created to automatically solve the issue.
-If this action is available, the vulnerability modal contains a **Create merge request** button.
+If this action is available, the vulnerability page or modal contains a **Create merge request** button.
Click this button to create a merge request to apply the solution onto the source branch.
-![Create merge request from vulnerability](img/create_issue_with_list_hover.png)
+![Create merge request from vulnerability](img/create_mr_from_vulnerability_v13_4.png)
+
+### Creating an issue for a vulnerability
+
+You can create an issue for a vulnerability by visiting the vulnerability's page and clicking
+**Create issue**, which you can find in the **Related issues** section.
+
+![Create issue from vulnerability](img/create_issue_from_vulnerability_v13_3.png)
+
+This creates a [confidential issue](../project/issues/confidential_issues.md) in the project the
+vulnerability came from, and pre-populates it with some useful information taken from the vulnerability
+report. Once the issue is created, you are redirected to it so you can edit, assign, or comment on
+it.
+
+Upon returning to the group security dashboard, the vulnerability now has an associated issue next
+to the name.
+
+![Linked issue in the group security dashboard](img/issue.png)
### Managing related issues for a vulnerability
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index 6e6dc023e79..3789f0edc1c 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -12,81 +12,24 @@ variables:
DS_DEFAULT_ANALYZERS: "bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python"
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
DS_MAJOR_VERSION: 2
- DS_DISABLE_DIND: "true"
dependency_scanning:
stage: test
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- DOCKER_TLS_CERTDIR: ""
- allow_failure: true
- services:
- - docker:stable-dind
script:
- - |
- if ! docker info &>/dev/null; then
- if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
- export DOCKER_HOST='tcp://localhost:2375'
- fi
- fi
- - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
- function propagate_env_vars() {
- CURRENT_ENV=$(printenv)
-
- for VAR_NAME; do
- echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
- done
- }
- - |
- docker run \
- $(propagate_env_vars \
- DS_ANALYZER_IMAGES \
- SECURE_ANALYZERS_PREFIX \
- DS_ANALYZER_IMAGE_TAG \
- DS_DEFAULT_ANALYZERS \
- DS_EXCLUDED_PATHS \
- DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
- DS_PULL_ANALYZER_IMAGE_TIMEOUT \
- DS_RUN_ANALYZER_TIMEOUT \
- DS_PYTHON_VERSION \
- DS_PIP_VERSION \
- DS_PIP_DEPENDENCY_PATH \
- DS_JAVA_VERSION \
- GEMNASIUM_DB_LOCAL_PATH \
- GEMNASIUM_DB_REMOTE_URL \
- GEMNASIUM_DB_REF_NAME \
- PIP_INDEX_URL \
- PIP_EXTRA_INDEX_URL \
- PIP_REQUIREMENTS_FILE \
- MAVEN_CLI_OPTS \
- GRADLE_CLI_OPTS \
- SBT_CLI_OPTS \
- BUNDLER_AUDIT_UPDATE_DISABLED \
- BUNDLER_AUDIT_ADVISORY_DB_URL \
- BUNDLER_AUDIT_ADVISORY_DB_REF_NAME \
- RETIREJS_JS_ADVISORY_DB \
- RETIREJS_NODE_ADVISORY_DB \
- DS_REMEDIATE \
- ) \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_MAJOR_VERSION" /code
+ - echo "$CI_JOB_NAME is used for configuration only, and its script should not be executed"
+ - exit 1
artifacts:
reports:
dependency_scanning: gl-dependency-scanning-report.json
dependencies: []
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'true'
- when: never
- - if: $CI_COMMIT_BRANCH &&
- $GITLAB_FEATURES =~ /\bdependency_scanning\b/
+ - when: never
.ds-analyzer:
extends: dependency_scanning
- services: []
+ allow_failure: true
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
+ - if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/
@@ -100,7 +43,7 @@ gemnasium-dependency_scanning:
variables:
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium:$DS_MAJOR_VERSION"
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
+ - if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
@@ -123,7 +66,7 @@ gemnasium-maven-dependency_scanning:
variables:
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
+ - if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
@@ -141,7 +84,7 @@ gemnasium-python-dependency_scanning:
variables:
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-python:$DS_MAJOR_VERSION"
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
+ - if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
@@ -166,7 +109,7 @@ bundler-audit-dependency_scanning:
variables:
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/bundler-audit:$DS_MAJOR_VERSION"
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
+ - if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
@@ -181,7 +124,7 @@ retire-js-dependency_scanning:
variables:
DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/retire.js:$DS_MAJOR_VERSION"
rules:
- - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false'
+ - if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 1fb8b6736aa..77ea11d01d1 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -12,45 +12,26 @@ variables:
SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, sobelow, pmd-apex, kubesec"
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
SAST_ANALYZER_IMAGE_TAG: 2
- SAST_DISABLE_DIND: "true"
SCAN_KUBERNETES_MANIFESTS: "false"
sast:
stage: test
- allow_failure: true
artifacts:
reports:
sast: gl-sast-report.json
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'true'
- when: never
- - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bsast\b/
- image: docker:stable
+ - when: never
variables:
SEARCH_MAX_DEPTH: 4
- DOCKER_DRIVER: overlay2
- DOCKER_TLS_CERTDIR: ""
- services:
- - docker:stable-dind
script:
- - |
- if ! docker info &>/dev/null; then
- if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
- export DOCKER_HOST='tcp://localhost:2375'
- fi
- fi
- - |
- docker run \
- $(awk 'BEGIN{for(v in ENVIRON) print v}' | grep -v -E '^(DOCKER_|CI|GITLAB_|FF_|HOME|PWD|OLDPWD|PATH|SHLVL|HOSTNAME)' | awk '{printf " -e %s", $0}') \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_ANALYZER_IMAGE_TAG" /app/bin/run /code
+ - echo "$CI_JOB_NAME is used for configuration only, and its script should not be executed"
+ - exit 1
.sast-analyzer:
extends: sast
- services: []
+ allow_failure: true
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH
script:
@@ -63,7 +44,7 @@ bandit-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/bandit:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /bandit/
@@ -77,7 +58,7 @@ brakeman-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/brakeman:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /brakeman/
@@ -91,7 +72,7 @@ eslint-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /eslint/
@@ -109,7 +90,7 @@ flawfinder-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /flawfinder/
@@ -124,7 +105,7 @@ kubesec-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/kubesec:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /kubesec/ &&
@@ -137,7 +118,7 @@ gosec-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gosec:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /gosec/
@@ -151,7 +132,7 @@ nodejs-scan-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /nodejs-scan/
@@ -165,7 +146,7 @@ phpcs-security-audit-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/phpcs-security-audit:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /phpcs-security-audit/
@@ -179,7 +160,7 @@ pmd-apex-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/pmd-apex:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /pmd-apex/
@@ -193,7 +174,7 @@ security-code-scan-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/security-code-scan:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /security-code-scan/
@@ -208,7 +189,7 @@ sobelow-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/sobelow:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /sobelow/
@@ -222,7 +203,7 @@ spotbugs-sast:
variables:
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/spotbugs:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
+ - if: $SAST_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
$SAST_DEFAULT_ANALYZERS =~ /spotbugs/
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4cff4073e4d..6910cf8f1a2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7102,6 +7102,11 @@ msgstr ""
msgid "Coverage Fuzzing"
msgstr ""
+msgid "Coverage value for this pipeline was calculated by the coverage value of %d job."
+msgid_plural "Coverage value for this pipeline was calculated by averaging the resulting coverage values of %d jobs."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Create"
msgstr ""
@@ -9536,6 +9541,9 @@ msgstr ""
msgid "Environments in %{name}"
msgstr ""
+msgid "EnvironmentsAlert|%{severity} • %{title} %{text}. %{linkStart}View Details%{linkEnd} · %{startedAt} "
+msgstr ""
+
msgid "EnvironmentsDashboard|Add a project to the dashboard"
msgstr ""
@@ -29512,6 +29520,11 @@ msgstr ""
msgid "from"
msgstr ""
+msgid "from %d job"
+msgid_plural "from %d jobs"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "group"
msgstr ""
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index a069a5dc050..3605699bb5d 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -3,96 +3,122 @@
require 'spec_helper'
RSpec.describe InvitesController do
- let(:token) { '123456' }
let_it_be(:user) { create(:user) }
- let(:member) { create(:project_member, :invited, invite_token: token, invite_email: user.email) }
+ let(:member) { create(:project_member, :invited, invite_email: user.email) }
+ let(:raw_invite_token) { member.raw_invite_token }
let(:project_members) { member.source.users }
let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) }
+ let(:params) { { id: raw_invite_token } }
before do
stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: 'localhost')
controller.instance_variable_set(:@member, member)
- sign_in(user)
end
describe 'GET #show' do
- let(:params) { { id: token } }
-
subject(:request) { get :show, params: params }
- it 'accepts user if invite email matches signed in user' do
- expect do
- request
- end.to change { project_members.include?(user) }.from(false).to(true)
-
- expect(response).to have_gitlab_http_status(:found)
- expect(flash[:notice]).to include 'You have been granted'
- end
+ context 'when logged in' do
+ before do
+ sign_in(user)
+ end
- it 'forces re-confirmation if email does not match signed in user' do
- member.invite_email = 'bogus@email.com'
+ it 'accepts user if invite email matches signed in user' do
+ expect do
+ request
+ end.to change { project_members.include?(user) }.from(false).to(true)
- expect do
- request
- end.not_to change { project_members.include?(user) }
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:notice]).to include 'You have been granted'
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(flash[:notice]).to be_nil
- end
+ it 'forces re-confirmation if email does not match signed in user' do
+ member.invite_email = 'bogus@email.com'
- context 'when new_user_invite is not set' do
- it 'does not track the user as experiment group' do
- expect(Gitlab::Tracking).not_to receive(:event)
+ expect do
+ request
+ end.not_to change { project_members.include?(user) }
- request
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(flash[:notice]).to be_nil
end
- end
- context 'when new_user_invite is experiment' do
- let(:params) { { id: token, new_user_invite: 'experiment' } }
+ context 'when new_user_invite is not set' do
+ it 'does not track the user as experiment group' do
+ expect(Gitlab::Tracking).not_to receive(:event)
- it 'tracks the user as experiment group' do
- expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
- 'Growth::Acquisition::Experiment::InviteEmail',
- 'opened',
- property: 'experiment_group',
- label: md5_member_global_id
- )
- expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
- 'Growth::Acquisition::Experiment::InviteEmail',
- 'accepted',
- property: 'experiment_group',
- label: md5_member_global_id
- )
+ request
+ end
+ end
- request
+ context 'when new_user_invite is experiment' do
+ let(:params) { { id: raw_invite_token, new_user_invite: 'experiment' } }
+
+ it 'tracks the user as experiment group' do
+ expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'opened',
+ property: 'experiment_group',
+ label: md5_member_global_id
+ )
+ expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'accepted',
+ property: 'experiment_group',
+ label: md5_member_global_id
+ )
+
+ request
+ end
+ end
+
+ context 'when new_user_invite is control' do
+ let(:params) { { id: raw_invite_token, new_user_invite: 'control' } }
+
+ it 'tracks the user as control group' do
+ expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'opened',
+ property: 'control_group',
+ label: md5_member_global_id
+ )
+ expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'accepted',
+ property: 'control_group',
+ label: md5_member_global_id
+ )
+
+ request
+ end
end
end
- context 'when new_user_invite is control' do
- let(:params) { { id: token, new_user_invite: 'control' } }
+ context 'when not logged in' do
+ context 'when inviter is a member' do
+ it 'is redirected to a new session with invite email param' do
+ request
- it 'tracks the user as control group' do
- expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
- 'Growth::Acquisition::Experiment::InviteEmail',
- 'opened',
- property: 'control_group',
- label: md5_member_global_id
- )
- expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
- 'Growth::Acquisition::Experiment::InviteEmail',
- 'accepted',
- property: 'control_group',
- label: md5_member_global_id
- )
+ expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
+ end
+ end
- request
+ context 'when inviter is not a member' do
+ let(:params) { { id: '_bogus_token_' } }
+
+ it 'is redirected to a new session' do
+ request
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
end
end
end
describe 'POST #accept' do
- let(:params) { { id: token } }
+ before do
+ sign_in(user)
+ end
subject(:request) { post :accept, params: params }
@@ -105,7 +131,7 @@ RSpec.describe InvitesController do
end
context 'when new_user_invite is experiment' do
- let(:params) { { id: token, new_user_invite: 'experiment' } }
+ let(:params) { { id: raw_invite_token, new_user_invite: 'experiment' } }
it 'tracks the user as experiment group' do
expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
@@ -120,7 +146,7 @@ RSpec.describe InvitesController do
end
context 'when new_user_invite is control' do
- let(:params) { { id: token, new_user_invite: 'control' } }
+ let(:params) { { id: raw_invite_token, new_user_invite: 'control' } }
it 'tracks the user as control group' do
expect(Gitlab::Tracking).to receive(:event).and_call_original.with(
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 6486826c3ec..7e7be1eaae9 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -29,6 +29,8 @@ describe('MRWidgetPipeline', () => {
const findAllPipelineStages = () => wrapper.findAll(PipelineStage);
const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]');
const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]');
+ const findPipelineCoverageTooltipText = () =>
+ wrapper.find('[data-testid="pipeline-coverage-tooltip"]').text();
const findMonitoringPipelineMessage = () =>
wrapper.find('[data-testid="monitoring-pipeline-message"]');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
@@ -140,6 +142,7 @@ describe('MRWidgetPipeline', () => {
createWrapper(
{
pipelineCoverageDelta: mockData.pipelineCoverageDelta,
+ buildsWithCoverage: mockData.buildsWithCoverage,
},
mount,
);
@@ -178,6 +181,22 @@ describe('MRWidgetPipeline', () => {
expect(findPipelineCoverageDelta().exists()).toBe(true);
expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`);
});
+
+ it('should render tooltip for jobs contributing to code coverage', () => {
+ const tooltipText = findPipelineCoverageTooltipText();
+ const expectedDescription = `Coverage value for this pipeline was calculated by averaging the resulting coverage values of ${mockData.buildsWithCoverage.length} jobs.`;
+
+ expect(tooltipText).toContain(expectedDescription);
+ });
+
+ it.each(mockData.buildsWithCoverage)(
+ 'should have name and coverage for build %s listed in tooltip',
+ build => {
+ const tooltipText = findPipelineCoverageTooltipText();
+
+ expect(tooltipText).toContain(`${build.name} (${build.coverage}%)`);
+ },
+ );
});
describe('without commit path', () => {
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index d64a7f88b6b..4688af30269 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -193,6 +193,7 @@ export default {
updated_at: '2017-04-07T15:28:44.800Z',
},
pipelineCoverageDelta: '15.25',
+ buildsWithCoverage: [{ name: 'karma', coverage: '40.2' }, { name: 'rspec', coverage: '80.4' }],
work_in_progress: false,
source_branch_exists: false,
mergeable_discussions_state: true,
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index b83c769284a..7905e3531bc 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -630,6 +630,14 @@ RSpec.describe Member do
end
end
+ describe '.find_by_invite_token' do
+ let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
+
+ it 'finds the member' do
+ expect(described_class.find_by_invite_token(member.raw_invite_token)).to eq member
+ end
+ end
+
describe "#invite_to_unknown_user?" do
subject { member.invite_to_unknown_user? }
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 81dd9022657..02f62b03170 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -417,7 +417,7 @@ RSpec.describe API::Snippets do
true | nil | '' | [create_action] | :bad_request
true | nil | nil | [bad_file_path] | :bad_request
true | nil | nil | [bad_previous_path] | :bad_request
- true | nil | nil | [invalid_move] | :forbidden
+ true | nil | nil | [invalid_move] | :unprocessable_entity
false | 'foo.txt' | 'bar' | nil | :success
false | 'foo.txt' | nil | nil | :success
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index 2106a9c2045..b7fb5a98d06 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -313,6 +313,7 @@ RSpec.describe Snippets::CreateService do
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions', ProjectSnippet
it_behaves_like 'when snippet_actions param is present'
+ it_behaves_like 'invalid params error response'
context 'when uploaded files are passed to the service' do
let(:extra_opts) { { files: ['foo'] } }
@@ -340,6 +341,7 @@ RSpec.describe Snippets::CreateService do
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions', PersonalSnippet
it_behaves_like 'when snippet_actions param is present'
+ it_behaves_like 'invalid params error response'
context 'when the snippet description contains files' do
include FileMoverHelpers
diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb
index 9d1fe8fe83f..641fc56294a 100644
--- a/spec/services/snippets/update_service_spec.rb
+++ b/spec/services/snippets/update_service_spec.rb
@@ -698,6 +698,7 @@ RSpec.describe Snippets::UpdateService do
it_behaves_like 'when snippet_actions param is present'
it_behaves_like 'only file_name is present'
it_behaves_like 'only content is present'
+ it_behaves_like 'invalid params error response'
it_behaves_like 'snippets spam check is performed' do
before do
subject
@@ -725,6 +726,7 @@ RSpec.describe Snippets::UpdateService do
it_behaves_like 'when snippet_actions param is present'
it_behaves_like 'only file_name is present'
it_behaves_like 'only content is present'
+ it_behaves_like 'invalid params error response'
it_behaves_like 'snippets spam check is performed' do
before do
subject
diff --git a/spec/support/shared_examples/services/snippets_shared_examples.rb b/spec/support/shared_examples/services/snippets_shared_examples.rb
index 51a4a8b1cd9..4a08c0d4365 100644
--- a/spec/support/shared_examples/services/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/services/snippets_shared_examples.rb
@@ -40,3 +40,20 @@ RSpec.shared_examples 'snippets spam check is performed' do
end
end
end
+
+shared_examples 'invalid params error response' do
+ before do
+ allow_next_instance_of(described_class) do |service|
+ allow(service).to receive(:valid_params?).and_return false
+ end
+ end
+
+ it 'responds to errors appropriately' do
+ response = subject
+
+ aggregate_failures do
+ expect(response).to be_error
+ expect(response.http_status).to eq 422
+ end
+ end
+end