Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
19ab203bec
commit
9c042f0dad
|
@ -136,8 +136,8 @@ export default {
|
||||||
return __('Branch');
|
return __('Branch');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commitTitleText() {
|
commitTitle() {
|
||||||
return this.pipeline?.commit?.title || __("Can't find HEAD commit for this branch");
|
return this.pipeline?.commit?.title;
|
||||||
},
|
},
|
||||||
hasAuthor() {
|
hasAuthor() {
|
||||||
return (
|
return (
|
||||||
|
@ -159,22 +159,27 @@ export default {
|
||||||
<div class="pipeline-tags" data-testid="pipeline-url-table-cell">
|
<div class="pipeline-tags" data-testid="pipeline-url-table-cell">
|
||||||
<template v-if="rearrangePipelinesTable">
|
<template v-if="rearrangePipelinesTable">
|
||||||
<div class="commit-title gl-mb-2" data-testid="commit-title-container">
|
<div class="commit-title gl-mb-2" data-testid="commit-title-container">
|
||||||
<span class="gl-display-flex">
|
<span v-if="commitTitle" class="gl-display-flex">
|
||||||
<tooltip-on-truncate :title="commitTitleText" class="flex-truncate-child gl-flex-grow-1">
|
<tooltip-on-truncate :title="commitTitle" class="flex-truncate-child gl-flex-grow-1">
|
||||||
<gl-link
|
<gl-link
|
||||||
:href="pipeline.path"
|
:href="commitUrl"
|
||||||
class="commit-row-message gl-text-blue-600!"
|
class="commit-row-message gl-text-gray-900"
|
||||||
data-testid="commit-title"
|
data-testid="commit-title"
|
||||||
data-qa-selector="pipeline_url_link"
|
>{{ commitTitle }}</gl-link
|
||||||
>{{ commitTitleText }}</gl-link
|
|
||||||
>
|
>
|
||||||
</tooltip-on-truncate>
|
</tooltip-on-truncate>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-mb-2">
|
<div class="gl-mb-2">
|
||||||
<span class="gl-font-weight-bold gl-text-gray-500" data-testid="pipeline-identifier">
|
<gl-link
|
||||||
|
:href="pipeline.path"
|
||||||
|
class="gl-text-decoration-underline gl-text-blue-600!"
|
||||||
|
data-testid="pipeline-url-link"
|
||||||
|
data-qa-selector="pipeline_url_link"
|
||||||
|
>
|
||||||
#{{ pipeline[pipelineKey] }}
|
#{{ pipeline[pipelineKey] }}
|
||||||
</span>
|
</gl-link>
|
||||||
<!--Commit row-->
|
<!--Commit row-->
|
||||||
<div class="icon-container gl-display-inline-block">
|
<div class="icon-container gl-display-inline-block">
|
||||||
<gl-icon
|
<gl-icon
|
||||||
|
|
|
@ -10,11 +10,11 @@ import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import { redirectTo } from '~/lib/utils/url_utility';
|
import { redirectTo } from '~/lib/utils/url_utility';
|
||||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
||||||
import getRefMixin from '../mixins/get_ref';
|
import getRefMixin from '../mixins/get_ref';
|
||||||
import blobInfoQuery from '../queries/blob_info.query.graphql';
|
import blobInfoQuery from '../queries/blob_info.query.graphql';
|
||||||
import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE } from '../constants';
|
import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE } from '../constants';
|
||||||
import BlobButtonGroup from './blob_button_group.vue';
|
import BlobButtonGroup from './blob_button_group.vue';
|
||||||
import BlobEdit from './blob_edit.vue';
|
|
||||||
import ForkSuggestion from './fork_suggestion.vue';
|
import ForkSuggestion from './fork_suggestion.vue';
|
||||||
import { loadViewer } from './blob_viewers';
|
import { loadViewer } from './blob_viewers';
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ export default {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BlobHeader,
|
BlobHeader,
|
||||||
BlobEdit,
|
|
||||||
BlobButtonGroup,
|
BlobButtonGroup,
|
||||||
BlobContent,
|
BlobContent,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
GlButton,
|
GlButton,
|
||||||
ForkSuggestion,
|
ForkSuggestion,
|
||||||
|
WebIdeLink,
|
||||||
},
|
},
|
||||||
mixins: [getRefMixin, glFeatureFlagMixin()],
|
mixins: [getRefMixin, glFeatureFlagMixin()],
|
||||||
inject: {
|
inject: {
|
||||||
|
@ -213,12 +213,15 @@ export default {
|
||||||
@viewer-changed="switchViewer"
|
@viewer-changed="switchViewer"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<blob-edit
|
<web-ide-link
|
||||||
v-if="!blobInfo.archived"
|
v-if="!blobInfo.archived"
|
||||||
:show-edit-button="!isBinaryFileType"
|
:show-edit-button="!isBinaryFileType"
|
||||||
:edit-path="blobInfo.editBlobPath"
|
class="gl-mr-3"
|
||||||
:web-ide-path="blobInfo.ideEditPath"
|
:edit-url="blobInfo.editBlobPath"
|
||||||
|
:web-ide-url="blobInfo.ideEditPath"
|
||||||
:needs-to-fork="showForkSuggestion"
|
:needs-to-fork="showForkSuggestion"
|
||||||
|
is-blob
|
||||||
|
disable-fork-modal
|
||||||
@edit="editBlob"
|
@edit="editBlob"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
<script>
|
|
||||||
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
WebIdeLink,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
showEditButton: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
editPath: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
webIdePath: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
needsToFork: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onEdit(target) {
|
|
||||||
this.$emit('edit', target);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<web-ide-link
|
|
||||||
:show-edit-button="showEditButton"
|
|
||||||
class="gl-mr-3"
|
|
||||||
:edit-url="editPath"
|
|
||||||
:web-ide-url="webIdePath"
|
|
||||||
:needs-to-fork="needsToFork"
|
|
||||||
:is-blob="true"
|
|
||||||
disable-fork-modal
|
|
||||||
@edit="onEdit"
|
|
||||||
/>
|
|
||||||
</template>
|
|
|
@ -111,6 +111,15 @@ class ApplicationController < ActionController::Base
|
||||||
render plain: e.message, status: :too_many_requests
|
render plain: e.message, status: :too_many_requests
|
||||||
end
|
end
|
||||||
|
|
||||||
|
content_security_policy do |p|
|
||||||
|
next if p.directives.blank?
|
||||||
|
next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank?
|
||||||
|
|
||||||
|
default_connect_src = p.directives['connect-src'] || p.directives['default-src']
|
||||||
|
connect_src_values = Array.wrap(default_connect_src) | [Gitlab::CurrentSettings.snowplow_collector_hostname]
|
||||||
|
p.connect_src(*connect_src_values)
|
||||||
|
end
|
||||||
|
|
||||||
def redirect_back_or_default(default: root_path, options: {})
|
def redirect_back_or_default(default: root_path, options: {})
|
||||||
redirect_back(fallback_location: default, **options)
|
redirect_back(fallback_location: default, **options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,19 @@ module Types
|
||||||
class ServiceTypeEnum < BaseEnum
|
class ServiceTypeEnum < BaseEnum
|
||||||
graphql_name 'ServiceType'
|
graphql_name 'ServiceType'
|
||||||
|
|
||||||
|
class << self
|
||||||
|
private
|
||||||
|
|
||||||
|
def type_description(type)
|
||||||
|
"#{type} type"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This prepend must stay here because the dynamic block below depends on it.
|
||||||
|
prepend_mod # rubocop: disable Cop/InjectEnterpriseEditionModule
|
||||||
|
|
||||||
::Integration.available_integration_types(include_dev: false).each do |type|
|
::Integration.available_integration_types(include_dev: false).each do |type|
|
||||||
value type.underscore.upcase, value: type, description: "#{type} type"
|
value type.underscore.upcase, value: type, description: type_description(type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Integration < ApplicationRecord
|
||||||
include EachBatch
|
include EachBatch
|
||||||
include IgnorableColumns
|
include IgnorableColumns
|
||||||
|
|
||||||
ignore_column :template, remove_with: '14.10', remove_after: '2022-03-22'
|
ignore_column :template, remove_with: '15.0', remove_after: '2022-04-22'
|
||||||
|
|
||||||
INTEGRATION_NAMES = %w[
|
INTEGRATION_NAMES = %w[
|
||||||
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
|
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
= render 'layouts/startup_css', { startup_filename: local_assigns.fetch(:startup_filename, nil) }
|
= render 'layouts/startup_css', { startup_filename: local_assigns.fetch(:startup_filename, nil) }
|
||||||
- if user_application_theme == 'gl-dark'
|
- if user_application_theme == 'gl-dark'
|
||||||
|
%meta{ name: 'color-scheme', content: 'dark light' }
|
||||||
= stylesheet_link_tag_defer "application_dark"
|
= stylesheet_link_tag_defer "application_dark"
|
||||||
= yield :page_specific_styles
|
= yield :page_specific_styles
|
||||||
= stylesheet_link_tag_defer "application_utilities_dark"
|
= stylesheet_link_tag_defer "application_utilities_dark"
|
||||||
|
|
|
@ -714,7 +714,14 @@ module.exports = {
|
||||||
},
|
},
|
||||||
host: DEV_SERVER_HOST || 'localhost',
|
host: DEV_SERVER_HOST || 'localhost',
|
||||||
port: DEV_SERVER_PORT || 3808,
|
port: DEV_SERVER_PORT || 3808,
|
||||||
|
// Setting up hot module reloading
|
||||||
|
// HMR works by setting up a websocket server and injecting
|
||||||
|
// a client script which connects to that server.
|
||||||
|
// The server will push messages to the client to reload parts
|
||||||
|
// of the JavaScript or reload the page if necessary
|
||||||
|
webSocketServer: DEV_SERVER_LIVERELOAD ? 'ws' : false,
|
||||||
hot: DEV_SERVER_LIVERELOAD,
|
hot: DEV_SERVER_LIVERELOAD,
|
||||||
|
liveReload: DEV_SERVER_LIVERELOAD,
|
||||||
// The following settings are mainly needed for HMR support in gitpod.
|
// The following settings are mainly needed for HMR support in gitpod.
|
||||||
// Per default only local hosts are allowed, but here we could
|
// Per default only local hosts are allowed, but here we could
|
||||||
// allow different hosts (e.g. ['.gitpod'], all of gitpod),
|
// allow different hosts (e.g. ['.gitpod'], all of gitpod),
|
||||||
|
|
|
@ -18018,6 +18018,7 @@ State of a Sentry error.
|
||||||
| <a id="servicetypeexternal_wiki_service"></a>`EXTERNAL_WIKI_SERVICE` | ExternalWikiService type. |
|
| <a id="servicetypeexternal_wiki_service"></a>`EXTERNAL_WIKI_SERVICE` | ExternalWikiService type. |
|
||||||
| <a id="servicetypeflowdock_service"></a>`FLOWDOCK_SERVICE` | FlowdockService type. |
|
| <a id="servicetypeflowdock_service"></a>`FLOWDOCK_SERVICE` | FlowdockService type. |
|
||||||
| <a id="servicetypegithub_service"></a>`GITHUB_SERVICE` | GithubService type. |
|
| <a id="servicetypegithub_service"></a>`GITHUB_SERVICE` | GithubService type. |
|
||||||
|
| <a id="servicetypegitlab_slack_application_service"></a>`GITLAB_SLACK_APPLICATION_SERVICE` | GitlabSlackApplicationService type (Gitlab.com only). |
|
||||||
| <a id="servicetypehangouts_chat_service"></a>`HANGOUTS_CHAT_SERVICE` | HangoutsChatService type. |
|
| <a id="servicetypehangouts_chat_service"></a>`HANGOUTS_CHAT_SERVICE` | HangoutsChatService type. |
|
||||||
| <a id="servicetypeirker_service"></a>`IRKER_SERVICE` | IrkerService type. |
|
| <a id="servicetypeirker_service"></a>`IRKER_SERVICE` | IrkerService type. |
|
||||||
| <a id="servicetypejenkins_service"></a>`JENKINS_SERVICE` | JenkinsService type. |
|
| <a id="servicetypejenkins_service"></a>`JENKINS_SERVICE` | JenkinsService type. |
|
||||||
|
|
|
@ -559,6 +559,8 @@ Decreasing the user cap does not approve pending members.
|
||||||
When the number of billable users reaches the user cap, any new member is put in a pending state
|
When the number of billable users reaches the user cap, any new member is put in a pending state
|
||||||
and must be approved.
|
and must be approved.
|
||||||
|
|
||||||
|
Pending members do not count as billable. Members count as billable only after they have been approved and are no longer in a pending state.
|
||||||
|
|
||||||
Prerequisite:
|
Prerequisite:
|
||||||
|
|
||||||
- You must be assigned the Owner role) for the group.
|
- You must be assigned the Owner role) for the group.
|
||||||
|
|
|
@ -20,7 +20,7 @@ The default behavior is:
|
||||||
explicitly, the inventory object is stored in the `default` namespace.
|
explicitly, the inventory object is stored in the `default` namespace.
|
||||||
- The `name` is generated from the numeric project ID of the manifest project and the numeric agent ID.
|
- The `name` is generated from the numeric project ID of the manifest project and the numeric agent ID.
|
||||||
|
|
||||||
This way the Agent constructs the name and local where the inventory object is
|
This way, the Agent constructs the name and location where the inventory object is
|
||||||
stored in the cluster.
|
stored in the cluster.
|
||||||
|
|
||||||
The Agent cannot locate the existing inventory object if you:
|
The Agent cannot locate the existing inventory object if you:
|
||||||
|
@ -31,7 +31,7 @@ The Agent cannot locate the existing inventory object if you:
|
||||||
## Inventory object template
|
## Inventory object template
|
||||||
|
|
||||||
The inventory object template is a `ConfigMap` object that allows you to configure the namespace and the name of the inventory
|
The inventory object template is a `ConfigMap` object that allows you to configure the namespace and the name of the inventory
|
||||||
object. Store this template with manifest files in a single group.
|
object. Store this template with manifest files as a single logical group.
|
||||||
|
|
||||||
Example inventory object template:
|
Example inventory object template:
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ These shortcuts are available in most areas of GitLab:
|
||||||
| <kbd>Shift</kbd> + <kbd>i</kbd> | Go to your Issues page. |
|
| <kbd>Shift</kbd> + <kbd>i</kbd> | Go to your Issues page. |
|
||||||
| <kbd>Shift</kbd> + <kbd>m</kbd> | Go to your [Merge requests](project/merge_requests/index.md) page. |
|
| <kbd>Shift</kbd> + <kbd>m</kbd> | Go to your [Merge requests](project/merge_requests/index.md) page. |
|
||||||
| <kbd>Shift</kbd> + <kbd>t</kbd> | Go to your To-Do List page. |
|
| <kbd>Shift</kbd> + <kbd>t</kbd> | Go to your To-Do List page. |
|
||||||
| <kbd>p</kbd> + <kbd>b</kbd> | Show or hide the Performance Bar. |
|
| <kbd>p</kbd> then <kbd>b</kbd> | Show or hide the Performance Bar. |
|
||||||
| <kbd>g</kbd> + <kbd>x</kbd> | Toggle between [GitLab](https://gitlab.com/) and [GitLab Next](https://next.gitlab.com/) (GitLab SaaS only). |
|
| <kbd>g</kbd> then <kbd>x</kbd> | Toggle between [GitLab](https://gitlab.com/) and [GitLab Next](https://next.gitlab.com/) (GitLab SaaS only). |
|
||||||
| <kbd>.</kbd> | Open the [Web IDE](project/web_ide/index.md). |
|
| <kbd>.</kbd> | Open the [Web IDE](project/web_ide/index.md). |
|
||||||
|
|
||||||
Additionally, the following shortcuts are available when editing text in text
|
Additionally, the following shortcuts are available when editing text in text
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'Subscriptions Content Security Policy' do
|
RSpec.describe 'Subscriptions Content Security Policy' do
|
||||||
|
include ContentSecurityPolicyHelpers
|
||||||
|
|
||||||
let(:installation) { create(:jira_connect_installation) }
|
let(:installation) { create(:jira_connect_installation) }
|
||||||
let(:qsh) { Atlassian::Jwt.create_query_string_hash('https://gitlab.test/subscriptions', 'GET', 'https://gitlab.test') }
|
let(:qsh) { Atlassian::Jwt.create_query_string_hash('https://gitlab.test/subscriptions', 'GET', 'https://gitlab.test') }
|
||||||
let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) }
|
let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) }
|
||||||
|
@ -11,10 +13,7 @@ RSpec.describe 'Subscriptions Content Security Policy' do
|
||||||
|
|
||||||
context 'when there is no global config' do
|
context 'when there is no global config' do
|
||||||
before do
|
before do
|
||||||
expect_next_instance_of(JiraConnect::SubscriptionsController) do |controller|
|
setup_csp_for_controller(JiraConnect::SubscriptionsController)
|
||||||
expect(controller).to receive(:current_content_security_policy)
|
|
||||||
.and_return(ActionDispatch::ContentSecurityPolicy.new)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not add CSP directives' do
|
it 'does not add CSP directives' do
|
||||||
|
@ -31,9 +30,7 @@ RSpec.describe 'Subscriptions Content Security Policy' do
|
||||||
p.style_src :self, 'https://some-cdn.test'
|
p.style_src :self, 'https://some-cdn.test'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect_next_instance_of(JiraConnect::SubscriptionsController) do |controller|
|
setup_existing_csp_for_controller(JiraConnect::SubscriptionsController, csp)
|
||||||
expect(controller).to receive(:current_content_security_policy).and_return(csp)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'appends to CSP directives' do
|
it 'appends to CSP directives' do
|
||||||
|
|
|
@ -64,7 +64,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
||||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||||
page.within('.ci-table') do
|
page.within('.ci-table') do
|
||||||
expect(page).to have_selector('.ci-created', count: 2)
|
expect(page).to have_selector('.ci-created', count: 2)
|
||||||
expect(first('[data-testid="pipeline-identifier"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -101,16 +101,16 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
||||||
page.within('.ci-table') do
|
page.within('.ci-table') do
|
||||||
expect(page).to have_selector('.ci-pending', count: 4)
|
expect(page).to have_selector('.ci-pending', count: 4)
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[0])
|
expect(all('[data-testid="pipeline-url-link"]')[0])
|
||||||
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[1])
|
expect(all('[data-testid="pipeline-url-link"]')[1])
|
||||||
.to have_content("##{detached_merge_request_pipeline.id}")
|
.to have_content("##{detached_merge_request_pipeline.id}")
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[2])
|
expect(all('[data-testid="pipeline-url-link"]')[2])
|
||||||
.to have_content("##{push_pipeline_2.id}")
|
.to have_content("##{push_pipeline_2.id}")
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[3])
|
expect(all('[data-testid="pipeline-url-link"]')[3])
|
||||||
.to have_content("##{push_pipeline.id}")
|
.to have_content("##{push_pipeline.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -201,7 +201,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
||||||
it 'sees a branch pipeline in pipeline tab' do
|
it 'sees a branch pipeline in pipeline tab' do
|
||||||
page.within('.ci-table') do
|
page.within('.ci-table') do
|
||||||
expect(page).to have_selector('.ci-created', count: 1)
|
expect(page).to have_selector('.ci-created', count: 1)
|
||||||
expect(first('[data-testid="pipeline-identifier"]')).to have_content("##{push_pipeline.id}")
|
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{push_pipeline.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
||||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||||
page.within('.ci-table') do
|
page.within('.ci-table') do
|
||||||
expect(page).to have_selector('.ci-pending', count: 2)
|
expect(page).to have_selector('.ci-pending', count: 2)
|
||||||
expect(first('[data-testid="pipeline-identifier"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -295,16 +295,16 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
||||||
page.within('.ci-table') do
|
page.within('.ci-table') do
|
||||||
expect(page).to have_selector('.ci-pending', count: 4)
|
expect(page).to have_selector('.ci-pending', count: 4)
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[0])
|
expect(all('[data-testid="pipeline-url-link"]')[0])
|
||||||
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[1])
|
expect(all('[data-testid="pipeline-url-link"]')[1])
|
||||||
.to have_content("##{detached_merge_request_pipeline.id}")
|
.to have_content("##{detached_merge_request_pipeline.id}")
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[2])
|
expect(all('[data-testid="pipeline-url-link"]')[2])
|
||||||
.to have_content("##{push_pipeline_2.id}")
|
.to have_content("##{push_pipeline_2.id}")
|
||||||
|
|
||||||
expect(all('[data-testid="pipeline-identifier"]')[3])
|
expect(all('[data-testid="pipeline-url-link"]')[3])
|
||||||
.to have_content("##{push_pipeline.id}")
|
.to have_content("##{push_pipeline.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -134,7 +134,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
|
||||||
create_merge_request_pipeline
|
create_merge_request_pipeline
|
||||||
act_on_security_warning(action: 'Run pipeline')
|
act_on_security_warning(action: 'Run pipeline')
|
||||||
|
|
||||||
check_pipeline(expected_project: parent_project, link_selector: 'pipeline-url-link')
|
check_pipeline(expected_project: parent_project)
|
||||||
check_head_pipeline(expected_project: parent_project)
|
check_head_pipeline(expected_project: parent_project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -179,13 +179,13 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
|
||||||
click_button('Run pipeline')
|
click_button('Run pipeline')
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_pipeline(expected_project:, link_selector: 'commit-title')
|
def check_pipeline(expected_project:)
|
||||||
page.within('.ci-table') do
|
page.within('.ci-table') do
|
||||||
expect(page).to have_selector('.commit', count: 2)
|
expect(page).to have_selector('.commit', count: 2)
|
||||||
|
|
||||||
page.within(first('.commit')) do
|
page.within(first('.commit')) do
|
||||||
page.within('.pipeline-tags') do
|
page.within('.pipeline-tags') do
|
||||||
expect(page.find("[data-testid=#{link_selector}]")[:href]).to include(expected_project.full_path)
|
expect(page.find('[data-testid="pipeline-url-link"]')[:href]).to include(expected_project.full_path)
|
||||||
expect(page).to have_content('detached')
|
expect(page).to have_content('detached')
|
||||||
end
|
end
|
||||||
page.within('.pipeline-triggerer') do
|
page.within('.pipeline-triggerer') do
|
||||||
|
|
|
@ -711,7 +711,7 @@ RSpec.describe 'Pipelines', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
|
expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
|
||||||
expect(page.find('[data-testid="pipeline-identifier"]')).to have_content "##{pipeline.iid}"
|
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'Tracings Content Security Policy' do
|
RSpec.describe 'Tracings Content Security Policy' do
|
||||||
|
include ContentSecurityPolicyHelpers
|
||||||
|
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
|
@ -18,10 +20,7 @@ RSpec.describe 'Tracings Content Security Policy' do
|
||||||
|
|
||||||
context 'when there is no global config' do
|
context 'when there is no global config' do
|
||||||
before do
|
before do
|
||||||
expect_next_instance_of(Projects::TracingsController) do |controller|
|
setup_csp_for_controller(Projects::TracingsController)
|
||||||
expect(controller).to receive(:current_content_security_policy)
|
|
||||||
.and_return(ActionDispatch::ContentSecurityPolicy.new)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not add CSP directives' do
|
it 'does not add CSP directives' do
|
||||||
|
@ -37,9 +36,7 @@ RSpec.describe 'Tracings Content Security Policy' do
|
||||||
p.frame_src 'https://global-policy.com'
|
p.frame_src 'https://global-policy.com'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect_next_instance_of(Projects::TracingsController) do |controller|
|
setup_existing_csp_for_controller(Projects::TracingsController, csp)
|
||||||
expect(controller).to receive(:current_content_security_policy).and_return(csp)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when external_url is set' do
|
context 'when external_url is set' do
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'Static Site Editor' do
|
RSpec.describe 'Static Site Editor' do
|
||||||
|
include ContentSecurityPolicyHelpers
|
||||||
|
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :public, :repository) }
|
let_it_be(:project) { create(:project, :public, :repository) }
|
||||||
|
|
||||||
|
@ -79,10 +81,7 @@ RSpec.describe 'Static Site Editor' do
|
||||||
|
|
||||||
context 'when no global CSP config exists' do
|
context 'when no global CSP config exists' do
|
||||||
before do
|
before do
|
||||||
expect_next_instance_of(Projects::StaticSiteEditorController) do |controller|
|
setup_csp_for_controller(Projects::StaticSiteEditorController)
|
||||||
expect(controller).to receive(:current_content_security_policy)
|
|
||||||
.and_return(ActionDispatch::ContentSecurityPolicy.new)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not add CSP directives' do
|
it 'does not add CSP directives' do
|
||||||
|
@ -101,9 +100,7 @@ RSpec.describe 'Static Site Editor' do
|
||||||
p.frame_src :self, cdn_url
|
p.frame_src :self, cdn_url
|
||||||
end
|
end
|
||||||
|
|
||||||
expect_next_instance_of(Projects::StaticSiteEditorController) do |controller|
|
setup_existing_csp_for_controller(Projects::StaticSiteEditorController, csp)
|
||||||
expect(controller).to receive(:current_content_security_policy).and_return(csp)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'appends youtube to the CSP frame-src policy' do
|
it 'appends youtube to the CSP frame-src policy' do
|
||||||
|
|
|
@ -676,7 +676,7 @@ export const mockPipeline = (projectPath) => {
|
||||||
short_id: 'fd6df5b3',
|
short_id: 'fd6df5b3',
|
||||||
created_at: '2021-10-19T21:17:12.000+00:00',
|
created_at: '2021-10-19T21:17:12.000+00:00',
|
||||||
parent_ids: ['7147906b84306e83cb3fec6582a25390b75713c6'],
|
parent_ids: ['7147906b84306e83cb3fec6582a25390b75713c6'],
|
||||||
title: 'Commit Title',
|
title: 'Commit',
|
||||||
message: 'Commit',
|
message: 'Commit',
|
||||||
author_name: 'Administrator',
|
author_name: 'Administrator',
|
||||||
author_email: 'admin@example.com',
|
author_email: 'admin@example.com',
|
||||||
|
@ -1141,176 +1141,3 @@ export const mockPipelineBranch = () => {
|
||||||
viewType: 'root',
|
viewType: 'root',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockPipelineNoCommit = () => {
|
|
||||||
return {
|
|
||||||
pipeline: {
|
|
||||||
id: 268,
|
|
||||||
iid: 34,
|
|
||||||
user: {
|
|
||||||
id: 1,
|
|
||||||
username: 'root',
|
|
||||||
name: 'Administrator',
|
|
||||||
state: 'active',
|
|
||||||
avatar_url:
|
|
||||||
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
|
||||||
web_url: 'http://gdk.test:3000/root',
|
|
||||||
show_status: false,
|
|
||||||
path: '/root',
|
|
||||||
},
|
|
||||||
active: false,
|
|
||||||
source: 'push',
|
|
||||||
created_at: '2022-01-14T17:40:27.866Z',
|
|
||||||
updated_at: '2022-01-14T18:02:35.850Z',
|
|
||||||
path: '/root/mr-widgets/-/pipelines/268',
|
|
||||||
flags: {
|
|
||||||
stuck: false,
|
|
||||||
auto_devops: false,
|
|
||||||
merge_request: false,
|
|
||||||
yaml_errors: false,
|
|
||||||
retryable: true,
|
|
||||||
cancelable: false,
|
|
||||||
failure_reason: false,
|
|
||||||
detached_merge_request_pipeline: false,
|
|
||||||
merge_request_pipeline: false,
|
|
||||||
merge_train_pipeline: false,
|
|
||||||
latest: true,
|
|
||||||
},
|
|
||||||
details: {
|
|
||||||
status: {
|
|
||||||
icon: 'status_warning',
|
|
||||||
text: 'passed',
|
|
||||||
label: 'passed with warnings',
|
|
||||||
group: 'success-with-warnings',
|
|
||||||
tooltip: 'passed',
|
|
||||||
has_details: true,
|
|
||||||
details_path: '/root/mr-widgets/-/pipelines/268',
|
|
||||||
illustration: null,
|
|
||||||
favicon:
|
|
||||||
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
|
|
||||||
},
|
|
||||||
stages: [
|
|
||||||
{
|
|
||||||
name: 'validate',
|
|
||||||
title: 'validate: passed with warnings',
|
|
||||||
status: {
|
|
||||||
icon: 'status_warning',
|
|
||||||
text: 'passed',
|
|
||||||
label: 'passed with warnings',
|
|
||||||
group: 'success-with-warnings',
|
|
||||||
tooltip: 'passed',
|
|
||||||
has_details: true,
|
|
||||||
details_path: '/root/mr-widgets/-/pipelines/268#validate',
|
|
||||||
illustration: null,
|
|
||||||
favicon:
|
|
||||||
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
|
|
||||||
},
|
|
||||||
path: '/root/mr-widgets/-/pipelines/268#validate',
|
|
||||||
dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=validate',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'test',
|
|
||||||
title: 'test: passed',
|
|
||||||
status: {
|
|
||||||
icon: 'status_success',
|
|
||||||
text: 'passed',
|
|
||||||
label: 'passed',
|
|
||||||
group: 'success',
|
|
||||||
tooltip: 'passed',
|
|
||||||
has_details: true,
|
|
||||||
details_path: '/root/mr-widgets/-/pipelines/268#test',
|
|
||||||
illustration: null,
|
|
||||||
favicon:
|
|
||||||
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
|
|
||||||
},
|
|
||||||
path: '/root/mr-widgets/-/pipelines/268#test',
|
|
||||||
dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=test',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'build',
|
|
||||||
title: 'build: passed',
|
|
||||||
status: {
|
|
||||||
icon: 'status_success',
|
|
||||||
text: 'passed',
|
|
||||||
label: 'passed',
|
|
||||||
group: 'success',
|
|
||||||
tooltip: 'passed',
|
|
||||||
has_details: true,
|
|
||||||
details_path: '/root/mr-widgets/-/pipelines/268#build',
|
|
||||||
illustration: null,
|
|
||||||
favicon:
|
|
||||||
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
|
|
||||||
},
|
|
||||||
path: '/root/mr-widgets/-/pipelines/268#build',
|
|
||||||
dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=build',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
duration: 75,
|
|
||||||
finished_at: '2022-01-14T18:02:35.842Z',
|
|
||||||
name: 'Pipeline',
|
|
||||||
manual_actions: [],
|
|
||||||
scheduled_actions: [],
|
|
||||||
},
|
|
||||||
ref: {
|
|
||||||
name: 'update-ci',
|
|
||||||
path: '/root/mr-widgets/-/commits/update-ci',
|
|
||||||
tag: false,
|
|
||||||
branch: true,
|
|
||||||
merge_request: false,
|
|
||||||
},
|
|
||||||
retry_path: '/root/mr-widgets/-/pipelines/268/retry',
|
|
||||||
delete_path: '/root/mr-widgets/-/pipelines/268',
|
|
||||||
failed_builds: [
|
|
||||||
{
|
|
||||||
id: 1260,
|
|
||||||
name: 'fmt',
|
|
||||||
started: '2022-01-14T17:40:36.435Z',
|
|
||||||
complete: true,
|
|
||||||
archived: false,
|
|
||||||
build_path: '/root/mr-widgets/-/jobs/1260',
|
|
||||||
retry_path: '/root/mr-widgets/-/jobs/1260/retry',
|
|
||||||
playable: false,
|
|
||||||
scheduled: false,
|
|
||||||
created_at: '2022-01-14T17:40:27.879Z',
|
|
||||||
updated_at: '2022-01-14T17:40:42.129Z',
|
|
||||||
status: {
|
|
||||||
icon: 'status_warning',
|
|
||||||
text: 'failed',
|
|
||||||
label: 'failed (allowed to fail)',
|
|
||||||
group: 'failed-with-warnings',
|
|
||||||
tooltip: 'failed - (script failure) (allowed to fail)',
|
|
||||||
has_details: true,
|
|
||||||
details_path: '/root/mr-widgets/-/jobs/1260',
|
|
||||||
illustration: {
|
|
||||||
image:
|
|
||||||
'/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg',
|
|
||||||
size: 'svg-430',
|
|
||||||
title: 'This job does not have a trace.',
|
|
||||||
},
|
|
||||||
favicon:
|
|
||||||
'/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
|
|
||||||
action: {
|
|
||||||
icon: 'retry',
|
|
||||||
title: 'Retry',
|
|
||||||
path: '/root/mr-widgets/-/jobs/1260/retry',
|
|
||||||
method: 'post',
|
|
||||||
button_title: 'Retry this job',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
recoverable: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
project: {
|
|
||||||
id: 23,
|
|
||||||
name: 'mr-widgets',
|
|
||||||
full_path: '/root/mr-widgets',
|
|
||||||
full_name: 'Administrator / mr-widgets',
|
|
||||||
},
|
|
||||||
triggered_by: null,
|
|
||||||
triggered: [],
|
|
||||||
},
|
|
||||||
pipelineScheduleUrl: 'foo',
|
|
||||||
pipelineKey: 'id',
|
|
||||||
viewType: 'root',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import { trimText } from 'helpers/text_helper';
|
import { trimText } from 'helpers/text_helper';
|
||||||
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
|
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
|
||||||
import {
|
import { mockPipeline, mockPipelineBranch, mockPipelineTag } from './mock_data';
|
||||||
mockPipeline,
|
|
||||||
mockPipelineBranch,
|
|
||||||
mockPipelineTag,
|
|
||||||
mockPipelineNoCommit,
|
|
||||||
} from './mock_data';
|
|
||||||
|
|
||||||
const projectPath = 'test/test';
|
const projectPath = 'test/test';
|
||||||
|
|
||||||
|
@ -31,7 +26,7 @@ describe('Pipeline Url Component', () => {
|
||||||
const findCommitIconType = () => wrapper.findByTestId('commit-icon-type');
|
const findCommitIconType = () => wrapper.findByTestId('commit-icon-type');
|
||||||
|
|
||||||
const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
|
const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
|
||||||
const findCommitTitle = () => wrapper.findByTestId('commit-title');
|
const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
|
||||||
|
|
||||||
const defaultProps = mockPipeline(projectPath);
|
const defaultProps = mockPipeline(projectPath);
|
||||||
|
|
||||||
|
@ -237,33 +232,5 @@ describe('Pipeline Url Component', () => {
|
||||||
expect(findCommitIconType().attributes('title')).toBe(expectedTitle);
|
expect(findCommitIconType().attributes('title')).toBe(expectedTitle);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('with commit', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
createComponent({}, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays commit title with link to pipeline', () => {
|
|
||||||
expect(findCommitTitle().attributes('href')).toBe(defaultProps.pipeline.path);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays commit title text', () => {
|
|
||||||
expect(findCommitTitle().text()).toBe(defaultProps.pipeline.commit.title);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('without commit', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
createComponent(mockPipelineNoCommit(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays cant find head commit text', () => {
|
|
||||||
expect(findCommitTitle().text()).toBe("Can't find HEAD commit for this branch");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays link to pipeline', () => {
|
|
||||||
expect(findCommitTitle().attributes('href')).toBe(mockPipelineNoCommit().pipeline.path);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ import BlobContent from '~/blob/components/blob_content.vue';
|
||||||
import BlobHeader from '~/blob/components/blob_header.vue';
|
import BlobHeader from '~/blob/components/blob_header.vue';
|
||||||
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
|
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
|
||||||
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
|
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
|
||||||
import BlobEdit from '~/repository/components/blob_edit.vue';
|
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
||||||
import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
|
import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
|
||||||
import { loadViewer } from '~/repository/components/blob_viewers';
|
import { loadViewer } from '~/repository/components/blob_viewers';
|
||||||
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
|
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
|
||||||
|
@ -99,7 +99,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
|
||||||
describe('Blob content viewer component', () => {
|
describe('Blob content viewer component', () => {
|
||||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||||
const findBlobHeader = () => wrapper.findComponent(BlobHeader);
|
const findBlobHeader = () => wrapper.findComponent(BlobHeader);
|
||||||
const findBlobEdit = () => wrapper.findComponent(BlobEdit);
|
const findWebIdeLink = () => wrapper.findComponent(WebIdeLink);
|
||||||
const findPipelineEditor = () => wrapper.findByTestId('pipeline-editor');
|
const findPipelineEditor = () => wrapper.findByTestId('pipeline-editor');
|
||||||
const findBlobContent = () => wrapper.findComponent(BlobContent);
|
const findBlobContent = () => wrapper.findComponent(BlobContent);
|
||||||
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
|
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
|
||||||
|
@ -255,32 +255,32 @@ describe('Blob content viewer component', () => {
|
||||||
describe('BlobHeader action slot', () => {
|
describe('BlobHeader action slot', () => {
|
||||||
const { ideEditPath, editBlobPath } = simpleViewerMock;
|
const { ideEditPath, editBlobPath } = simpleViewerMock;
|
||||||
|
|
||||||
it('renders BlobHeaderEdit buttons in simple viewer', async () => {
|
it('renders WebIdeLink button in simple viewer', async () => {
|
||||||
await createComponent({ inject: { BlobContent: true, BlobReplace: true } }, mount);
|
await createComponent({ inject: { BlobContent: true, BlobReplace: true } }, mount);
|
||||||
|
|
||||||
expect(findBlobEdit().props()).toMatchObject({
|
expect(findWebIdeLink().props()).toMatchObject({
|
||||||
editPath: editBlobPath,
|
editUrl: editBlobPath,
|
||||||
webIdePath: ideEditPath,
|
webIdeUrl: ideEditPath,
|
||||||
showEditButton: true,
|
showEditButton: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders BlobHeaderEdit button in rich viewer', async () => {
|
it('renders WebIdeLink button in rich viewer', async () => {
|
||||||
await createComponent({ blob: richViewerMock }, mount);
|
await createComponent({ blob: richViewerMock }, mount);
|
||||||
|
|
||||||
expect(findBlobEdit().props()).toMatchObject({
|
expect(findWebIdeLink().props()).toMatchObject({
|
||||||
editPath: editBlobPath,
|
editUrl: editBlobPath,
|
||||||
webIdePath: ideEditPath,
|
webIdeUrl: ideEditPath,
|
||||||
showEditButton: true,
|
showEditButton: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders BlobHeaderEdit button for binary files', async () => {
|
it('renders WebIdeLink button for binary files', async () => {
|
||||||
await createComponent({ blob: richViewerMock, isBinary: true }, mount);
|
await createComponent({ blob: richViewerMock, isBinary: true }, mount);
|
||||||
|
|
||||||
expect(findBlobEdit().props()).toMatchObject({
|
expect(findWebIdeLink().props()).toMatchObject({
|
||||||
editPath: editBlobPath,
|
editUrl: editBlobPath,
|
||||||
webIdePath: ideEditPath,
|
webIdeUrl: ideEditPath,
|
||||||
showEditButton: false,
|
showEditButton: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -318,7 +318,7 @@ describe('Blob content viewer component', () => {
|
||||||
|
|
||||||
expect(findBlobHeader().props('hideViewerSwitcher')).toBe(true);
|
expect(findBlobHeader().props('hideViewerSwitcher')).toBe(true);
|
||||||
expect(findBlobHeader().props('isBinary')).toBe(true);
|
expect(findBlobHeader().props('isBinary')).toBe(true);
|
||||||
expect(findBlobEdit().props('showEditButton')).toBe(false);
|
expect(findWebIdeLink().props('showEditButton')).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -401,12 +401,12 @@ describe('Blob content viewer component', () => {
|
||||||
beforeEach(() => createComponent({}, mount));
|
beforeEach(() => createComponent({}, mount));
|
||||||
|
|
||||||
it('simple edit redirects to the simple editor', () => {
|
it('simple edit redirects to the simple editor', () => {
|
||||||
findBlobEdit().vm.$emit('edit', 'simple');
|
findWebIdeLink().vm.$emit('edit', 'simple');
|
||||||
expect(redirectTo).toHaveBeenCalledWith(simpleViewerMock.editBlobPath);
|
expect(redirectTo).toHaveBeenCalledWith(simpleViewerMock.editBlobPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('IDE edit redirects to the IDE editor', () => {
|
it('IDE edit redirects to the IDE editor', () => {
|
||||||
findBlobEdit().vm.$emit('edit', 'ide');
|
findWebIdeLink().vm.$emit('edit', 'ide');
|
||||||
expect(redirectTo).toHaveBeenCalledWith(simpleViewerMock.ideEditPath);
|
expect(redirectTo).toHaveBeenCalledWith(simpleViewerMock.ideEditPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -435,7 +435,7 @@ describe('Blob content viewer component', () => {
|
||||||
mount,
|
mount,
|
||||||
);
|
);
|
||||||
|
|
||||||
findBlobEdit().vm.$emit('edit', 'simple');
|
findWebIdeLink().vm.$emit('edit', 'simple');
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
expect(findForkSuggestion().exists()).toBe(showForkSuggestion);
|
expect(findForkSuggestion().exists()).toBe(showForkSuggestion);
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
|
||||||
import BlobEdit from '~/repository/components/blob_edit.vue';
|
|
||||||
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
|
||||||
|
|
||||||
const DEFAULT_PROPS = {
|
|
||||||
editPath: 'some_file.js/edit',
|
|
||||||
webIdePath: 'some_file.js/ide/edit',
|
|
||||||
showEditButton: true,
|
|
||||||
needsToFork: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('BlobEdit component', () => {
|
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
const createComponent = (props = {}) => {
|
|
||||||
wrapper = shallowMount(BlobEdit, {
|
|
||||||
propsData: {
|
|
||||||
...DEFAULT_PROPS,
|
|
||||||
...props,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
wrapper.destroy();
|
|
||||||
wrapper = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const findEditButton = () => wrapper.find('[data-testid="edit"]');
|
|
||||||
const findWebIdeLink = () => wrapper.find(WebIdeLink);
|
|
||||||
|
|
||||||
it('renders component', () => {
|
|
||||||
createComponent();
|
|
||||||
|
|
||||||
const { editPath, webIdePath } = DEFAULT_PROPS;
|
|
||||||
|
|
||||||
expect(wrapper.props()).toMatchObject({
|
|
||||||
editPath,
|
|
||||||
webIdePath,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders WebIdeLink component', () => {
|
|
||||||
createComponent();
|
|
||||||
|
|
||||||
const { editPath: editUrl, webIdePath: webIdeUrl, needsToFork } = DEFAULT_PROPS;
|
|
||||||
|
|
||||||
expect(findWebIdeLink().props()).toMatchObject({
|
|
||||||
editUrl,
|
|
||||||
webIdeUrl,
|
|
||||||
isBlob: true,
|
|
||||||
showEditButton: true,
|
|
||||||
needsToFork,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Without Edit button', () => {
|
|
||||||
const showEditButton = false;
|
|
||||||
|
|
||||||
it('renders WebIdeLink component without an edit button', () => {
|
|
||||||
createComponent({ showEditButton });
|
|
||||||
|
|
||||||
expect(findWebIdeLink().props()).toMatchObject({ showEditButton });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render an Edit button', () => {
|
|
||||||
createComponent({ showEditButton });
|
|
||||||
|
|
||||||
expect(findEditButton().exists()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -566,6 +566,12 @@ RSpec.describe Integration do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.integration_name_to_type' do
|
||||||
|
it 'transforms the name to a type' do
|
||||||
|
expect(described_class.integration_name_to_type('asana')).to eq('AsanaService')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "{property}_changed?" do
|
describe "{property}_changed?" do
|
||||||
let(:integration) do
|
let(:integration) do
|
||||||
Integrations::Bamboo.create!(
|
Integrations::Bamboo.create!(
|
||||||
|
@ -774,35 +780,33 @@ RSpec.describe Integration do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.available_integration_names' do
|
describe '.available_integration_names' do
|
||||||
it 'calls the right methods' do
|
subject { described_class.available_integration_names }
|
||||||
expect(described_class).to receive(:integration_names).and_call_original
|
|
||||||
expect(described_class).to receive(:dev_integration_names).and_call_original
|
|
||||||
expect(described_class).to receive(:project_specific_integration_names).and_call_original
|
|
||||||
|
|
||||||
described_class.available_integration_names
|
before do
|
||||||
|
allow(described_class).to receive(:integration_names).and_return(%w(foo))
|
||||||
|
allow(described_class).to receive(:project_specific_integration_names).and_return(['bar'])
|
||||||
|
allow(described_class).to receive(:dev_integration_names).and_return(['baz'])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not call project_specific_integration_names with include_project_specific false' do
|
it { is_expected.to include('foo', 'bar', 'baz') }
|
||||||
expect(described_class).to receive(:integration_names).and_call_original
|
|
||||||
expect(described_class).to receive(:dev_integration_names).and_call_original
|
|
||||||
expect(described_class).not_to receive(:project_specific_integration_names)
|
|
||||||
|
|
||||||
described_class.available_integration_names(include_project_specific: false)
|
context 'when `include_project_specific` is false' do
|
||||||
|
subject { described_class.available_integration_names(include_project_specific: false) }
|
||||||
|
|
||||||
|
it { is_expected.to include('foo', 'baz') }
|
||||||
|
it { is_expected.not_to include('bar') }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not call dev_integration_names with include_dev false' do
|
context 'when `include_dev` is false' do
|
||||||
expect(described_class).to receive(:integration_names).and_call_original
|
subject { described_class.available_integration_names(include_dev: false) }
|
||||||
expect(described_class).not_to receive(:dev_integration_names)
|
|
||||||
expect(described_class).to receive(:project_specific_integration_names).and_call_original
|
|
||||||
|
|
||||||
described_class.available_integration_names(include_dev: false)
|
it { is_expected.to include('foo', 'bar') }
|
||||||
|
it { is_expected.not_to include('baz') }
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(described_class.available_integration_names).to include('jenkins') }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.project_specific_integration_names' do
|
describe '.project_specific_integration_names' do
|
||||||
it do
|
specify do
|
||||||
expect(described_class.project_specific_integration_names)
|
expect(described_class.project_specific_integration_names)
|
||||||
.to include(*described_class::PROJECT_SPECIFIC_INTEGRATION_NAMES)
|
.to include(*described_class::PROJECT_SPECIFIC_INTEGRATION_NAMES)
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,14 @@ RSpec.describe API::Integrations do
|
||||||
create(:project, creator_id: user.id, namespace: user.namespace)
|
create(:project, creator_id: user.id, namespace: user.namespace)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The API supports all integrations except the GitLab Slack Application
|
||||||
|
# integration; this integration must be installed via the UI.
|
||||||
|
def self.integration_names
|
||||||
|
names = Integration.available_integration_names
|
||||||
|
names.delete(Integrations::GitlabSlackApplication.to_param) if Gitlab.ee?
|
||||||
|
names
|
||||||
|
end
|
||||||
|
|
||||||
%w[integrations services].each do |endpoint|
|
%w[integrations services].each do |endpoint|
|
||||||
describe "GET /projects/:id/#{endpoint}" do
|
describe "GET /projects/:id/#{endpoint}" do
|
||||||
it 'returns authentication error when unauthenticated' do
|
it 'returns authentication error when unauthenticated' do
|
||||||
|
@ -43,7 +51,7 @@ RSpec.describe API::Integrations do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Integration.available_integration_names.each do |integration|
|
integration_names.each do |integration|
|
||||||
describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
|
describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
|
||||||
include_context integration
|
include_context integration
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
# The AnonymousController doesn't support setting the CSP
|
||||||
|
# This is why an arbitrary test request was chosen instead
|
||||||
|
# of testing in application_controller_spec.
|
||||||
|
RSpec.describe 'Content Security Policy' do
|
||||||
|
let(:snowplow_host) { 'snowplow.example.com' }
|
||||||
|
|
||||||
|
shared_examples 'snowplow is not in the CSP' do
|
||||||
|
it 'does not add the snowplow collector hostname to the CSP' do
|
||||||
|
get explore_root_url
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response.headers['Content-Security-Policy']).not_to include(snowplow_host)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET #explore' do
|
||||||
|
context 'snowplow is enabled' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: snowplow_host)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds the snowplow collector hostname to the CSP' do
|
||||||
|
get explore_root_url
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response.headers['Content-Security-Policy']).to include(snowplow_host)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'snowplow is enabled but host is not configured' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(snowplow_enabled: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'snowplow is not in the CSP'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'snowplow is disabled' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(snowplow_enabled: false, snowplow_collector_hostname: snowplow_host)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'snowplow is not in the CSP'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ContentSecurityPolicyHelpers
|
||||||
|
# Expecting 2 calls to current_content_security_policy by default, once for
|
||||||
|
# the call that's being tested and once for the call in ApplicationController
|
||||||
|
def setup_csp_for_controller(controller_class, times = 2)
|
||||||
|
expect_next_instance_of(controller_class) do |controller|
|
||||||
|
expect(controller).to receive(:current_content_security_policy)
|
||||||
|
.and_return(ActionDispatch::ContentSecurityPolicy.new).exactly(times).times
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Expecting 2 calls to current_content_security_policy by default, once for
|
||||||
|
# the call that's being tested and once for the call in ApplicationController
|
||||||
|
def setup_existing_csp_for_controller(controller_class, csp, times = 2)
|
||||||
|
expect_next_instance_of(controller_class) do |controller|
|
||||||
|
expect(controller).to receive(:current_content_security_policy).and_return(csp).exactly(times).times
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue