diff options
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' |