summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/google_tag_manager/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js2
-rw-r--r--app/assets/javascripts/repository/queries/blob_info.query.graphql1
-rw-r--r--app/assets/javascripts/terms/components/app.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue2
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/highlight.scss44
-rw-r--r--app/assets/stylesheets/highlight/common.scss48
-rw-r--r--app/assets/stylesheets/highlight/themes/dark.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/monokai.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/none.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-dark.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-light.scss14
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss14
-rw-r--r--app/controllers/users/terms_controller.rb4
-rw-r--r--app/services/merge_requests/base_service.rb5
-rw-r--r--app/views/shared/_file_highlight.html.haml9
-rw-r--r--lib/gitlab/content_security_policy/config_loader.rb3
-rw-r--r--spec/frontend/google_tag_manager/index_spec.js11
-rw-r--r--spec/frontend/repository/mock_data.js1
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js3
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb4
-rw-r--r--spec/mailers/devise_mailer_spec.rb30
-rw-r--r--spec/services/merge_requests/update_service_spec.rb26
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb3
-rw-r--r--spec/views/projects/blob/_viewer.html.haml_spec.rb1
30 files changed, 308 insertions, 48 deletions
diff --git a/app/assets/javascripts/google_tag_manager/index.js b/app/assets/javascripts/google_tag_manager/index.js
index 2969121bf06..c8204f397ff 100644
--- a/app/assets/javascripts/google_tag_manager/index.js
+++ b/app/assets/javascripts/google_tag_manager/index.js
@@ -176,6 +176,14 @@ export const trackSaasTrialGetStarted = () => {
});
};
+export const trackTrialAcceptTerms = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ pushEvent('saasTrialAcceptTerms');
+};
+
export const trackCheckout = (selectedPlan, quantity) => {
if (!isSupported()) {
return;
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index 7db34816cfe..f37a2987685 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -11,7 +11,7 @@ export default () => {
// eslint-disable-next-line no-new
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
- '.diff-line-num[data-line-number], .diff-line-num[data-line-number] *',
+ '.file-line-num[data-line-number], .file-line-num[data-line-number] *',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index 8baee80e5d6..45a7793e559 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -27,6 +27,7 @@ query getBlobInfo(
fileType
language
path
+ blamePath
editBlobPath
gitpodBlobUrl
ideEditPath
diff --git a/app/assets/javascripts/terms/components/app.vue b/app/assets/javascripts/terms/components/app.vue
index aedf5b6acfe..a54a198faed 100644
--- a/app/assets/javascripts/terms/components/app.vue
+++ b/app/assets/javascripts/terms/components/app.vue
@@ -7,6 +7,7 @@ import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import csrf from '~/lib/utils/csrf';
import '~/behaviors/markdown/render_gfm';
+import { trackTrialAcceptTerms } from '~/google_tag_manager';
export default {
name: 'TermsApp',
@@ -73,6 +74,7 @@ export default {
this.setScrollableViewportHeight();
event.target.removeEventListener(FLASH_CLOSED_EVENT, this.handleFlashClose);
},
+ trackTrialAcceptTerms,
},
};
</script>
@@ -99,7 +101,13 @@ export default {
<gl-button type="submit">{{ $options.i18n.decline }}</gl-button>
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
</form>
- <form v-if="permissions.canAccept" class="gl-ml-3" method="post" :action="paths.accept">
+ <form
+ v-if="permissions.canAccept"
+ class="gl-ml-3"
+ method="post"
+ :action="paths.accept"
+ @submit="trackTrialAcceptTerms"
+ >
<gl-button
type="submit"
variant="confirm"
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
index 6babbca58c3..9683288f937 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
@@ -51,6 +51,10 @@ export default {
required: false,
default: null,
},
+ blamePath: {
+ type: String,
+ required: true,
+ },
},
computed: {
lines() {
@@ -76,6 +80,7 @@ export default {
:number="startingFrom + index + 1"
:content="line"
:language="language"
+ :blame-path="blamePath"
/>
</div>
<div v-else class="gl-display-flex">
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
index 7b62f0cdb7d..b6854ee0375 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlLink, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
import { setAttributes } from '~/lib/utils/dom_utils';
import { BIDI_CHARS, BIDI_CHARS_CLASS_LIST, BIDI_CHAR_TOOLTIP } from '../constants';
@@ -9,6 +9,7 @@ export default {
},
directives: {
SafeHtml: GlSafeHtmlDirective,
+ GlTooltip: GlTooltipDirective,
},
props: {
number: {
@@ -23,6 +24,10 @@ export default {
type: String,
required: true,
},
+ blamePath: {
+ type: String,
+ required: true,
+ },
},
computed: {
formattedContent() {
@@ -58,21 +63,35 @@ export default {
};
</script>
<template>
- <div class="gl-display-flex">
- <div class="gl-p-0! gl-absolute gl-z-index-3 gl-border-r diff-line-num line-numbers">
+ <div class="gl-display-flex line-links-wrapper">
+ <div
+ class="gl-p-0! gl-absolute gl-z-index-3 diff-line-num gl-border-r gl-display-flex line-links line-numbers"
+ :class="firstLineClass"
+ >
+ <gl-link
+ v-gl-tooltip="__('View blame')"
+ class="gl-user-select-none gl-ml-3 gl-shadow-none! file-line-blame"
+ :href="`${blamePath}#L${number}`"
+ data-track-action="click_link"
+ data-track-label="file_line_action"
+ data-track-property="blame"
+ />
+
<gl-link
:id="`L${number}`"
- class="gl-user-select-none gl-ml-5 gl-pr-3 gl-shadow-none! file-line-num diff-line-num"
- :class="firstLineClass"
+ class="gl-user-select-none gl-flex-grow-1 gl-justify-content-end gl-pr-3 gl-shadow-none! file-line-num"
:to="`#L${number}`"
:data-line-number="number"
+ data-track-action="click_link"
+ data-track-label="file_line_action"
+ data-track-property="link"
>
{{ number }}
</gl-link>
</div>
<pre
- class="gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11! gl-border-none! code highlight gl-line-height-normal"
+ class="gl-p-0! gl-w-full gl-overflow-visible! gl-border-none! code highlight gl-line-height-normal"
:class="firstLineClass"
><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
index b5c66365836..4ef37b1d059 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
@@ -199,6 +199,7 @@ export default {
:starting-from="firstChunk.startingFrom"
:is-highlighted="firstChunk.isHighlighted"
:language="firstChunk.language"
+ :blame-path="blob.blamePath"
/>
<gl-loading-icon v-if="isLoading" size="sm" class="gl-my-5" />
@@ -213,6 +214,7 @@ export default {
:is-highlighted="chunk.isHighlighted"
:chunk-index="index"
:language="chunk.language"
+ :blame-path="blob.blamePath"
@appear="highlightChunk"
/>
</div>
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index f322c6c8929..d59ca14ee84 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -202,6 +202,10 @@
float: none;
border-left: 1px solid $gray-100;
+ .file-line-num {
+ @include gl-min-w-9;
+ }
+
i {
float: none;
margin-right: 0;
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 1c43212f501..f1f43e55921 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -48,8 +48,9 @@
a {
font-family: $monospace-font;
- display: block;
white-space: nowrap;
+ @include gl-display-flex;
+ @include gl-justify-content-end;
i,
svg {
@@ -90,3 +91,44 @@ td.line-numbers {
cursor: pointer;
text-decoration: underline wavy $red-500;
}
+
+.blob-viewer {
+ .line-numbers {
+ // for server-side-rendering
+ .line-links {
+ min-width: 6.5rem;
+
+ &:first-child {
+ margin-top: 10px;
+ }
+
+ &:last-child {
+ margin-bottom: 10px;
+ }
+ }
+
+ // for client
+ &.line-links {
+ min-width: 6.5rem;
+ border-bottom-left-radius: 0;
+
+ + pre {
+ margin-left: 6.5rem;
+ }
+ }
+ }
+
+ .line-links {
+ &:hover .file-line-blame::before,
+ &:hover .file-line-num::before,
+ &:focus-within .file-line-blame::before,
+ &:focus-within .file-line-num::before {
+ @include gl-visibility-visible;
+ }
+ }
+
+ .file-line-num,
+ .file-line-blame {
+ @include gl-align-items-center;
+ }
+}
diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss
index fcbd05141b9..8e4f4600d5c 100644
--- a/app/assets/stylesheets/highlight/common.scss
+++ b/app/assets/stylesheets/highlight/common.scss
@@ -98,32 +98,50 @@
}
}
-@mixin line-number-link($color) {
- min-width: $gl-spacing-scale-9;
+@mixin line-link($color, $icon) {
&::before {
- @include gl-display-none;
+ @include gl-visibility-hidden;
@include gl-align-self-center;
- @include gl-mt-2;
- @include gl-mr-2;
- @include gl-w-4;
- @include gl-h-4;
- @include gl-absolute;
- @include gl-left-3;
- background-color: $color;
- mask-image: asset_url('icons-stacked.svg#link');
+ @include gl-mr-1;
+ @include gl-w-5;
+ @include gl-h-5;
+ background-color: rgba($color, 0.3);
+ mask-image: asset_url('icons-stacked.svg##{$icon}');
mask-repeat: no-repeat;
mask-size: cover;
mask-position: center;
content: '';
}
- &:hover::before {
- @include gl-display-inline-block;
+ &:hover {
+ &::before {
+ background-color: rgba($color, 0.6);
+ }
+ }
+}
+
+@mixin line-hover-bg($color: $white-normal) {
+ &:hover,
+ &:focus-within {
+ background-color: darken($color, 10);
}
+}
- &:focus::before {
- @include gl-display-inline-block;
+@mixin first-line-top-space($bg-color: $gray-light, $border-color: $white-normal) {
+ &:first-child {
+ .line-links {
+ &::before {
+ @include gl-absolute;
+ @include gl-h-3;
+ content: '';
+ bottom: 100%;
+ left: 0;
+ width: 6.5rem;
+ background-color: $bg-color;
+ border-right: 1px solid $border-color;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index 709e7f5ae18..f7f3c8964bf 100644
--- a/app/assets/stylesheets/highlight/themes/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -127,7 +127,19 @@ $dark-il: #de935f;
.code.dark {
// Line numbers
.file-line-num {
- @include line-number-link($dark-line-num-color);
+ @include line-link($white, 'link');
+ }
+
+ .file-line-blame {
+ @include line-link($white, 'git');
+ }
+
+ .line-links {
+ @include line-hover-bg($dark-main-bg);
+ }
+
+ .line-links-wrapper {
+ @include first-line-top-space($dark-main-bg, $dark-code-border);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 9ce6937b8ed..c19189ef31b 100644
--- a/app/assets/stylesheets/highlight/themes/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -119,7 +119,19 @@ $monokai-gh: #75715e;
// Line numbers
.file-line-num {
- @include line-number-link($monokai-line-num-color);
+ @include line-link($white, 'link');
+ }
+
+ .file-line-blame {
+ @include line-link($white, 'git');
+ }
+
+ .line-links {
+ @include line-hover-bg($monokai-bg);
+ }
+
+ .line-links-wrapper {
+ @include first-line-top-space($monokai-bg, $monokai-border);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index 868e466b1f8..7fd81353363 100644
--- a/app/assets/stylesheets/highlight/themes/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -25,7 +25,19 @@
// Line numbers
.file-line-num {
- @include line-number-link($black-transparent);
+ @include line-link($black, 'link');
+ }
+
+ .file-line-blame {
+ @include line-link($black, 'git');
+ }
+
+ .line-links {
+ @include line-hover-bg;
+ }
+
+ .line-links-wrapper {
+ @include first-line-top-space;
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index 70c9d2c2e0b..b11d49bd952 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -122,7 +122,19 @@ $solarized-dark-il: #2aa198;
// Line numbers
.file-line-num {
- @include line-number-link($solarized-dark-line-color);
+ @include line-link($white, 'link');
+ }
+
+ .file-line-blame {
+ @include line-link($white, 'git');
+ }
+
+ .line-links {
+ @include line-hover-bg($solarized-dark-pre-bg);
+ }
+
+ .line-links-wrapper {
+ @include first-line-top-space($solarized-dark-pre-bg, $solarized-dark-pre-border);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index fb77af139b3..9ecf6a59c2e 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -108,7 +108,19 @@ $solarized-light-il: #2aa198;
.code.solarized-light {
// Line numbers
.file-line-num {
- @include line-number-link($solarized-light-line-color);
+ @include line-link($black, 'link');
+ }
+
+ .file-line-blame {
+ @include line-link($black, 'git');
+ }
+
+ .line-links {
+ @include line-hover-bg($solarized-light-pre-bg);
+ }
+
+ .line-links-wrapper {
+ @include first-line-top-space($solarized-light-pre-bg, $solarized-light-border);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index 770a90bbc57..336c2b739f1 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -95,7 +95,15 @@ $white-gc-bg: #eaf2f5;
// Line numbers
.file-line-num {
- @include line-number-link($black-transparent);
+ @include line-link($black, 'link');
+}
+
+.file-line-blame {
+ @include line-link($black, 'git');
+}
+
+.line-links {
+ @include line-hover-bg;
}
.line-numbers,
@@ -126,6 +134,10 @@ pre.code,
border-color: $white-normal;
}
+.line-links-wrapper {
+ @include first-line-top-space;
+}
+
&,
pre.code,
.line_holder .line_content {
diff --git a/app/controllers/users/terms_controller.rb b/app/controllers/users/terms_controller.rb
index 3dfa717474e..f36b140f3a2 100644
--- a/app/controllers/users/terms_controller.rb
+++ b/app/controllers/users/terms_controller.rb
@@ -14,6 +14,10 @@ module Users
before_action :terms
+ before_action only: [:index] do
+ push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops)
+ end
+
layout 'terms'
feature_category :user_management
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index fcc3051c440..9bd38478796 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -128,13 +128,8 @@ module MergeRequests
if draft_event = params.delete(:wip_event)
# We update the title that is provided in the params or we use the mr title
title = params[:title] || merge_request.title
- # Supports both `wip` and `draft` permutations of draft_event
- # This support can be removed >= %15.2
- #
params[:title] = case draft_event
- when 'wip' then MergeRequest.draft_title(title)
when 'draft' then MergeRequest.draft_title(title)
- when 'unwip' then MergeRequest.draftless_title(title)
when 'ready' then MergeRequest.draftless_title(title)
end
end
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index f8ac3832a77..453c6438edf 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,13 +1,16 @@
#blob-content.file-content.code.js-syntax-highlight
- offset = defined?(first_line_number) ? first_line_number : 1
- .line-numbers
+ .line-numbers{ class: "gl-p-0\!" }
- if blob.data.present?
- link = blob_link if defined?(blob_link)
+ - blame_link = project_blame_path(@project, tree_join(@ref, blob.path))
- blob.data.each_line.each_with_index do |_, index|
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
- %a.file-line-num.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
- = i
+ .gl-display-flex.line-links.diff-line-num
+ %a.file-line-blame.gl-display-flex.has-tooltip.gl-ml-3{ href: "#{blame_link}#L#{i}", title: _('View blame'), data: { track_action: "click_link", track_label: "file_line_action", track_property: "blame" } }
+ %a.file-line-num.gl-display-flex.gl-justify-content-end.flex-grow-1.gl-pr-3{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i, data: { track_action: "click_link", track_label: "file_line_action", track_property: "link" } }
+ = i
- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
%pre.code.highlight
diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb
index f57050e5a1f..8648ffe5f49 100644
--- a/lib/gitlab/content_security_policy/config_loader.rb
+++ b/lib/gitlab/content_security_policy/config_loader.rb
@@ -138,9 +138,8 @@ module Gitlab
def self.allow_sentry(directives)
sentry_dsn = Gitlab.config.sentry.clientside_dsn
sentry_uri = URI(sentry_dsn)
- sentry_uri.user = nil
- append_to_directive(directives, 'connect_src', sentry_uri.to_s)
+ append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
end
def self.allow_letter_opener(directives)
diff --git a/spec/frontend/google_tag_manager/index_spec.js b/spec/frontend/google_tag_manager/index_spec.js
index 50811f43fc3..6a7eb1fd9f1 100644
--- a/spec/frontend/google_tag_manager/index_spec.js
+++ b/spec/frontend/google_tag_manager/index_spec.js
@@ -10,6 +10,7 @@ import {
trackSaasTrialGroup,
trackSaasTrialProject,
trackSaasTrialGetStarted,
+ trackTrialAcceptTerms,
trackCheckout,
trackTransaction,
trackAddToCartUsageTab,
@@ -255,6 +256,16 @@ describe('~/google_tag_manager/index', () => {
expect(logError).not.toHaveBeenCalled();
});
+ it('when trackTrialAcceptTerms is invoked', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackTrialAcceptTerms();
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(spy).toHaveBeenCalledWith({ event: 'saasTrialAcceptTerms' });
+ expect(logError).not.toHaveBeenCalled();
+ });
+
describe('when trackCheckout is invoked', () => {
it('with selectedPlan: 2c92a00d76f0d5060176f2fb0a5029ff', () => {
expect(spy).not.toHaveBeenCalled();
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index 0a5766a25f9..4db295fe0b7 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -8,6 +8,7 @@ export const simpleViewerMock = {
language: 'javascript',
path: 'some_file.js',
webPath: 'some_file.js',
+ blamePath: 'blame/file.js',
editBlobPath: 'some_file.js/edit',
gitpodBlobUrl: 'https://gitpod.io#path/to/blob.js',
ideEditPath: 'some_file.js/ide/edit',
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
index eb2eec92534..525f8983971 100644
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
@@ -11,6 +11,7 @@ const DEFAULT_PROPS = {
number: 2,
content: '// Line content',
language: 'javascript',
+ blamePath: 'blame/file.js',
};
describe('Chunk Line component', () => {
@@ -20,7 +21,7 @@ describe('Chunk Line component', () => {
wrapper = shallowMountExtended(ChunkLine, { propsData: { ...DEFAULT_PROPS, ...props } });
};
- const findLink = () => wrapper.findComponent(GlLink);
+ const findLinks = () => wrapper.findAllComponents(GlLink);
const findContent = () => wrapper.findByTestId('content');
const findWrappedBidiChars = () => wrapper.findAllByTestId('bidi-wrapper');
@@ -47,14 +48,22 @@ describe('Chunk Line component', () => {
});
});
+ it('renders a blame link', () => {
+ expect(findLinks().at(0).attributes()).toMatchObject({
+ href: `${DEFAULT_PROPS.blamePath}#L${DEFAULT_PROPS.number}`,
+ });
+
+ expect(findLinks().at(0).text()).toBe('');
+ });
+
it('renders a line number', () => {
- expect(findLink().attributes()).toMatchObject({
+ expect(findLinks().at(1).attributes()).toMatchObject({
'data-line-number': `${DEFAULT_PROPS.number}`,
to: `#L${DEFAULT_PROPS.number}`,
id: `L${DEFAULT_PROPS.number}`,
});
- expect(findLink().text()).toBe(DEFAULT_PROPS.number.toString());
+ expect(findLinks().at(1).text()).toBe(DEFAULT_PROPS.number.toString());
});
it('renders content', () => {
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
index 42c4f2eacb8..8dc3348acfa 100644
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
@@ -10,6 +10,7 @@ const DEFAULT_PROPS = {
startingFrom: 140,
totalLines: 50,
language: 'javascript',
+ blamePath: 'blame/file.js',
};
describe('Chunk component', () => {
@@ -76,6 +77,7 @@ describe('Chunk component', () => {
number: DEFAULT_PROPS.startingFrom + 1,
content: splitContent[0],
language: DEFAULT_PROPS.language,
+ blamePath: DEFAULT_PROPS.blamePath,
});
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index 9d389db4060..86b1506cf61 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -40,7 +40,8 @@ describe('Source Viewer component', () => {
const chunk2 = generateContent('// Some source code 2', 70);
const content = chunk1 + chunk2;
const path = 'some/path.js';
- const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path };
+ const blamePath = 'some/blame/path.js';
+ const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, blamePath };
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
const createComponent = async (blob = {}) => {
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
index d15c07ba137..616fe15c1a6 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -92,11 +92,11 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
context 'when sentry is configured' do
before do
stub_sentry_settings
- stub_config_setting(host: 'example.com')
+ stub_config_setting(host: 'gitlab.example.com')
end
it 'adds sentry path to CSP without user' do
- expect(directives['connect_src']).to eq("'self' ws://example.com dummy://example.com/43")
+ expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://example.com")
end
end
diff --git a/spec/mailers/devise_mailer_spec.rb b/spec/mailers/devise_mailer_spec.rb
index 2634d7c722b..360eb827927 100644
--- a/spec/mailers/devise_mailer_spec.rb
+++ b/spec/mailers/devise_mailer_spec.rb
@@ -126,4 +126,34 @@ RSpec.describe DeviseMailer do
is_expected.to have_link("Reset password", href: "#{Gitlab.config.gitlab.url}/users/password/edit?reset_password_token=faketoken")
end
end
+
+ describe '#email_changed' do
+ subject { described_class.email_changed(user, {}) }
+
+ let_it_be(:user) { create(:user) }
+
+ it_behaves_like 'an email sent from GitLab'
+
+ it 'is sent to the user' do
+ is_expected.to deliver_to user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject 'Email Changed'
+ end
+
+ it 'greets the user' do
+ is_expected.to have_body_text /Hello, #{user.name}!/
+ end
+
+ context "email contains updated id" do
+ before do
+ user.update!(email: "new_email@test.com")
+ end
+
+ it 'includes changed email id' do
+ is_expected.to have_body_text /email is being changed to new_email@test.com./
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 7164ba8fac0..212f75d853f 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -845,6 +845,8 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
context 'when the draft status is changed' do
+ let(:title) { 'New Title' }
+ let(:draft_title) { "Draft: #{title}" }
let!(:non_subscriber) { create(:user) }
let!(:subscriber) do
create(:user) { |u| merge_request.toggle_subscription(u, project) }
@@ -857,7 +859,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
context 'removing draft status' do
before do
- merge_request.update_attribute(:title, 'Draft: New Title')
+ merge_request.update_attribute(:title, draft_title)
end
it 'sends notifications for subscribers', :sidekiq_might_not_need_inline do
@@ -870,9 +872,22 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
should_email(subscriber)
should_not_email(non_subscriber)
end
+
+ context 'when removing through wip_event param' do
+ it 'removes Draft from the title' do
+ expect { update_merge_request({ wip_event: "ready" }) }
+ .to change { merge_request.title }
+ .from(draft_title)
+ .to(title)
+ end
+ end
end
context 'adding draft status' do
+ before do
+ merge_request.update_attribute(:title, title)
+ end
+
it 'does not send notifications', :sidekiq_might_not_need_inline do
opts = { title: 'Draft: New title' }
@@ -883,6 +898,15 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
should_not_email(subscriber)
should_not_email(non_subscriber)
end
+
+ context 'when adding through wip_event param' do
+ it 'adds Draft to the title' do
+ expect { update_merge_request({ wip_event: "draft" }) }
+ .to change { merge_request.title }
+ .from(title)
+ .to(draft_title)
+ end
+ end
end
end
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index 7396643823c..3bb05d0b6a6 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -12,6 +12,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let_it_be_with_refind(:private_project) { create(:project, :private, namespace: owner.namespace) }
let_it_be_with_refind(:internal_project) { create(:project, :internal, namespace: owner.namespace) }
let_it_be_with_refind(:public_project) { create(:project, :public, namespace: owner.namespace) }
+ let_it_be_with_refind(:public_project_in_group) { create(:project, :public, namespace: create(:group, :public)) }
let(:base_guest_permissions) do
%i[
@@ -93,7 +94,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let(:owner_permissions) { base_owner_permissions + additional_owner_permissions }
before_all do
- [private_project, internal_project, public_project].each do |project|
+ [private_project, internal_project, public_project, public_project_in_group].each do |project|
project.add_guest(guest)
project.add_reporter(reporter)
project.add_developer(developer)
diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb
index 893cfec1491..2761d10f9ad 100644
--- a/spec/views/projects/blob/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb
@@ -24,6 +24,7 @@ RSpec.describe 'projects/blob/_viewer.html.haml' do
before do
assign(:project, project)
assign(:blob, blob)
+ assign(:ref, 'master')
assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob'