Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c2367afbf5
commit
7671216b60
41 changed files with 869 additions and 154 deletions
78
app/assets/javascripts/blob/pipeline_tour_success_modal.vue
Normal file
78
app/assets/javascripts/blob/pipeline_tour_success_modal.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<script>
|
||||
import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { sprintf, s__, __ } from '~/locale';
|
||||
import Cookies from 'js-cookie';
|
||||
import { glEmojiTag } from '~/emoji';
|
||||
|
||||
export default {
|
||||
beginnerLink:
|
||||
'https://about.gitlab.com/blog/2018/01/22/a-beginners-guide-to-continuous-integration/',
|
||||
exampleLink: 'https://docs.gitlab.com/ee/ci/examples/',
|
||||
bodyMessage: s__(
|
||||
'MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!',
|
||||
),
|
||||
modalTitle: sprintf(
|
||||
__("That's it, well done!%{celebrate}"),
|
||||
{
|
||||
celebrate: glEmojiTag('tada'),
|
||||
},
|
||||
false,
|
||||
),
|
||||
components: {
|
||||
GlModal,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
goToPipelinesPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
commitCookie: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.disableModalFromRenderingAgain();
|
||||
},
|
||||
methods: {
|
||||
disableModalFromRenderingAgain() {
|
||||
Cookies.remove(this.commitCookie);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-modal
|
||||
visible
|
||||
size="sm"
|
||||
:title="$options.modalTitle"
|
||||
modal-id="success-pipeline-modal-id-not-used"
|
||||
>
|
||||
<p>
|
||||
{{ $options.bodyMessage }}
|
||||
</p>
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(`MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd}
|
||||
and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd}
|
||||
to see all the cool stuff you can do with it.`)
|
||||
"
|
||||
>
|
||||
<template #beginnerLink="{content}">
|
||||
<gl-link :href="$options.beginnerLink" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
<template #exampleLink="{content}">
|
||||
<gl-link :href="$options.exampleLink" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<template #modal-footer>
|
||||
<a :href="goToPipelinesPath" class="btn btn-success">{{ __('Go to Pipelines') }}</a>
|
||||
</template>
|
||||
</gl-modal>
|
||||
</template>
|
|
@ -4,6 +4,7 @@ import BlobViewer from '~/blob/viewer/index';
|
|||
import initBlob from '~/pages/projects/init_blob';
|
||||
import GpgBadges from '~/gpg_badges';
|
||||
import '~/sourcegraph/load';
|
||||
import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new BlobViewer(); // eslint-disable-line no-new
|
||||
|
@ -35,4 +36,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
// eslint-disable-next-line promise/catch-or-return
|
||||
import('~/code_navigation').then(m => m.default());
|
||||
}
|
||||
|
||||
if (gon.features?.suggestPipeline) {
|
||||
const successPipelineEl = document.querySelector('.js-success-pipeline-modal');
|
||||
|
||||
if (successPipelineEl) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: successPipelineEl,
|
||||
render(createElement) {
|
||||
const { commitCookie, pipelinesPath: goToPipelinesPath } = this.$el.dataset;
|
||||
|
||||
return createElement(PipelineTourSuccessModal, {
|
||||
props: {
|
||||
goToPipelinesPath,
|
||||
commitCookie,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,45 +1,75 @@
|
|||
<script>
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import MrWidgetIcon from './mr_widget_icon.vue';
|
||||
import PipelineTourState from './states/mr_widget_pipeline_tour.vue';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetSuggestPipeline',
|
||||
iconName: 'status_notfound',
|
||||
popoverTarget: 'suggest-popover',
|
||||
popoverContainer: 'suggest-pipeline',
|
||||
trackLabel: 'no_pipeline_noticed',
|
||||
linkTrackValue: 30,
|
||||
linkTrackEvent: 'click_link',
|
||||
components: {
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
MrWidgetIcon,
|
||||
PipelineTourState,
|
||||
},
|
||||
props: {
|
||||
pipelinePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
pipelineSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
humanAccess: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="d-flex mr-pipeline-suggest append-bottom-default">
|
||||
<div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest append-bottom-default">
|
||||
<mr-widget-icon :name="$options.iconName" />
|
||||
<gl-sprintf
|
||||
class="js-no-pipeline-message"
|
||||
:message="
|
||||
s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd}
|
||||
<div :id="$options.popoverTarget">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd}
|
||||
%{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd}
|
||||
to create one.`)
|
||||
"
|
||||
>
|
||||
<template #prefixToLink="{content}">
|
||||
<strong>
|
||||
{{ content }}
|
||||
</strong>
|
||||
</template>
|
||||
<template #addPipelineLink="{content}">
|
||||
<gl-link :href="pipelinePath" class="ml-2">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
"
|
||||
>
|
||||
<template #prefixToLink="{content}">
|
||||
<strong>
|
||||
{{ content }}
|
||||
</strong>
|
||||
</template>
|
||||
<template #addPipelineLink="{content}">
|
||||
<gl-link
|
||||
:href="pipelinePath"
|
||||
class="ml-2 js-add-pipeline-path"
|
||||
:data-track-property="humanAccess"
|
||||
:data-track-value="$options.linkTrackValue"
|
||||
:data-track-event="$options.linkTrackEvent"
|
||||
:data-track-label="$options.trackLabel"
|
||||
>
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<pipeline-tour-state
|
||||
:pipeline-path="pipelinePath"
|
||||
:pipeline-svg-path="pipelineSvgPath"
|
||||
:human-access="humanAccess"
|
||||
:popover-target="$options.popoverTarget"
|
||||
:popover-container="$options.popoverContainer"
|
||||
:track-label="$options.trackLabel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<script>
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { GlPopover, GlButton } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import Cookies from 'js-cookie';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
const trackingMixin = Tracking.mixin();
|
||||
|
||||
const cookieKey = 'suggest_pipeline_dismissed';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetPipelineTour',
|
||||
dismissTrackValue: 20,
|
||||
showTrackValue: 10,
|
||||
trackEvent: 'click_button',
|
||||
popoverContent: sprintf(
|
||||
'%{messageText1}%{lineBreak}%{messageText2}%{lineBreak}%{messageText3}%{lineBreak}%{messageText4}%{lineBreak}%{messageText5}',
|
||||
{
|
||||
messageText1: s__('mrWidget|Detect issues before deployment with a CI pipeline'),
|
||||
messageText2: s__('mrWidget|that continuously tests your code. We created'),
|
||||
messageText3: s__("mrWidget|a quick guide that'll show you how to create"),
|
||||
messageText4: s__('mrWidget|one. Make your code more secure and more'),
|
||||
messageText5: s__('mrWidget|robust in just a minute.'),
|
||||
lineBreak: '<br/>',
|
||||
},
|
||||
false,
|
||||
),
|
||||
components: {
|
||||
GlPopover,
|
||||
GlButton,
|
||||
Icon,
|
||||
},
|
||||
mixins: [trackingMixin],
|
||||
props: {
|
||||
pipelinePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
pipelineSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
humanAccess: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
popoverTarget: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
popoverContainer: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
trackLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popoverDismissed: parseBoolean(Cookies.get(cookieKey)),
|
||||
tracking: {
|
||||
label: this.trackLabel,
|
||||
property: this.humanAccess,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.trackOnShow();
|
||||
},
|
||||
methods: {
|
||||
trackOnShow() {
|
||||
if (!this.popoverDismissed) {
|
||||
this.track();
|
||||
}
|
||||
},
|
||||
dismissPopover() {
|
||||
this.popoverDismissed = true;
|
||||
Cookies.set(cookieKey, this.popoverDismissed, { expires: 365 });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-popover
|
||||
v-if="!popoverDismissed"
|
||||
show
|
||||
:target="popoverTarget"
|
||||
:container="popoverContainer"
|
||||
placement="rightbottom"
|
||||
>
|
||||
<template #title>
|
||||
<button
|
||||
class="btn-blank float-right mt-1"
|
||||
type="button"
|
||||
:aria-label="__('Close')"
|
||||
:data-track-property="humanAccess"
|
||||
:data-track-value="$options.dismissTrackValue"
|
||||
:data-track-event="$options.trackEvent"
|
||||
:data-track-label="trackLabel"
|
||||
@click="dismissPopover"
|
||||
>
|
||||
<icon name="close" aria-hidden="true" />
|
||||
</button>
|
||||
{{ s__('mrWidget|Are you adding technical debt or code vulnerabilities?') }}
|
||||
</template>
|
||||
<div class="svg-content svg-150 pt-1">
|
||||
<img :src="pipelineSvgPath" />
|
||||
</div>
|
||||
<p v-html="$options.popoverContent"></p>
|
||||
<gl-button
|
||||
ref="ok"
|
||||
category="primary"
|
||||
class="mt-2 mb-0"
|
||||
variant="info"
|
||||
block
|
||||
:href="pipelinePath"
|
||||
:data-track-property="humanAccess"
|
||||
:data-track-value="$options.showTrackValue"
|
||||
:data-track-event="$options.trackEvent"
|
||||
:data-track-label="trackLabel"
|
||||
>
|
||||
{{ __('Show me how') }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
ref="no-thanks"
|
||||
category="secondary"
|
||||
class="mt-2 mb-0"
|
||||
variant="info"
|
||||
block
|
||||
:data-track-property="humanAccess"
|
||||
:data-track-value="$options.dismissTrackValue"
|
||||
:data-track-event="$options.trackEvent"
|
||||
:data-track-label="trackLabel"
|
||||
@click="dismissPopover"
|
||||
>
|
||||
{{ __("No thanks, don't show this again") }}
|
||||
</gl-button>
|
||||
</gl-popover>
|
||||
</template>
|
|
@ -362,6 +362,8 @@ export default {
|
|||
v-if="shouldSuggestPipelines"
|
||||
class="mr-widget-workflow"
|
||||
:pipeline-path="mr.mergeRequestAddCiConfigPath"
|
||||
:pipeline-svg-path="mr.pipelinesEmptySvgPath"
|
||||
:human-access="mr.humanAccess.toLowerCase()"
|
||||
/>
|
||||
<mr-widget-pipeline-container
|
||||
v-if="shouldRenderPipelines"
|
||||
|
|
|
@ -176,6 +176,7 @@ export default class MergeRequestStore {
|
|||
this.eligibleApproversDocsPath = data.eligible_approvers_docs_path;
|
||||
this.mergeImmediatelyDocsPath = data.merge_immediately_docs_path;
|
||||
this.mergeRequestAddCiConfigPath = data.merge_request_add_ci_config_path;
|
||||
this.pipelinesEmptySvgPath = data.pipelines_empty_svg_path;
|
||||
this.humanAccess = data.human_access;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
$image-widths: 80 130 250 306 394 430;
|
||||
$image-widths: 80 130 150 250 306 394 430;
|
||||
@each $width in $image-widths {
|
||||
&.svg-#{$width} {
|
||||
img,
|
||||
|
|
|
@ -614,6 +614,10 @@ $mr-widget-min-height: 69px;
|
|||
.circle-icon-container {
|
||||
color: $gl-text-color-quaternary;
|
||||
}
|
||||
|
||||
.popover {
|
||||
z-index: 240;
|
||||
}
|
||||
}
|
||||
|
||||
.card-new-merge-request {
|
||||
|
|
|
@ -7,7 +7,7 @@ module Groups
|
|||
before_action :authorize_admin_group!
|
||||
before_action :authorize_update_max_artifacts_size!, only: [:update]
|
||||
before_action do
|
||||
push_frontend_feature_flag(:new_variables_ui, @group)
|
||||
push_frontend_feature_flag(:new_variables_ui, @group, default_enabled: true)
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
|
||||
before_action only: :show do
|
||||
push_frontend_feature_flag(:code_navigation, @project)
|
||||
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
|
@ -6,7 +6,7 @@ module Projects
|
|||
before_action :authorize_admin_pipeline!
|
||||
before_action :define_variables
|
||||
before_action do
|
||||
push_frontend_feature_flag(:new_variables_ui, @project)
|
||||
push_frontend_feature_flag(:new_variables_ui, @project, default_enabled: true)
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
@ -341,4 +341,16 @@ module BlobHelper
|
|||
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
|
||||
end
|
||||
end
|
||||
|
||||
def show_suggest_pipeline_creation_celebration?
|
||||
experiment_enabled?(:suggest_pipeline) &&
|
||||
@blob.auxiliary_viewer.valid?(project: @project, sha: @commit.sha, user: current_user) &&
|
||||
@blob.path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] &&
|
||||
@project.uses_default_ci_config? &&
|
||||
cookies[suggest_pipeline_commit_cookie_name].present?
|
||||
end
|
||||
|
||||
def suggest_pipeline_commit_cookie_name
|
||||
"suggest_gitlab_ci_yml_commit_#{@project.id}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,8 @@ class MergeRequestWidgetEntity < Grape::Entity
|
|||
merge_request.source_project,
|
||||
merge_request.source_branch,
|
||||
file_name: '.gitlab-ci.yml',
|
||||
commit_message: s_("CommitMessage|Add %{file_name}") % { file_name: Gitlab::FileDetector::PATTERNS[:gitlab_ci] }
|
||||
commit_message: s_("CommitMessage|Add %{file_name}") % { file_name: Gitlab::FileDetector::PATTERNS[:gitlab_ci] },
|
||||
suggest_gitlab_ci_yml: true
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
class ImportExportUploader < AttachmentUploader
|
||||
EXTENSION_WHITELIST = %w[tar.gz gz].freeze
|
||||
|
||||
def self.workhorse_local_upload_path
|
||||
File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
|
||||
end
|
||||
|
||||
def extension_whitelist
|
||||
EXTENSION_WHITELIST
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protected-variables') }
|
||||
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
|
||||
- if Feature.enabled?(:new_variables_ui, @project || @group)
|
||||
- if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true)
|
||||
- is_group = !@group.nil?
|
||||
|
||||
#js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex} }
|
||||
|
|
1
app/views/projects/blob/_pipeline_tour_success.html.haml
Normal file
1
app/views/projects/blob/_pipeline_tour_success.html.haml
Normal file
|
@ -0,0 +1 @@
|
|||
.js-success-pipeline-modal{ 'data-commit-cookie': suggest_pipeline_commit_cookie_name, 'data-pipelines-path': project_pipelines_path(@project) }
|
|
@ -14,3 +14,5 @@
|
|||
|
||||
- title = "Replace #{@blob.name}"
|
||||
= render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
|
||||
|
||||
= render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration?
|
||||
|
|
|
@ -10,5 +10,6 @@
|
|||
window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/reviewing_and_managing_merge_requests.md', anchor: 'troubleshooting')}';
|
||||
window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.html', anchor: 'security-approvals-in-merge-requests-ultimate')}';
|
||||
window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/merge_request_approvals', anchor: 'eligible-approvers')}';
|
||||
window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}';
|
||||
|
||||
#js-vue-mr-widget.mr-widget
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow access to /version API endpoint with read_user scope
|
||||
merge_request: 25211
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/ci-variables-ui-turn-on-ff.yml
Normal file
5
changelogs/unreleased/ci-variables-ui-turn-on-ff.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Set new_variables_ui feature flag default value to true
|
||||
merge_request: 25731
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Group Import API file upload when object storage is disabled
|
||||
merge_request: 25715
|
||||
author:
|
||||
type: fixed
|
|
@ -1,19 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Gitlab.ee do
|
||||
begin
|
||||
public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
|
||||
public_key = OpenSSL::PKey::RSA.new(public_key_file)
|
||||
Gitlab::License.encryption_key = public_key
|
||||
rescue
|
||||
warn "WARNING: No valid license encryption key provided."
|
||||
end
|
||||
|
||||
# Needed to run migration
|
||||
if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
|
||||
message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
|
||||
if ::License.block_changes? && message.present?
|
||||
warn "WARNING: #{message}"
|
||||
end
|
||||
end
|
||||
public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
|
||||
public_key = OpenSSL::PKey::RSA.new(public_key_file)
|
||||
Gitlab::License.encryption_key = public_key
|
||||
rescue
|
||||
warn "WARNING: No valid license encryption key provided."
|
||||
end
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
module API
|
||||
class Version < Grape::API
|
||||
helpers ::API::Helpers::GraphqlHelpers
|
||||
include APIGuard
|
||||
|
||||
allow_access_with_scope :read_user, if: -> (request) { request.get? }
|
||||
|
||||
before { authenticate! }
|
||||
|
||||
|
|
|
@ -9448,6 +9448,9 @@ msgstr ""
|
|||
msgid "Go to %{link_to_google_takeout}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to Pipelines"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to Webhooks"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11687,6 +11690,12 @@ msgstr ""
|
|||
msgid "MERGED"
|
||||
msgstr ""
|
||||
|
||||
msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to see all the cool stuff you can do with it."
|
||||
msgstr ""
|
||||
|
||||
msgid "MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!"
|
||||
msgstr ""
|
||||
|
||||
msgid "MRApprovals|Approved by"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12960,6 +12969,9 @@ msgstr ""
|
|||
msgid "No template"
|
||||
msgstr ""
|
||||
|
||||
msgid "No thanks, don't show this again"
|
||||
msgstr ""
|
||||
|
||||
msgid "No value set by top-level parent group."
|
||||
msgstr ""
|
||||
|
||||
|
@ -17650,6 +17662,9 @@ msgstr ""
|
|||
msgid "Show latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show me how"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show only direct members"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19023,6 +19038,9 @@ msgstr ""
|
|||
msgid "Thanks! Don't show me this again"
|
||||
msgstr ""
|
||||
|
||||
msgid "That's it, well done!%{celebrate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23346,6 +23364,9 @@ msgstr ""
|
|||
msgid "mrWidget|Approved by"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Cancel automatic merge"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23379,6 +23400,9 @@ msgstr ""
|
|||
msgid "mrWidget|Deployment statistics are not available currently"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Detect issues before deployment with a CI pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Did not close"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23556,6 +23580,9 @@ msgstr ""
|
|||
msgid "mrWidget|Your password"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|a quick guide that'll show you how to create"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|branch does not exist."
|
||||
msgstr ""
|
||||
|
||||
|
@ -23565,6 +23592,15 @@ msgstr ""
|
|||
msgid "mrWidget|into"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|one. Make your code more secure and more"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|robust in just a minute."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|that continuously tests your code. We created"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
|
||||
msgstr ""
|
||||
|
||||
|
|
40
spec/frontend/blob/pipeline_tour_success_spec.js
Normal file
40
spec/frontend/blob/pipeline_tour_success_spec.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import pipelineTourSuccess from '~/blob/pipeline_tour_success_modal.vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Cookies from 'js-cookie';
|
||||
import { GlSprintf, GlModal } from '@gitlab/ui';
|
||||
|
||||
describe('PipelineTourSuccessModal', () => {
|
||||
let wrapper;
|
||||
let cookieSpy;
|
||||
const goToPipelinesPath = 'some_pipeline_path';
|
||||
const commitCookie = 'some_cookie';
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(pipelineTourSuccess, {
|
||||
propsData: {
|
||||
goToPipelinesPath,
|
||||
commitCookie,
|
||||
},
|
||||
});
|
||||
|
||||
cookieSpy = jest.spyOn(Cookies, 'remove');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('has expected structure', () => {
|
||||
const modal = wrapper.find(GlModal);
|
||||
const sprintf = modal.find(GlSprintf);
|
||||
|
||||
expect(modal.attributes('title')).toContain("That's it, well done!");
|
||||
expect(sprintf.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('calls to remove cookie', () => {
|
||||
wrapper.vm.disableModalFromRenderingAgain();
|
||||
|
||||
expect(cookieSpy).toHaveBeenCalledWith(commitCookie);
|
||||
});
|
||||
});
|
|
@ -8,7 +8,7 @@ let handlers;
|
|||
export function mockTracking(category = '_category_', documentOverride, spyMethod) {
|
||||
document = documentOverride || window.document;
|
||||
window.snowplow = () => {};
|
||||
Tracking.bindDocument(category, document);
|
||||
handlers = Tracking.bindDocument(category, document);
|
||||
return spyMethod ? spyMethod(Tracking, 'event') : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -226,6 +226,14 @@ describe('Tracking', () => {
|
|||
};
|
||||
});
|
||||
|
||||
it('calls the event method with no category or action defined', () => {
|
||||
mixin.trackingCategory = mixin.trackingCategory();
|
||||
mixin.trackingOptions = mixin.trackingOptions();
|
||||
|
||||
mixin.track();
|
||||
expect(eventSpy).toHaveBeenCalledWith(undefined, undefined, {});
|
||||
});
|
||||
|
||||
it('calls the event method', () => {
|
||||
mixin.trackingCategory = mixin.trackingCategory();
|
||||
mixin.trackingOptions = mixin.trackingOptions();
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
|
||||
import stubChildren from 'helpers/stub_children';
|
||||
import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
|
||||
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
|
||||
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
|
||||
|
||||
describe('MRWidgetHeader', () => {
|
||||
let wrapper;
|
||||
const pipelinePath = '/foo/bar/add/pipeline/path';
|
||||
const pipelineSvgPath = '/foo/bar/pipeline/svg/path';
|
||||
const humanAccess = 'maintainer';
|
||||
const iconName = 'status_notfound';
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(suggestPipelineComponent, {
|
||||
propsData: { pipelinePath },
|
||||
propsData: { pipelinePath, pipelineSvgPath, humanAccess },
|
||||
stubs: {
|
||||
...stubChildren(PipelineTourState),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -22,30 +30,47 @@ describe('MRWidgetHeader', () => {
|
|||
it('renders add pipeline file link', () => {
|
||||
const link = wrapper.find(GlLink);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(link.exists()).toBe(true);
|
||||
expect(link.attributes().href).toBe(pipelinePath);
|
||||
});
|
||||
expect(link.exists()).toBe(true);
|
||||
expect(link.attributes().href).toBe(pipelinePath);
|
||||
});
|
||||
|
||||
it('renders the expected text', () => {
|
||||
const messageText = /\s*No pipeline\s*Add the .gitlab-ci.yml file\s*to create one./;
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.text()).toMatch(messageText);
|
||||
});
|
||||
expect(wrapper.text()).toMatch(messageText);
|
||||
});
|
||||
|
||||
it('renders widget icon', () => {
|
||||
const icon = wrapper.find(MrWidgetIcon);
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(icon.exists()).toBe(true);
|
||||
expect(icon.props()).toEqual(
|
||||
expect.objectContaining({
|
||||
name: iconName,
|
||||
}),
|
||||
);
|
||||
expect(icon.exists()).toBe(true);
|
||||
expect(icon.props()).toEqual(
|
||||
expect.objectContaining({
|
||||
name: iconName,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('tracking', () => {
|
||||
let spy;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('send an event when ok button is clicked', () => {
|
||||
const link = wrapper.find(GlLink);
|
||||
triggerEvent(link.element);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('_category_', 'click_link', {
|
||||
label: 'no_pipeline_noticed',
|
||||
property: humanAccess,
|
||||
value: '30',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlPopover } from '@gitlab/ui';
|
||||
import Cookies from 'js-cookie';
|
||||
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
|
||||
import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
|
||||
import { popoverProps, cookieKey } from './pipeline_tour_mock_data';
|
||||
|
||||
describe('MRWidgetPipelineTour', () => {
|
||||
let wrapper;
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
describe(`when ${cookieKey} cookie is set`, () => {
|
||||
beforeEach(() => {
|
||||
Cookies.set(cookieKey, true);
|
||||
wrapper = shallowMount(pipelineTourState, {
|
||||
propsData: popoverProps,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render the popover', () => {
|
||||
const popover = wrapper.find(GlPopover);
|
||||
|
||||
expect(popover.exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('tracking', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
it('does not call tracking', () => {
|
||||
expect(trackingSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when ${cookieKey} cookie is not set`, () => {
|
||||
const findOkBtn = () => wrapper.find({ ref: 'ok' });
|
||||
const findDismissBtn = () => wrapper.find({ ref: 'no-thanks' });
|
||||
|
||||
beforeEach(() => {
|
||||
Cookies.remove(cookieKey);
|
||||
wrapper = shallowMount(pipelineTourState, {
|
||||
propsData: popoverProps,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the popover', () => {
|
||||
const popover = wrapper.find(GlPopover);
|
||||
|
||||
expect(popover.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the show me how button', () => {
|
||||
const button = findOkBtn();
|
||||
|
||||
expect(button.exists()).toBe(true);
|
||||
expect(button.attributes().category).toBe('primary');
|
||||
});
|
||||
|
||||
it('renders the dismiss button', () => {
|
||||
const button = findDismissBtn();
|
||||
|
||||
expect(button.exists()).toBe(true);
|
||||
expect(button.attributes().category).toBe('secondary');
|
||||
});
|
||||
|
||||
it('renders the empty pipelines image', () => {
|
||||
const image = wrapper.find('img');
|
||||
|
||||
expect(image.exists()).toBe(true);
|
||||
expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
|
||||
});
|
||||
|
||||
describe('tracking', () => {
|
||||
let trackingSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('send event for basic view of popover', () => {
|
||||
document.body.dataset.page = 'projects:merge_requests:show';
|
||||
|
||||
wrapper.vm.trackOnShow();
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
|
||||
label: popoverProps.trackLabel,
|
||||
property: popoverProps.humanAccess,
|
||||
});
|
||||
});
|
||||
|
||||
it('send an event when ok button is clicked', () => {
|
||||
const okBtn = findOkBtn();
|
||||
triggerEvent(okBtn.element);
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
|
||||
label: popoverProps.trackLabel,
|
||||
property: popoverProps.humanAccess,
|
||||
value: '10',
|
||||
});
|
||||
});
|
||||
|
||||
it('send an event when dismiss button is clicked', () => {
|
||||
const dismissBtn = findDismissBtn();
|
||||
triggerEvent(dismissBtn.element);
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
|
||||
label: popoverProps.trackLabel,
|
||||
property: popoverProps.humanAccess,
|
||||
value: '20',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dismissPopover', () => {
|
||||
it('updates popoverDismissed', () => {
|
||||
const button = findDismissBtn();
|
||||
const popover = wrapper.find(GlPopover);
|
||||
button.vm.$emit('click');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(Cookies.get(cookieKey)).toBe('true');
|
||||
expect(popover.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
export const popoverProps = {
|
||||
pipelinePath: '/foo/bar/add/pipeline/path',
|
||||
pipelineSvgPath: 'assets/illustrations/something.svg',
|
||||
humanAccess: 'maintainer',
|
||||
popoverTarget: 'suggest-popover',
|
||||
popoverContainer: 'suggest-pipeline',
|
||||
trackLabel: 'some_tracking_label',
|
||||
};
|
||||
|
||||
export const cookieKey = 'suggest_pipeline_dismissed';
|
90
spec/frontend/vue_shared/components/file_row_spec.js
Normal file
90
spec/frontend/vue_shared/components/file_row_spec.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { file } from 'jest/ide/helpers';
|
||||
import FileRow from '~/vue_shared/components/file_row.vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
describe('File row component', () => {
|
||||
let wrapper;
|
||||
|
||||
function createComponent(propsData) {
|
||||
wrapper = mount(FileRow, {
|
||||
propsData,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders name', () => {
|
||||
const fileName = 't4';
|
||||
createComponent({
|
||||
file: file(fileName),
|
||||
level: 0,
|
||||
});
|
||||
|
||||
const name = wrapper.find('.file-row-name');
|
||||
|
||||
expect(name.text().trim()).toEqual(fileName);
|
||||
});
|
||||
|
||||
it('emits toggleTreeOpen on click', () => {
|
||||
const fileName = 't3';
|
||||
createComponent({
|
||||
file: {
|
||||
...file(fileName),
|
||||
type: 'tree',
|
||||
},
|
||||
level: 0,
|
||||
});
|
||||
jest.spyOn(wrapper.vm, '$emit');
|
||||
|
||||
wrapper.element.click();
|
||||
|
||||
expect(wrapper.vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', fileName);
|
||||
});
|
||||
|
||||
it('calls scrollIntoView if made active', () => {
|
||||
createComponent({
|
||||
file: {
|
||||
...file(),
|
||||
type: 'blob',
|
||||
active: false,
|
||||
},
|
||||
level: 0,
|
||||
});
|
||||
|
||||
jest.spyOn(wrapper.vm, 'scrollIntoView');
|
||||
|
||||
wrapper.setProps({
|
||||
file: Object.assign({}, wrapper.props('file'), {
|
||||
active: true,
|
||||
}),
|
||||
});
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.vm.scrollIntoView).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('indents row based on level', () => {
|
||||
createComponent({
|
||||
file: file('t4'),
|
||||
level: 2,
|
||||
});
|
||||
|
||||
expect(wrapper.find('.file-row-name').element.style.marginLeft).toBe('32px');
|
||||
});
|
||||
|
||||
it('renders header for file', () => {
|
||||
createComponent({
|
||||
file: {
|
||||
isHeader: true,
|
||||
path: 'app/assets',
|
||||
tree: [],
|
||||
},
|
||||
level: 0,
|
||||
});
|
||||
|
||||
expect(wrapper.element.classList).toContain('js-file-row-header');
|
||||
});
|
||||
});
|
|
@ -27,7 +27,7 @@ describe BlobHelper do
|
|||
end
|
||||
|
||||
describe "#edit_blob_link" do
|
||||
let(:namespace) { create(:namespace, name: 'gitlab' )}
|
||||
let(:namespace) { create(:namespace, name: 'gitlab') }
|
||||
let(:project) { create(:project, :repository, namespace: namespace) }
|
||||
|
||||
before do
|
||||
|
@ -202,6 +202,90 @@ describe BlobHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_suggest_pipeline_creation_celebration?' do
|
||||
let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci]) }
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
before do
|
||||
assign(:project, project)
|
||||
assign(:blob, blob)
|
||||
assign(:commit, double('Commit', sha: 'whatever'))
|
||||
helper.request.cookies["suggest_gitlab_ci_yml_commit_#{project.id}"] = 'true'
|
||||
allow(blob).to receive(:auxiliary_viewer).and_return(double('viewer', valid?: true))
|
||||
allow(helper).to receive(:current_user).and_return(current_user)
|
||||
end
|
||||
|
||||
context 'experiment enabled' do
|
||||
before do
|
||||
allow(helper).to receive(:experiment_enabled?).and_return(true)
|
||||
end
|
||||
|
||||
it 'is true' do
|
||||
expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
|
||||
end
|
||||
|
||||
context 'file is invalid format' do
|
||||
before do
|
||||
allow(blob).to receive(:auxiliary_viewer).and_return(double('viewer', valid?: false))
|
||||
end
|
||||
|
||||
it 'is false' do
|
||||
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'path is not a ci file' do
|
||||
before do
|
||||
allow(blob).to receive(:path).and_return('something_bad')
|
||||
end
|
||||
|
||||
it 'is false' do
|
||||
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'does not use the default ci config' do
|
||||
before do
|
||||
project.ci_config_path = 'something_bad'
|
||||
end
|
||||
|
||||
it 'is false' do
|
||||
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'does not have the needed cookie' do
|
||||
before do
|
||||
helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
|
||||
end
|
||||
|
||||
it 'is false' do
|
||||
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'experiment disabled' do
|
||||
before do
|
||||
allow(helper).to receive(:experiment_enabled?).and_return(false)
|
||||
end
|
||||
|
||||
it 'is false' do
|
||||
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'suggest_pipeline_commit_cookie_name' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'uses project id to make up the cookie name' do
|
||||
assign(:project, project)
|
||||
|
||||
expect(helper.suggest_pipeline_commit_cookie_name).to eq "suggest_gitlab_ci_yml_commit_#{project.id}"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ide_edit_path' do
|
||||
|
|
|
@ -8,7 +8,7 @@ let handlers;
|
|||
export function mockTracking(category = '_category_', documentOverride, spyMethod) {
|
||||
document = documentOverride || window.document;
|
||||
window.snowplow = () => {};
|
||||
Tracking.bindDocument(category, document);
|
||||
handlers = Tracking.bindDocument(category, document);
|
||||
return spyMethod ? spyMethod(Tracking, 'event') : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ export default {
|
|||
},
|
||||
merge_status: 'can_be_merged',
|
||||
merge_user_id: null,
|
||||
pipelines_empty_svg_path: '/path/to/svg',
|
||||
source_branch: 'daaaa',
|
||||
source_branch_link: 'daaaa',
|
||||
source_project_id: 19,
|
||||
|
|
|
@ -96,5 +96,11 @@ describe('MergeRequestStore', () => {
|
|||
|
||||
expect(store.humanAccess).toEqual('Maintainer');
|
||||
});
|
||||
|
||||
it('should set pipelinesEmptySvgPath', () => {
|
||||
store.setData({ ...mockData });
|
||||
|
||||
expect(store.pipelinesEmptySvgPath).toBe('/path/to/svg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import { file } from 'spec/ide/helpers';
|
||||
import FileRow from '~/vue_shared/components/file_row.vue';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('File row component', () => {
|
||||
let vm;
|
||||
|
||||
function createComponent(propsData) {
|
||||
const FileRowComponent = Vue.extend(FileRow);
|
||||
|
||||
vm = mountComponent(FileRowComponent, propsData);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('renders name', () => {
|
||||
createComponent({
|
||||
file: file('t4'),
|
||||
level: 0,
|
||||
});
|
||||
|
||||
const name = vm.$el.querySelector('.file-row-name');
|
||||
|
||||
expect(name.textContent.trim()).toEqual(vm.file.name);
|
||||
});
|
||||
|
||||
it('emits toggleTreeOpen on click', () => {
|
||||
createComponent({
|
||||
file: {
|
||||
...file('t3'),
|
||||
type: 'tree',
|
||||
},
|
||||
level: 0,
|
||||
});
|
||||
spyOn(vm, '$emit').and.stub();
|
||||
|
||||
vm.$el.click();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
|
||||
});
|
||||
|
||||
it('calls scrollIntoView if made active', done => {
|
||||
createComponent({
|
||||
file: {
|
||||
...file(),
|
||||
type: 'blob',
|
||||
active: false,
|
||||
},
|
||||
level: 0,
|
||||
});
|
||||
|
||||
spyOn(vm, 'scrollIntoView').and.stub();
|
||||
|
||||
vm.file.active = true;
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(vm.scrollIntoView).toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('indents row based on level', () => {
|
||||
createComponent({
|
||||
file: file('t4'),
|
||||
level: 2,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
|
||||
});
|
||||
|
||||
it('renders header for file', () => {
|
||||
createComponent({
|
||||
file: {
|
||||
isHeader: true,
|
||||
path: 'app/assets',
|
||||
tree: [],
|
||||
},
|
||||
level: 0,
|
||||
});
|
||||
|
||||
expect(vm.$el.classList).toContain('js-file-row-header');
|
||||
});
|
||||
});
|
|
@ -19,12 +19,14 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
|
|||
context 'with a metrics charts placeholder' do
|
||||
let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) }
|
||||
|
||||
it_behaves_like 'a supported metrics dashboard url'
|
||||
it_behaves_like 'redacts the embed placeholder'
|
||||
it_behaves_like 'retains the embed placeholder when applicable'
|
||||
|
||||
context 'for a grafana dashboard' do
|
||||
let(:url) { urls.project_grafana_api_metrics_dashboard_url(project, embedded: true) }
|
||||
|
||||
it_behaves_like 'a supported metrics dashboard url'
|
||||
it_behaves_like 'redacts the embed placeholder'
|
||||
it_behaves_like 'retains the embed placeholder when applicable'
|
||||
end
|
||||
|
||||
context 'the user has requisite permissions' do
|
||||
|
|
|
@ -12,17 +12,55 @@ describe API::Version do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
context 'when authenticated as user' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'returns the version information' do
|
||||
get api('/version', user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response['version']).to eq(Gitlab::VERSION)
|
||||
expect(json_response['revision']).to eq(Gitlab.revision)
|
||||
expect_version
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated with token' do
|
||||
let(:personal_access_token) { create(:personal_access_token, scopes: scopes) }
|
||||
|
||||
context 'with api scope' do
|
||||
let(:scopes) { %i(api) }
|
||||
|
||||
it 'returns the version information' do
|
||||
get api('/version', personal_access_token: personal_access_token)
|
||||
|
||||
expect_version
|
||||
end
|
||||
end
|
||||
|
||||
context 'with read_user scope' do
|
||||
let(:scopes) { %i(read_user) }
|
||||
|
||||
it 'returns the version information' do
|
||||
get api('/version', personal_access_token: personal_access_token)
|
||||
|
||||
expect_version
|
||||
end
|
||||
end
|
||||
|
||||
context 'with neither api nor read_user scope' do
|
||||
let(:scopes) { %i(read_repository) }
|
||||
|
||||
it 'returns authorization error' do
|
||||
get api('/version', personal_access_token: personal_access_token)
|
||||
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expect_version
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response['version']).to eq(Gitlab::VERSION)
|
||||
expect(json_response['revision']).to eq(Gitlab.revision)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with graphql enabled' do
|
||||
|
|
|
@ -75,8 +75,9 @@ describe MergeRequestWidgetEntity do
|
|||
let(:role) { :developer }
|
||||
|
||||
it 'has add ci config path' do
|
||||
expect(subject[:merge_request_add_ci_config_path])
|
||||
.to eq("/#{resource.project.full_path}/-/new/#{resource.source_branch}?commit_message=Add+.gitlab-ci.yml&file_name=.gitlab-ci.yml")
|
||||
expected_path = "/#{resource.project.full_path}/-/new/#{resource.source_branch}?commit_message=Add+.gitlab-ci.yml&file_name=.gitlab-ci.yml&suggest_gitlab_ci_yml=true"
|
||||
|
||||
expect(subject[:merge_request_add_ci_config_path]).to eq(expected_path)
|
||||
end
|
||||
|
||||
context 'when source project is missing' do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'a supported metrics dashboard url' do
|
||||
RSpec.shared_examples 'redacts the embed placeholder' do
|
||||
context 'no user is logged in' do
|
||||
it 'redacts the placeholder' do
|
||||
expect(doc.to_s).to be_empty
|
||||
|
@ -14,7 +14,9 @@ RSpec.shared_examples 'a supported metrics dashboard url' do
|
|||
expect(doc.to_s).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'retains the embed placeholder when applicable' do
|
||||
context 'the user has requisite permissions' do
|
||||
let(:user) { create(:user) }
|
||||
let(:doc) { filter(input, current_user: user) }
|
||||
|
@ -22,7 +24,7 @@ RSpec.shared_examples 'a supported metrics dashboard url' do
|
|||
it 'leaves the placeholder' do
|
||||
project.add_maintainer(user)
|
||||
|
||||
expect(doc.to_s).to eq(input)
|
||||
expect(CGI.unescapeHTML(doc.to_s)).to eq(input)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,4 +51,10 @@ describe ImportExportUploader do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.workhorse_local_upload_path' do
|
||||
it 'returns path that includes uploads dir' do
|
||||
expect(described_class.workhorse_local_upload_path).to end_with('/uploads/tmp/uploads')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue