Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
17f2e5035c
commit
f46d20e508
|
@ -285,7 +285,6 @@ Gitlab/NamespacedClass:
|
||||||
- 'app/models/project_snippet.rb'
|
- 'app/models/project_snippet.rb'
|
||||||
- 'app/models/project_statistics.rb'
|
- 'app/models/project_statistics.rb'
|
||||||
- 'app/models/project_team.rb'
|
- 'app/models/project_team.rb'
|
||||||
- 'app/models/project_tracing_setting.rb'
|
|
||||||
- 'app/models/project_wiki.rb'
|
- 'app/models/project_wiki.rb'
|
||||||
- 'app/models/prometheus_alert.rb'
|
- 'app/models/prometheus_alert.rb'
|
||||||
- 'app/models/prometheus_alert_event.rb'
|
- 'app/models/prometheus_alert_event.rb'
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -407,7 +407,7 @@ group :development, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test, :danger do
|
group :development, :test, :danger do
|
||||||
gem 'gitlab-dangerfiles', '~> 3.3.0', require: false
|
gem 'gitlab-dangerfiles', '~> 3.4.1', require: false
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test, :coverage do
|
group :development, :test, :coverage do
|
||||||
|
|
|
@ -475,7 +475,7 @@ GEM
|
||||||
terminal-table (~> 1.5, >= 1.5.1)
|
terminal-table (~> 1.5, >= 1.5.1)
|
||||||
gitlab-chronic (0.10.5)
|
gitlab-chronic (0.10.5)
|
||||||
numerizer (~> 0.2)
|
numerizer (~> 0.2)
|
||||||
gitlab-dangerfiles (3.3.0)
|
gitlab-dangerfiles (3.4.1)
|
||||||
danger (>= 8.4.5)
|
danger (>= 8.4.5)
|
||||||
danger-gitlab (>= 8.0.0)
|
danger-gitlab (>= 8.0.0)
|
||||||
rake
|
rake
|
||||||
|
@ -1534,7 +1534,7 @@ DEPENDENCIES
|
||||||
gitaly (~> 15.1.0.pre.rc1)
|
gitaly (~> 15.1.0.pre.rc1)
|
||||||
github-markup (~> 1.7.0)
|
github-markup (~> 1.7.0)
|
||||||
gitlab-chronic (~> 0.10.5)
|
gitlab-chronic (~> 0.10.5)
|
||||||
gitlab-dangerfiles (~> 3.3.0)
|
gitlab-dangerfiles (~> 3.4.1)
|
||||||
gitlab-experiment (~> 0.7.1)
|
gitlab-experiment (~> 0.7.1)
|
||||||
gitlab-fog-azure-rm (~> 1.3.0)
|
gitlab-fog-azure-rm (~> 1.3.0)
|
||||||
gitlab-labkit (~> 0.23.0)
|
gitlab-labkit (~> 0.23.0)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlSafeHtmlDirective, GlBadge } from '@gitlab/ui';
|
import { GlButton, GlSafeHtmlDirective, GlBadge } from '@gitlab/ui';
|
||||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||||
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
import NoteableNote from '~/notes/components/noteable_note.vue';
|
import NoteableNote from '~/notes/components/noteable_note.vue';
|
||||||
import PublishButton from './publish_button.vue';
|
import PublishButton from './publish_button.vue';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
SafeHtml: GlSafeHtmlDirective,
|
SafeHtml: GlSafeHtmlDirective,
|
||||||
},
|
},
|
||||||
|
mixins: [glFeatureFlagMixin()],
|
||||||
props: {
|
props: {
|
||||||
draft: {
|
draft: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -92,6 +94,7 @@ export default {
|
||||||
:note="draft"
|
:note="draft"
|
||||||
:line="line"
|
:line="line"
|
||||||
:discussion-root="true"
|
:discussion-root="true"
|
||||||
|
:class="{ 'gl-mb-0!': glFeatures.mrReviewSubmitComment }"
|
||||||
class="draft-note"
|
class="draft-note"
|
||||||
@handleEdit="handleEditing"
|
@handleEdit="handleEditing"
|
||||||
@cancelForm="handleNotEditing"
|
@cancelForm="handleNotEditing"
|
||||||
|
@ -113,7 +116,11 @@ export default {
|
||||||
class="referenced-commands draft-note-commands"
|
class="referenced-commands draft-note-commands"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<p class="draft-note-actions d-flex" data-qa-selector="draft_note_content">
|
<p
|
||||||
|
v-if="!glFeatures.mrReviewSubmitComment"
|
||||||
|
class="draft-note-actions d-flex"
|
||||||
|
data-qa-selector="draft_note_content"
|
||||||
|
>
|
||||||
<publish-button
|
<publish-button
|
||||||
:show-count="true"
|
:show-count="true"
|
||||||
:should-publish="false"
|
:should-publish="false"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { GlToggle, GlSprintf, GlLink } from '@gitlab/ui';
|
import { GlToggle, GlSprintf, GlLink } from '@gitlab/ui';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
||||||
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
|
|
||||||
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
|
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
|
||||||
import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql';
|
import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql';
|
||||||
import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
|
import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
|
||||||
|
@ -13,6 +12,7 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEPENDENCY_PROXY_HEADER,
|
DEPENDENCY_PROXY_HEADER,
|
||||||
|
DEPENDENCY_PROXY_DESCRIPTION,
|
||||||
DEPENDENCY_PROXY_DOCS_PATH,
|
DEPENDENCY_PROXY_DOCS_PATH,
|
||||||
} from '~/packages_and_registries/settings/group/constants';
|
} from '~/packages_and_registries/settings/group/constants';
|
||||||
|
|
||||||
|
@ -23,15 +23,14 @@ export default {
|
||||||
GlSprintf,
|
GlSprintf,
|
||||||
GlLink,
|
GlLink,
|
||||||
SettingsBlock,
|
SettingsBlock,
|
||||||
SettingsTitles,
|
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
DEPENDENCY_PROXY_HEADER,
|
DEPENDENCY_PROXY_HEADER,
|
||||||
|
DEPENDENCY_PROXY_DESCRIPTION,
|
||||||
enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'),
|
enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'),
|
||||||
enabledProxyHelpText: s__(
|
enabledProxyHelpText: s__(
|
||||||
'DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}',
|
'DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}',
|
||||||
),
|
),
|
||||||
storageSettingsTitle: s__('DependencyProxy|Storage settings'),
|
|
||||||
ttlPolicyEnabledLabel: s__('DependencyProxy|Clear the Dependency Proxy cache automatically'),
|
ttlPolicyEnabledLabel: s__('DependencyProxy|Clear the Dependency Proxy cache automatically'),
|
||||||
ttlPolicyEnabledHelpText: s__(
|
ttlPolicyEnabledHelpText: s__(
|
||||||
'DependencyProxy|When enabled, images older than 90 days will be removed from the cache.',
|
'DependencyProxy|When enabled, images older than 90 days will be removed from the cache.',
|
||||||
|
@ -135,6 +134,7 @@ export default {
|
||||||
data-qa-selector="dependency_proxy_settings_content"
|
data-qa-selector="dependency_proxy_settings_content"
|
||||||
>
|
>
|
||||||
<template #title> {{ $options.i18n.DEPENDENCY_PROXY_HEADER }} </template>
|
<template #title> {{ $options.i18n.DEPENDENCY_PROXY_HEADER }} </template>
|
||||||
|
<template #description> {{ $options.i18n.DEPENDENCY_PROXY_DESCRIPTION }} </template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div>
|
<div>
|
||||||
<gl-toggle
|
<gl-toggle
|
||||||
|
@ -156,13 +156,12 @@ export default {
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</gl-toggle>
|
</gl-toggle>
|
||||||
|
|
||||||
<settings-titles :title="$options.i18n.storageSettingsTitle" class="gl-my-6" />
|
|
||||||
<gl-toggle
|
<gl-toggle
|
||||||
v-model="ttlEnabled"
|
v-model="ttlEnabled"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
:label="$options.i18n.ttlPolicyEnabledLabel"
|
:label="$options.i18n.ttlPolicyEnabledLabel"
|
||||||
:help="$options.i18n.ttlPolicyEnabledHelpText"
|
:help="$options.i18n.ttlPolicyEnabledHelpText"
|
||||||
|
class="gl-mt-6"
|
||||||
data-testid="dependency-proxy-ttl-policies-toggle"
|
data-testid="dependency-proxy-ttl-policies-toggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,9 @@ export const DUPLICATES_SETTINGS_EXCEPTION_LEGEND = s__(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const DEPENDENCY_PROXY_HEADER = s__('DependencyProxy|Dependency Proxy');
|
export const DEPENDENCY_PROXY_HEADER = s__('DependencyProxy|Dependency Proxy');
|
||||||
|
export const DEPENDENCY_PROXY_DESCRIPTION = s__(
|
||||||
|
'DependencyProxy|Enable the Dependency Proxy and settings for clearing the cache.',
|
||||||
|
);
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@ export default {
|
||||||
project: DEFAULT_BLOB_INFO.project,
|
project: DEFAULT_BLOB_INFO.project,
|
||||||
gitpodEnabled: DEFAULT_BLOB_INFO.gitpodEnabled,
|
gitpodEnabled: DEFAULT_BLOB_INFO.gitpodEnabled,
|
||||||
currentUser: DEFAULT_BLOB_INFO.currentUser,
|
currentUser: DEFAULT_BLOB_INFO.currentUser,
|
||||||
|
useFallback: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -130,7 +131,7 @@ export default {
|
||||||
},
|
},
|
||||||
shouldLoadLegacyViewer() {
|
shouldLoadLegacyViewer() {
|
||||||
const isTextFile = this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs;
|
const isTextFile = this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs;
|
||||||
return isTextFile || LEGACY_FILE_TYPES.includes(this.blobInfo.fileType);
|
return isTextFile || LEGACY_FILE_TYPES.includes(this.blobInfo.fileType) || this.useFallback;
|
||||||
},
|
},
|
||||||
legacyViewerLoaded() {
|
legacyViewerLoaded() {
|
||||||
return (
|
return (
|
||||||
|
@ -173,6 +174,10 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onError() {
|
||||||
|
this.useFallback = true;
|
||||||
|
this.loadLegacyViewer();
|
||||||
|
},
|
||||||
loadLegacyViewer() {
|
loadLegacyViewer() {
|
||||||
if (this.legacyViewerLoaded) {
|
if (this.legacyViewerLoaded) {
|
||||||
return;
|
return;
|
||||||
|
@ -303,7 +308,7 @@ export default {
|
||||||
:loading="isLoadingLegacyViewer"
|
:loading="isLoadingLegacyViewer"
|
||||||
:data-loading="isRenderingLegacyTextViewer"
|
:data-loading="isRenderingLegacyTextViewer"
|
||||||
/>
|
/>
|
||||||
<component :is="blobViewer" v-else :blob="blobInfo" class="blob-viewer" />
|
<component :is="blobViewer" v-else :blob="blobInfo" class="blob-viewer" @error="onError" />
|
||||||
<code-intelligence
|
<code-intelligence
|
||||||
v-if="blobViewer || legacyViewerLoaded"
|
v-if="blobViewer || legacyViewerLoaded"
|
||||||
:code-navigation-path="blobInfo.codeNavigationPath"
|
:code-navigation-path="blobInfo.codeNavigationPath"
|
||||||
|
|
|
@ -112,6 +112,12 @@ export const ROUGE_TO_HLJS_LANGUAGE_MAP = {
|
||||||
yaml: 'yaml',
|
yaml: 'yaml',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const EVENT_ACTION = 'view_source';
|
||||||
|
|
||||||
|
export const EVENT_LABEL_VIEWER = 'source_viewer';
|
||||||
|
|
||||||
|
export const EVENT_LABEL_FALLBACK = 'legacy_fallback';
|
||||||
|
|
||||||
export const LINES_PER_CHUNK = 70;
|
export const LINES_PER_CHUNK = 70;
|
||||||
|
|
||||||
export const BIDI_CHARS = [
|
export const BIDI_CHARS = [
|
||||||
|
|
|
@ -3,7 +3,14 @@ import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
import eventHub from '~/notes/event_hub';
|
import eventHub from '~/notes/event_hub';
|
||||||
import languageLoader from '~/content_editor/services/highlight_js_language_loader';
|
import languageLoader from '~/content_editor/services/highlight_js_language_loader';
|
||||||
import { ROUGE_TO_HLJS_LANGUAGE_MAP, LINES_PER_CHUNK } from './constants';
|
import Tracking from '~/tracking';
|
||||||
|
import {
|
||||||
|
EVENT_ACTION,
|
||||||
|
EVENT_LABEL_VIEWER,
|
||||||
|
EVENT_LABEL_FALLBACK,
|
||||||
|
ROUGE_TO_HLJS_LANGUAGE_MAP,
|
||||||
|
LINES_PER_CHUNK,
|
||||||
|
} from './constants';
|
||||||
import Chunk from './components/chunk.vue';
|
import Chunk from './components/chunk.vue';
|
||||||
import { registerPlugins } from './plugins/index';
|
import { registerPlugins } from './plugins/index';
|
||||||
|
|
||||||
|
@ -23,6 +30,7 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
SafeHtml: GlSafeHtmlDirective,
|
SafeHtml: GlSafeHtmlDirective,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
blob: {
|
blob: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -49,8 +57,22 @@ export default {
|
||||||
lineNumbers() {
|
lineNumbers() {
|
||||||
return this.splitContent.length;
|
return this.splitContent.length;
|
||||||
},
|
},
|
||||||
|
unsupportedLanguage() {
|
||||||
|
const supportedLanguages = Object.keys(languageLoader);
|
||||||
|
return (
|
||||||
|
!supportedLanguages.includes(this.language) &&
|
||||||
|
!supportedLanguages.includes(this.blob.language)
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
|
this.trackEvent(EVENT_LABEL_VIEWER);
|
||||||
|
|
||||||
|
if (this.unsupportedLanguage) {
|
||||||
|
this.handleUnsupportedLanguage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.generateFirstChunk();
|
this.generateFirstChunk();
|
||||||
this.hljs = await this.loadHighlightJS();
|
this.hljs = await this.loadHighlightJS();
|
||||||
|
|
||||||
|
@ -70,6 +92,13 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
trackEvent(label) {
|
||||||
|
this.track(EVENT_ACTION, { label, property: this.blob.language });
|
||||||
|
},
|
||||||
|
handleUnsupportedLanguage() {
|
||||||
|
this.trackEvent(EVENT_LABEL_FALLBACK);
|
||||||
|
this.$emit('error');
|
||||||
|
},
|
||||||
generateFirstChunk() {
|
generateFirstChunk() {
|
||||||
const lines = this.splitContent.splice(0, LINES_PER_CHUNK);
|
const lines = this.splitContent.splice(0, LINES_PER_CHUNK);
|
||||||
this.firstChunk = this.createChunk(lines);
|
this.firstChunk = this.createChunk(lines);
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default function initWorkItemLinks() {
|
||||||
if (!workItemLinksRoot) {
|
if (!workItemLinksRoot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-new
|
// eslint-disable-next-line no-new
|
||||||
new Vue({
|
new Vue({
|
||||||
el: workItemLinksRoot,
|
el: workItemLinksRoot,
|
||||||
|
@ -27,6 +28,9 @@ export default function initWorkItemLinks() {
|
||||||
components: {
|
components: {
|
||||||
workItemLinks: WorkItemLinks,
|
workItemLinks: WorkItemLinks,
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
projectPath: workItemLinksRoot.dataset.projectPath,
|
||||||
|
},
|
||||||
render: (createElement) =>
|
render: (createElement) =>
|
||||||
createElement('work-item-links', {
|
createElement('work-item-links', {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,23 +1,64 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlForm, GlFormInput, GlButton } from '@gitlab/ui';
|
import { GlForm, GlFormCombobox, GlButton } from '@gitlab/ui';
|
||||||
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import projectWorkItemsQuery from '../../graphql/project_work_items.query.graphql';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GlForm,
|
GlForm,
|
||||||
GlFormInput,
|
GlFormCombobox,
|
||||||
GlButton,
|
GlButton,
|
||||||
},
|
},
|
||||||
|
inject: ['projectPath'],
|
||||||
|
apollo: {
|
||||||
|
availableWorkItems: {
|
||||||
|
query: projectWorkItemsQuery,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
projectPath: this.projectPath,
|
||||||
|
searchTerm: this.search,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
update(data) {
|
||||||
|
return data.workspace.workItems.edges.map((wi) => wi.node);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
relatedWorkItem: '',
|
relatedWorkItem: '',
|
||||||
|
availableWorkItems: [],
|
||||||
|
search: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
getIdFromGraphQLId,
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
inputLabel: __('Children'),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<gl-form @submit.prevent>
|
<gl-form @submit.prevent>
|
||||||
<gl-form-input v-model="relatedWorkItem" class="gl-mb-4" />
|
<gl-form-combobox
|
||||||
|
v-model="search"
|
||||||
|
:token-list="availableWorkItems"
|
||||||
|
match-value-to-attr="title"
|
||||||
|
class="gl-mb-4"
|
||||||
|
:label-text="$options.i18n.inputLabel"
|
||||||
|
label-sr-only
|
||||||
|
autofocus
|
||||||
|
>
|
||||||
|
<template #result="{ item }">
|
||||||
|
<div class="gl-display-flex">
|
||||||
|
<div class="gl-text-gray-400 gl-mr-4">{{ getIdFromGraphQLId(item.id) }}</div>
|
||||||
|
<div>{{ item.title }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</gl-form-combobox>
|
||||||
<gl-button type="submit" category="secondary" variant="confirm">
|
<gl-button type="submit" category="secondary" variant="confirm">
|
||||||
{{ s__('WorkItem|Add') }}
|
{{ s__('WorkItem|Add') }}
|
||||||
</gl-button>
|
</gl-button>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
query projectWorkItems($searchTerm: String, $projectPath: ID!) {
|
||||||
|
workspace: project(fullPath: $projectPath) {
|
||||||
|
id
|
||||||
|
workItems(search: $searchTerm) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Projects
|
|
||||||
class TracingsController < Projects::ApplicationController
|
|
||||||
content_security_policy do |p|
|
|
||||||
next if p.directives.blank?
|
|
||||||
|
|
||||||
global_frame_src = p.frame_src
|
|
||||||
|
|
||||||
p.frame_src -> { frame_src_csp_policy(global_frame_src) }
|
|
||||||
end
|
|
||||||
|
|
||||||
before_action :authorize_update_environment!
|
|
||||||
|
|
||||||
feature_category :tracing
|
|
||||||
urgency :low
|
|
||||||
|
|
||||||
def show
|
|
||||||
render_404 unless Feature.enabled?(:monitor_tracing, @project)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def frame_src_csp_policy(global_frame_src)
|
|
||||||
external_url = @project&.tracing_setting&.external_url
|
|
||||||
|
|
||||||
external_url.presence || global_frame_src
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -683,7 +683,6 @@ module ProjectsHelper
|
||||||
product_analytics
|
product_analytics
|
||||||
metrics_dashboard
|
metrics_dashboard
|
||||||
feature_flags
|
feature_flags
|
||||||
tracings
|
|
||||||
terraform
|
terraform
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -247,7 +247,6 @@ class Project < ApplicationRecord
|
||||||
has_many :export_jobs, class_name: 'ProjectExportJob'
|
has_many :export_jobs, class_name: 'ProjectExportJob'
|
||||||
has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :project
|
has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :project
|
||||||
has_one :project_repository, inverse_of: :project
|
has_one :project_repository, inverse_of: :project
|
||||||
has_one :tracing_setting, class_name: 'ProjectTracingSetting'
|
|
||||||
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
|
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
|
||||||
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
|
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
|
||||||
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
|
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
|
||||||
|
@ -434,7 +433,6 @@ class Project < ApplicationRecord
|
||||||
allow_destroy: true,
|
allow_destroy: true,
|
||||||
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
|
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
|
||||||
|
|
||||||
accepts_nested_attributes_for :tracing_setting, update_only: true, allow_destroy: true
|
|
||||||
accepts_nested_attributes_for :incident_management_setting, update_only: true
|
accepts_nested_attributes_for :incident_management_setting, update_only: true
|
||||||
accepts_nested_attributes_for :error_tracking_setting, update_only: true
|
accepts_nested_attributes_for :error_tracking_setting, update_only: true
|
||||||
accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
|
accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
|
||||||
|
@ -667,7 +665,6 @@ class Project < ApplicationRecord
|
||||||
scope :created_by, -> (user) { where(creator: user) }
|
scope :created_by, -> (user) { where(creator: user) }
|
||||||
scope :imported_from, -> (type) { where(import_type: type) }
|
scope :imported_from, -> (type) { where(import_type: type) }
|
||||||
scope :imported, -> { where.not(import_type: nil) }
|
scope :imported, -> { where.not(import_type: nil) }
|
||||||
scope :with_tracing_enabled, -> { joins(:tracing_setting) }
|
|
||||||
scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) }
|
scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) }
|
||||||
|
|
||||||
scope :with_service_desk_key, -> (key) do
|
scope :with_service_desk_key, -> (key) do
|
||||||
|
@ -2762,10 +2759,6 @@ class Project < ApplicationRecord
|
||||||
instance.token
|
instance.token
|
||||||
end
|
end
|
||||||
|
|
||||||
def tracing_external_url
|
|
||||||
tracing_setting&.external_url
|
|
||||||
end
|
|
||||||
|
|
||||||
override :git_garbage_collect_worker_klass
|
override :git_garbage_collect_worker_klass
|
||||||
def git_garbage_collect_worker_klass
|
def git_garbage_collect_worker_klass
|
||||||
Projects::GitGarbageCollectWorker
|
Projects::GitGarbageCollectWorker
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ProjectTracingSetting < ApplicationRecord
|
|
||||||
belongs_to :project
|
|
||||||
|
|
||||||
validates :external_url, length: { maximum: 255 }, public_url: true
|
|
||||||
|
|
||||||
before_validation :sanitize_external_url
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def sanitize_external_url
|
|
||||||
self.external_url = Rails::Html::FullSanitizer.new.sanitize(self.external_url)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -18,7 +18,6 @@ module Projects
|
||||||
.merge(grafana_integration_params)
|
.merge(grafana_integration_params)
|
||||||
.merge(prometheus_integration_params)
|
.merge(prometheus_integration_params)
|
||||||
.merge(incident_management_setting_params)
|
.merge(incident_management_setting_params)
|
||||||
.merge(tracing_setting_params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def alerting_setting_params
|
def alerting_setting_params
|
||||||
|
@ -131,15 +130,6 @@ module Projects
|
||||||
|
|
||||||
{ incident_management_setting_attributes: attrs }
|
{ incident_management_setting_attributes: attrs }
|
||||||
end
|
end
|
||||||
|
|
||||||
def tracing_setting_params
|
|
||||||
attr = params[:tracing_setting_attributes]
|
|
||||||
return {} unless attr
|
|
||||||
|
|
||||||
destroy = attr[:external_url].blank?
|
|
||||||
|
|
||||||
{ tracing_setting_attributes: attr.merge(_destroy: destroy) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
- if Feature.enabled?(:work_items_hierarchy, @project)
|
- if Feature.enabled?(:work_items_hierarchy, @project)
|
||||||
.js-work-item-links-root{ data: { issuable_id: @issue.id } }
|
.js-work-item-links-root{ data: { issuable_id: @issue.id, project_path: @project.full_path } }
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
= link_to project_settings_operations_path(@project), title: _('Configure Tracing'), class: 'gl-button btn btn-confirm' do
|
|
||||||
= _('Add Jaeger URL')
|
|
|
@ -1,50 +0,0 @@
|
||||||
- @content_class = "limit-container-width" unless fluid_layout
|
|
||||||
- page_title _("Tracing")
|
|
||||||
|
|
||||||
.gl-alert.gl-alert-danger.gl-mb-5
|
|
||||||
- removal_epic_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/7188'
|
|
||||||
- removal_epic_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="gl-link">'.html_safe % { url: removal_epic_link_url }
|
|
||||||
- opstrace_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/6976'
|
|
||||||
- opstrace_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="gl-link">'.html_safe % { url: opstrace_link_url }
|
|
||||||
- link_end = '</a>'.html_safe
|
|
||||||
.gl-alert-container
|
|
||||||
= sprite_icon('error', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
|
||||||
.gl-alert-content
|
|
||||||
.gl-alert-title
|
|
||||||
= s_('Deprecations|Feature deprecation and removal')
|
|
||||||
.gl-alert-body
|
|
||||||
%p
|
|
||||||
= html_escape(s_('Deprecations|The logs and tracing features were deprecated in GitLab 14.7, and are %{removal_link_start} scheduled for removal %{link_end} in GitLab 15.0. For information on a possible replacement, %{opstrace_link_start} learn more about Opstrace %{link_end}.')) % {removal_link_start: removal_epic_link_start, opstrace_link_start: opstrace_link_start, link_end: link_end }
|
|
||||||
|
|
||||||
- if @project.tracing_external_url.present?
|
|
||||||
%h1.page-title.gl-font-size-h-display= _('Tracing')
|
|
||||||
.gl-alert.gl-alert-info.gl-mb-5
|
|
||||||
.gl-alert-container
|
|
||||||
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
|
||||||
.gl-alert-content
|
|
||||||
.gl-alert-body
|
|
||||||
= _("Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse.")
|
|
||||||
- jaeger_link = link_to('Jaeger tracing', 'https://www.jaegertracing.io/', target: "_blank", rel: "noreferrer")
|
|
||||||
%p.light= _("GitLab uses %{jaeger_link} to monitor distributed systems.").html_safe % { jaeger_link: jaeger_link }
|
|
||||||
|
|
||||||
|
|
||||||
.card
|
|
||||||
- iframe_permissions = "allow-forms allow-scripts allow-same-origin allow-popups"
|
|
||||||
%iframe.border-0{ src: sanitize(@project.tracing_external_url, scrubber: Rails::Html::TextOnlyScrubber.new), width: '100%', height: 970, sandbox: iframe_permissions }
|
|
||||||
- else
|
|
||||||
.row.empty-state
|
|
||||||
.col-12
|
|
||||||
.svg-content
|
|
||||||
= image_tag 'illustrations/monitoring/tracing.svg'
|
|
||||||
|
|
||||||
.col-12
|
|
||||||
.text-content
|
|
||||||
%h4.text-left= _('Troubleshoot and monitor your application with tracing')
|
|
||||||
%p
|
|
||||||
- jaeger_help_url = "https://www.jaegertracing.io/docs/getting-started/"
|
|
||||||
- link_start_tag = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: jaeger_help_url }
|
|
||||||
- link_end_tag = "#{sprite_icon('external-link', css_class: 'ml-1 vertical-align-middle')}</a>".html_safe
|
|
||||||
= _('Add a Jaeger URL to replace this page with a link to your Jaeger server. You first need to %{link_start_tag}install Jaeger%{link_end_tag}.').html_safe % { link_start_tag: link_start_tag, link_end_tag: link_end_tag }
|
|
||||||
|
|
||||||
.text-center
|
|
||||||
= render 'tracing_button'
|
|
|
@ -1,5 +1,7 @@
|
||||||
- wiki_page_title @page, @page.persisted? ? _('Edit') : _('New')
|
- wiki_page_title @page, @page.persisted? ? _('Edit') : _('New')
|
||||||
- add_page_specific_style 'page_bundles/wiki'
|
- add_page_specific_style 'page_bundles/wiki'
|
||||||
|
- @gfm_form = true
|
||||||
|
- @noteable_type = 'Wiki'
|
||||||
|
|
||||||
- if @error
|
- if @error
|
||||||
#js-wiki-error{ data: { error: @error, wiki_page_path: wiki_page_path(@wiki, @page) } }
|
#js-wiki-error{ data: { error: @error, wiki_page_path: wiki_page_path(@wiki, @page) } }
|
||||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343448
|
||||||
milestone: '15.1'
|
milestone: '15.1'
|
||||||
type: development
|
type: development
|
||||||
group: group::release
|
group: group::release
|
||||||
default_enabled: false
|
default_enabled: true
|
||||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354784
|
||||||
milestone: '14.5'
|
milestone: '14.5'
|
||||||
type: development
|
type: development
|
||||||
group: group::project management
|
group: group::project management
|
||||||
default_enabled: false
|
default_enabled: true
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: use_click_house_database_for_error_tracking
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90675
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/1728
|
||||||
|
milestone: '15.2'
|
||||||
|
type: development
|
||||||
|
group: group::observability
|
||||||
|
default_enabled: false
|
|
@ -111,6 +111,10 @@ if changes.any?
|
||||||
markdown_row_for_spin(spin.category, spin)
|
markdown_row_for_spin(spin.category, spin)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
roulette.required_approvals.each do |approval|
|
||||||
|
rows << markdown_row_for_spin(approval.category, approval.spin)
|
||||||
|
end
|
||||||
|
|
||||||
markdown(REVIEW_ROULETTE_SECTION)
|
markdown(REVIEW_ROULETTE_SECTION)
|
||||||
|
|
||||||
if rows.empty?
|
if rows.empty?
|
||||||
|
|
|
@ -334,14 +334,15 @@ Example response:
|
||||||
## Authenticate Error Tracking requests
|
## Authenticate Error Tracking requests
|
||||||
|
|
||||||
This endpoint is called by the error tracking Go REST API application to authenticate a project.
|
This endpoint is called by the error tracking Go REST API application to authenticate a project.
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/1693) in GitLab 15.1.
|
||||||
|
|
||||||
| Attribute | Type | Required | Description |
|
| Attribute | Type | Required | Description |
|
||||||
|:-------------|:--------|:---------|:-------------------------------------------------------------------|
|
|:-------------|:--------|:---------|:-------------------------------------------------------------------|
|
||||||
| `project_id` | integer | yes | The ID of the project which has the associated key. |
|
| `project_id` | integer | yes | The ID of the project which has the associated key. |
|
||||||
| `public_key` | string | yes | The public key generated by the integrated error tracking feature. |
|
| `public_key` | string | yes | The [public key](../../api/error_tracking.md#error-tracking-client-keys) generated by the integrated Error Tracking feature. |
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
POST /internal/error_tracking_allowed
|
POST /internal/error_tracking/allowed
|
||||||
```
|
```
|
||||||
|
|
||||||
Example request:
|
Example request:
|
||||||
|
@ -349,7 +350,7 @@ Example request:
|
||||||
```shell
|
```shell
|
||||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||||
--data "project_id=111&public_key=generated-error-tracking-key" \
|
--data "project_id=111&public_key=generated-error-tracking-key" \
|
||||||
"http://localhost:3001/api/v4/internal/error_tracking_allowed"
|
"http://localhost:3001/api/v4/internal/error_tracking/allowed"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example response:
|
Example response:
|
||||||
|
|
|
@ -292,7 +292,7 @@ fail.
|
||||||
### Troubleshooting `rspec:undercoverage` failures
|
### Troubleshooting `rspec:undercoverage` failures
|
||||||
|
|
||||||
The `rspec:undercoverage` job has [known bugs](https://gitlab.com/groups/gitlab-org/-/epics/8254)
|
The `rspec:undercoverage` job has [known bugs](https://gitlab.com/groups/gitlab-org/-/epics/8254)
|
||||||
that can cause false positive failures. You can locally test coverage locally to determine if it's
|
that can cause false positive failures. You can test coverage locally to determine if it's
|
||||||
safe to apply `~"pipeline:skip-undercoverage"`. For example, using `<spec>` as the name of the
|
safe to apply `~"pipeline:skip-undercoverage"`. For example, using `<spec>` as the name of the
|
||||||
test causing the failure:
|
test causing the failure:
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,8 @@ You can use a GitLab-managed Terraform state backend as a
|
||||||
a [Personal Access Token](../../profile/personal_access_tokens.md) for
|
a [Personal Access Token](../../profile/personal_access_tokens.md) for
|
||||||
authentication, this value is your GitLab username. If you are using GitLab CI/CD, this value is `'gitlab-ci-token'`.
|
authentication, this value is your GitLab username. If you are using GitLab CI/CD, this value is `'gitlab-ci-token'`.
|
||||||
- **password**: The password to authenticate with the data source. If you are using a Personal Access Token for
|
- **password**: The password to authenticate with the data source. If you are using a Personal Access Token for
|
||||||
authentication, this value is the token value. If you are using GitLab CI/CD, this value is the contents of the `${CI_JOB_TOKEN}` CI/CD variable.
|
authentication, this value is the token value (the token must have the **API** scope).
|
||||||
|
If you are using GitLab CI/CD, this value is the contents of the `${CI_JOB_TOKEN}` CI/CD variable.
|
||||||
|
|
||||||
Outputs from the data source can now be referenced in your Terraform resources
|
Outputs from the data source can now be referenced in your Terraform resources
|
||||||
using `data.terraform_remote_state.example.outputs.<OUTPUT-NAME>`.
|
using `data.terraform_remote_state.example.outputs.<OUTPUT-NAME>`.
|
||||||
|
|
|
@ -8,6 +8,11 @@ module API
|
||||||
|
|
||||||
expose :alert_status
|
expose :alert_status
|
||||||
expose :disabled_until
|
expose :disabled_until
|
||||||
|
expose :url_variables
|
||||||
|
|
||||||
|
def url_variables
|
||||||
|
object.url_variables.keys.map { { key: _1 } }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -476,9 +476,9 @@ module API
|
||||||
render_api_error!('202 Accepted', 202)
|
render_api_error!('202 Accepted', 202)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_validation_error!(model)
|
def render_validation_error!(model, status = 400)
|
||||||
if model.errors.any?
|
if model.errors.any?
|
||||||
render_api_error!(model_error_messages(model) || '400 Bad Request', 400)
|
render_api_error!(model_error_messages(model) || '400 Bad Request', status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module API
|
||||||
|
module Helpers
|
||||||
|
module WebHooksHelpers
|
||||||
|
extend Grape::API::Helpers
|
||||||
|
|
||||||
|
params :requires_url do
|
||||||
|
requires :url, type: String, desc: "The URL to send the request to"
|
||||||
|
end
|
||||||
|
|
||||||
|
params :optional_url do
|
||||||
|
optional :url, type: String, desc: "The URL to send the request to"
|
||||||
|
end
|
||||||
|
|
||||||
|
params :url_variables do
|
||||||
|
optional :url_variables, type: Array, desc: 'URL variables for interpolation' do
|
||||||
|
requires :key, type: String, desc: 'Name of the variable'
|
||||||
|
requires :value, type: String, desc: 'Value of the variable'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_hook
|
||||||
|
hook_scope.find(params.delete(:hook_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_hook_params
|
||||||
|
hook_params = declared_params(include_missing: false)
|
||||||
|
url_variables = hook_params.delete(:url_variables)
|
||||||
|
|
||||||
|
if url_variables.present?
|
||||||
|
hook_params[:url_variables] = url_variables.to_h { [_1[:key], _1[:value]] }
|
||||||
|
end
|
||||||
|
|
||||||
|
hook_params
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_hook(entity:)
|
||||||
|
hook = find_hook
|
||||||
|
update_params = update_hook_params(hook)
|
||||||
|
|
||||||
|
hook.assign_attributes(update_params)
|
||||||
|
|
||||||
|
save_hook(hook, entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_hook_params(hook)
|
||||||
|
update_params = declared_params(include_missing: false)
|
||||||
|
url_variables = update_params.delete(:url_variables) || []
|
||||||
|
url_variables = url_variables.to_h { [_1[:key], _1[:value]] }
|
||||||
|
update_params[:url_variables] = hook.url_variables.merge(url_variables) if url_variables.present?
|
||||||
|
|
||||||
|
error!('No parameters provided', :bad_request) if update_params.empty?
|
||||||
|
|
||||||
|
update_params
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_hook(hook, entity)
|
||||||
|
if hook.save
|
||||||
|
present hook, with: entity
|
||||||
|
else
|
||||||
|
error!("Invalid url given", 422) if hook.errors[:url].present?
|
||||||
|
error!("Invalid branch filter given", 422) if hook.errors[:push_events_branch_filter].present?
|
||||||
|
|
||||||
|
render_validation_error!(hook, 422)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module API
|
||||||
|
module Hooks
|
||||||
|
# It is important that this re-usable module is not a Grape Instance,
|
||||||
|
# since it will be re-mounted.
|
||||||
|
# rubocop: disable API/Base
|
||||||
|
class Test < ::Grape::API
|
||||||
|
params do
|
||||||
|
requires :hook_id, type: Integer, desc: 'The ID of the hook'
|
||||||
|
end
|
||||||
|
post ":hook_id" do
|
||||||
|
hook = find_hook
|
||||||
|
data = configuration[:data].dup
|
||||||
|
hook.execute(data, configuration[:kind])
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop: enable API/Base
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module API
|
||||||
|
module Hooks
|
||||||
|
# It is important that this re-usable module is not a Grape Instance,
|
||||||
|
# since it will be re-mounted.
|
||||||
|
# rubocop: disable API/Base
|
||||||
|
class UrlVariables < ::Grape::API
|
||||||
|
params do
|
||||||
|
requires :hook_id, type: Integer, desc: 'The ID of the hook'
|
||||||
|
requires :key, type: String, desc: 'The key of the variable'
|
||||||
|
end
|
||||||
|
namespace ':hook_id/url_variables' do
|
||||||
|
desc 'Set a url variable'
|
||||||
|
params do
|
||||||
|
requires :value, type: String, desc: 'The value of the variable'
|
||||||
|
end
|
||||||
|
put ":key" do
|
||||||
|
hook = find_hook
|
||||||
|
key = params.delete(:key)
|
||||||
|
value = params.delete(:value)
|
||||||
|
vars = hook.url_variables.merge(key => value)
|
||||||
|
|
||||||
|
error!('Illegal key or value', 422) unless hook.update(url_variables: vars)
|
||||||
|
|
||||||
|
status :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Un-Set a url variable'
|
||||||
|
delete ":key" do
|
||||||
|
hook = find_hook
|
||||||
|
key = params.delete(:key)
|
||||||
|
not_found!('URL variable') unless hook.url_variables.key?(key)
|
||||||
|
|
||||||
|
vars = hook.url_variables.reject { _1 == key }
|
||||||
|
|
||||||
|
error!('Could not unset variable', 422) unless hook.update(url_variables: vars)
|
||||||
|
|
||||||
|
status :no_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop: enable API/Base
|
||||||
|
end
|
||||||
|
end
|
|
@ -164,13 +164,15 @@ module API
|
||||||
check_allowed(params)
|
check_allowed(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/error_tracking_allowed', feature_category: :error_tracking do
|
post '/error_tracking/allowed', feature_category: :error_tracking do
|
||||||
public_key = params[:public_key]
|
public_key = params[:public_key]
|
||||||
project_id = params[:project_id]
|
project_id = params[:project_id]
|
||||||
|
|
||||||
unprocessable_entity! if public_key.blank? || project_id.blank?
|
unprocessable_entity! if public_key.blank? || project_id.blank?
|
||||||
|
|
||||||
enabled = ::ErrorTracking::ClientKey.enabled_key_for(project_id, public_key).exists?
|
project = Project.find(project_id)
|
||||||
|
enabled = Feature.enabled?(:use_click_house_database_for_error_tracking, project) &&
|
||||||
|
::ErrorTracking::ClientKey.enabled_key_for(project_id, public_key).exists?
|
||||||
|
|
||||||
status 200
|
status 200
|
||||||
{ enabled: enabled }
|
{ enabled: enabled }
|
||||||
|
|
|
@ -9,16 +9,21 @@ module API
|
||||||
|
|
||||||
feature_category :integrations
|
feature_category :integrations
|
||||||
|
|
||||||
|
helpers ::API::Helpers::WebHooksHelpers
|
||||||
|
|
||||||
helpers do
|
helpers do
|
||||||
params :project_hook_properties do
|
def hook_scope
|
||||||
requires :url, type: String, desc: "The URL to send the request to"
|
user_project.hooks
|
||||||
|
end
|
||||||
|
|
||||||
|
params :common_hook_parameters do
|
||||||
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
|
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
|
||||||
optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
|
optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
|
||||||
optional :confidential_issues_events, type: Boolean, desc: "Trigger hook on confidential issues events"
|
optional :confidential_issues_events, type: Boolean, desc: "Trigger hook on confidential issues events"
|
||||||
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
|
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
|
||||||
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
|
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
|
||||||
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
|
optional :note_events, type: Boolean, desc: "Trigger hook on note (comment) events"
|
||||||
optional :confidential_note_events, type: Boolean, desc: "Trigger hook on confidential note(comment) events"
|
optional :confidential_note_events, type: Boolean, desc: "Trigger hook on confidential note (comment) events"
|
||||||
optional :job_events, type: Boolean, desc: "Trigger hook on job events"
|
optional :job_events, type: Boolean, desc: "Trigger hook on job events"
|
||||||
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
|
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
|
||||||
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
|
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
|
||||||
|
@ -27,6 +32,7 @@ module API
|
||||||
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
|
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
|
||||||
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
|
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
|
||||||
optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only"
|
optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only"
|
||||||
|
use :url_variables
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,6 +40,10 @@ module API
|
||||||
requires :id, type: String, desc: 'The ID of a project'
|
requires :id, type: String, desc: 'The ID of a project'
|
||||||
end
|
end
|
||||||
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||||
|
namespace ':id/hooks' do
|
||||||
|
mount ::API::Hooks::UrlVariables
|
||||||
|
end
|
||||||
|
|
||||||
desc 'Get project hooks' do
|
desc 'Get project hooks' do
|
||||||
success Entities::ProjectHook
|
success Entities::ProjectHook
|
||||||
end
|
end
|
||||||
|
@ -59,43 +69,26 @@ module API
|
||||||
success Entities::ProjectHook
|
success Entities::ProjectHook
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
use :project_hook_properties
|
use :requires_url
|
||||||
|
use :common_hook_parameters
|
||||||
end
|
end
|
||||||
post ":id/hooks" do
|
post ":id/hooks" do
|
||||||
hook_params = declared_params(include_missing: false)
|
hook_params = create_hook_params
|
||||||
|
|
||||||
hook = user_project.hooks.new(hook_params)
|
hook = user_project.hooks.new(hook_params)
|
||||||
|
|
||||||
if hook.save
|
save_hook(hook, Entities::ProjectHook)
|
||||||
present hook, with: Entities::ProjectHook
|
|
||||||
else
|
|
||||||
error!("Invalid url given", 422) if hook.errors[:url].present?
|
|
||||||
error!("Invalid branch filter given", 422) if hook.errors[:push_events_branch_filter].present?
|
|
||||||
|
|
||||||
not_found!("Project hook #{hook.errors.messages}")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Update an existing project hook' do
|
desc 'Update an existing hook' do
|
||||||
success Entities::ProjectHook
|
success Entities::ProjectHook
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
requires :hook_id, type: Integer, desc: "The ID of the hook to update"
|
requires :hook_id, type: Integer, desc: "The ID of the hook to update"
|
||||||
use :project_hook_properties
|
use :optional_url
|
||||||
|
use :common_hook_parameters
|
||||||
end
|
end
|
||||||
put ":id/hooks/:hook_id" do
|
put ":id/hooks/:hook_id" do
|
||||||
hook = user_project.hooks.find(params.delete(:hook_id))
|
update_hook(entity: Entities::ProjectHook)
|
||||||
|
|
||||||
update_params = declared_params(include_missing: false)
|
|
||||||
|
|
||||||
if hook.update(update_params)
|
|
||||||
present hook, with: Entities::ProjectHook
|
|
||||||
else
|
|
||||||
error!("Invalid url given", 422) if hook.errors[:url].present?
|
|
||||||
error!("Invalid branch filter given", 422) if hook.errors[:push_events_branch_filter].present?
|
|
||||||
|
|
||||||
not_found!("Project hook #{hook.errors.messages}")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Deletes project hook' do
|
desc 'Deletes project hook' do
|
||||||
|
@ -105,7 +98,7 @@ module API
|
||||||
requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
|
requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
|
||||||
end
|
end
|
||||||
delete ":id/hooks/:hook_id" do
|
delete ":id/hooks/:hook_id" do
|
||||||
hook = user_project.hooks.find(params.delete(:hook_id))
|
hook = find_hook
|
||||||
|
|
||||||
destroy_conditionally!(hook) do
|
destroy_conditionally!(hook) do
|
||||||
WebHooks::DestroyService.new(current_user).execute(hook)
|
WebHooks::DestroyService.new(current_user).execute(hook)
|
||||||
|
|
|
@ -11,7 +11,27 @@ module API
|
||||||
authenticated_as_admin!
|
authenticated_as_admin!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
helpers ::API::Helpers::WebHooksHelpers
|
||||||
|
|
||||||
|
helpers do
|
||||||
|
def hook_scope
|
||||||
|
SystemHook
|
||||||
|
end
|
||||||
|
|
||||||
|
params :hook_parameters do
|
||||||
|
optional :token, type: String, desc: 'The token used to validate payloads'
|
||||||
|
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
|
||||||
|
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
|
||||||
|
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events"
|
||||||
|
optional :repository_update_events, type: Boolean, desc: "Trigger hook on repository update events"
|
||||||
|
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
|
||||||
|
use :url_variables
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resource :hooks do
|
resource :hooks do
|
||||||
|
mount ::API::Hooks::UrlVariables
|
||||||
|
|
||||||
desc 'Get the list of system hooks' do
|
desc 'Get the list of system hooks' do
|
||||||
success Entities::Hook
|
success Entities::Hook
|
||||||
end
|
end
|
||||||
|
@ -26,70 +46,63 @@ module API
|
||||||
success Entities::Hook
|
success Entities::Hook
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
requires :id, type: Integer, desc: 'The ID of the system hook'
|
requires :hook_id, type: Integer, desc: 'The ID of the system hook'
|
||||||
end
|
end
|
||||||
get ":id" do
|
get ":hook_id" do
|
||||||
hook = SystemHook.find(params[:id])
|
present find_hook, with: Entities::Hook
|
||||||
|
|
||||||
present hook, with: Entities::Hook
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Create a new system hook' do
|
desc 'Create a new system hook' do
|
||||||
success Entities::Hook
|
success Entities::Hook
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
requires :url, type: String, desc: "The URL to send the request to"
|
use :requires_url
|
||||||
optional :token, type: String, desc: 'The token used to validate payloads'
|
use :hook_parameters
|
||||||
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
|
|
||||||
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
|
|
||||||
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events"
|
|
||||||
optional :repository_update_events, type: Boolean, desc: "Trigger hook on repository update events"
|
|
||||||
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
|
|
||||||
end
|
end
|
||||||
post do
|
post do
|
||||||
hook = SystemHook.new(declared_params(include_missing: false))
|
hook_params = create_hook_params
|
||||||
|
hook = SystemHook.new(hook_params)
|
||||||
|
|
||||||
if hook.save
|
save_hook(hook, Entities::Hook)
|
||||||
present hook, with: Entities::Hook
|
|
||||||
else
|
|
||||||
render_validation_error!(hook)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Test a hook'
|
desc 'Update an existing system hook' do
|
||||||
|
success Entities::Hook
|
||||||
|
end
|
||||||
params do
|
params do
|
||||||
requires :id, type: Integer, desc: 'The ID of the system hook'
|
requires :hook_id, type: Integer, desc: "The ID of the hook to update"
|
||||||
|
use :optional_url
|
||||||
|
use :hook_parameters
|
||||||
end
|
end
|
||||||
post ":id" do
|
put ":hook_id" do
|
||||||
hook = SystemHook.find(params[:id])
|
update_hook(entity: Entities::Hook)
|
||||||
data = {
|
end
|
||||||
|
|
||||||
|
mount ::API::Hooks::Test, with: {
|
||||||
|
data: {
|
||||||
event_name: "project_create",
|
event_name: "project_create",
|
||||||
name: "Ruby",
|
name: "Ruby",
|
||||||
path: "ruby",
|
path: "ruby",
|
||||||
project_id: 1,
|
project_id: 1,
|
||||||
owner_name: "Someone",
|
owner_name: "Someone",
|
||||||
owner_email: "example@gitlabhq.com"
|
owner_email: "example@gitlabhq.com"
|
||||||
}
|
},
|
||||||
hook.execute(data, 'system_hooks')
|
kind: 'system_hooks'
|
||||||
data
|
}
|
||||||
end
|
|
||||||
|
|
||||||
desc 'Delete a hook' do
|
desc 'Delete a hook' do
|
||||||
success Entities::Hook
|
success Entities::Hook
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
requires :id, type: Integer, desc: 'The ID of the system hook'
|
requires :hook_id, type: Integer, desc: 'The ID of the system hook'
|
||||||
end
|
end
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
delete ":hook_id" do
|
||||||
delete ":id" do
|
hook = find_hook
|
||||||
hook = SystemHook.find_by(id: params[:id])
|
|
||||||
not_found!('System hook') unless hook
|
|
||||||
|
|
||||||
destroy_conditionally!(hook) do
|
destroy_conditionally!(hook) do
|
||||||
WebHooks::DestroyService.new(current_user).execute(hook)
|
WebHooks::DestroyService.new(current_user).execute(hook)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,7 +42,7 @@ module Gitlab
|
||||||
return false if taggings.empty?
|
return false if taggings.empty?
|
||||||
|
|
||||||
taggings.each_slice(TAGGINGS_BATCH_SIZE) do |taggings_slice|
|
taggings.each_slice(TAGGINGS_BATCH_SIZE) do |taggings_slice|
|
||||||
ActsAsTaggableOn::Tagging.insert_all!(taggings)
|
ActsAsTaggableOn::Tagging.insert_all!(taggings_slice)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
|
@ -2043,9 +2043,6 @@ msgstr ""
|
||||||
msgid "Add CONTRIBUTING"
|
msgid "Add CONTRIBUTING"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add Jaeger URL"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Add Kubernetes cluster"
|
msgid "Add Kubernetes cluster"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -2076,9 +2073,6 @@ msgstr ""
|
||||||
msgid "Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
|
msgid "Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add a Jaeger URL to replace this page with a link to your Jaeger server. You first need to %{link_start_tag}install Jaeger%{link_end_tag}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Add a Terms of Service agreement and Privacy Policy for users of this GitLab instance."
|
msgid "Add a Terms of Service agreement and Privacy Policy for users of this GitLab instance."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -7819,6 +7813,9 @@ msgstr ""
|
||||||
msgid "Child issues and epics"
|
msgid "Child issues and epics"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Children"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Chinese language support using"
|
msgid "Chinese language support using"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -9463,9 +9460,6 @@ msgstr ""
|
||||||
msgid "Configure Sentry integration for error tracking"
|
msgid "Configure Sentry integration for error tracking"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Configure Tracing"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Configure a %{codeStart}.gitlab-webide.yml%{codeEnd} file in the %{codeStart}.gitlab%{codeEnd} directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
|
msgid "Configure a %{codeStart}.gitlab-webide.yml%{codeEnd} file in the %{codeStart}.gitlab%{codeEnd} directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -12374,15 +12368,15 @@ msgstr ""
|
||||||
msgid "DependencyProxy|Enable Dependency Proxy"
|
msgid "DependencyProxy|Enable Dependency Proxy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "DependencyProxy|Enable the Dependency Proxy and settings for clearing the cache."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "DependencyProxy|Image list"
|
msgid "DependencyProxy|Image list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DependencyProxy|Scheduled for deletion"
|
msgid "DependencyProxy|Scheduled for deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DependencyProxy|Storage settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "DependencyProxy|There are no images in the cache"
|
msgid "DependencyProxy|There are no images in the cache"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -12802,9 +12796,6 @@ msgstr ""
|
||||||
msgid "Deprecations|The logs and tracing features were deprecated in GitLab 14.7 and are %{epicStart} scheduled for removal %{epicEnd} in GitLab 15.0."
|
msgid "Deprecations|The logs and tracing features were deprecated in GitLab 14.7 and are %{epicStart} scheduled for removal %{epicEnd} in GitLab 15.0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deprecations|The logs and tracing features were deprecated in GitLab 14.7, and are %{removal_link_start} scheduled for removal %{link_end} in GitLab 15.0. For information on a possible replacement, %{opstrace_link_start} learn more about Opstrace %{link_end}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Deprecations|The metrics feature was deprecated in GitLab 14.7."
|
msgid "Deprecations|The metrics feature was deprecated in GitLab 14.7."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -17313,9 +17304,6 @@ msgstr ""
|
||||||
msgid "GitLab username"
|
msgid "GitLab username"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "GitLab uses %{linkStart}Sidekiq%{linkEnd} to process background jobs"
|
msgid "GitLab uses %{linkStart}Sidekiq%{linkEnd} to process background jobs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -40278,9 +40266,6 @@ msgstr ""
|
||||||
msgid "TotalRefCountIndicator|1000+"
|
msgid "TotalRefCountIndicator|1000+"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Tracing"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Track groups of issues that share a theme, across projects and milestones"
|
msgid "Track groups of issues that share a theme, across projects and milestones"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -40503,9 +40488,6 @@ msgstr ""
|
||||||
msgid "Trigger|invalid"
|
msgid "Trigger|invalid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Troubleshoot and monitor your application with tracing"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Trusted"
|
msgid "Trusted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -44471,9 +44453,6 @@ msgstr ""
|
||||||
msgid "Your new comment"
|
msgid "Your new comment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Your password reset token has expired."
|
msgid "Your password reset token has expired."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -74,12 +74,7 @@ module QA
|
||||||
|
|
||||||
def list_of_runners(tag_list: nil)
|
def list_of_runners(tag_list: nil)
|
||||||
url = tag_list ? "#{api_post_path}?tag_list=#{tag_list.compact.join(',')}" : api_post_path
|
url = tag_list ? "#{api_post_path}?tag_list=#{tag_list.compact.join(',')}" : api_post_path
|
||||||
response = get(request_url(url, per_page: '100'))
|
auto_paginated_response(request_url(url))
|
||||||
|
|
||||||
# Capturing 500 error code responses to log this issue better. We can consider cleaning it up once https://gitlab.com/gitlab-org/gitlab/-/issues/331753 is addressed.
|
|
||||||
raise "Response returned a #{response.code} error code. #{response.body}" if response.code == Support::API::HTTP_STATUS_SERVER_ERROR
|
|
||||||
|
|
||||||
parse_body(response)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload!
|
def reload!
|
||||||
|
|
|
@ -263,14 +263,6 @@ module QA
|
||||||
ENV['GITLAB_QA_PASSWORD_6']
|
ENV['GITLAB_QA_PASSWORD_6']
|
||||||
end
|
end
|
||||||
|
|
||||||
def gitlab_qa_2fa_owner_username_1
|
|
||||||
ENV['GITLAB_QA_2FA_OWNER_USERNAME_1'] || 'gitlab-qa-2fa-owner-user1'
|
|
||||||
end
|
|
||||||
|
|
||||||
def gitlab_qa_2fa_owner_password_1
|
|
||||||
ENV['GITLAB_QA_2FA_OWNER_PASSWORD_1']
|
|
||||||
end
|
|
||||||
|
|
||||||
def gitlab_qa_1p_email
|
def gitlab_qa_1p_email
|
||||||
ENV['GITLAB_QA_1P_EMAIL']
|
ENV['GITLAB_QA_1P_EMAIL']
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,10 +4,9 @@ module QA
|
||||||
RSpec.describe 'Manage', :requires_admin, :skip_live_env, :reliable do
|
RSpec.describe 'Manage', :requires_admin, :skip_live_env, :reliable do
|
||||||
describe '2FA' do
|
describe '2FA' do
|
||||||
let(:owner_user) do
|
let(:owner_user) do
|
||||||
Resource::User.fabricate_or_use(
|
Resource::User.fabricate_via_api! do |usr|
|
||||||
Runtime::Env.gitlab_qa_2fa_owner_username_1,
|
usr.api_client = admin_api_client
|
||||||
Runtime::Env.gitlab_qa_2fa_owner_password_1
|
end
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:developer_user) do
|
let(:developer_user) do
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
module QA
|
module QA
|
||||||
RSpec.describe 'Manage', :requires_admin, :skip_live_env do
|
RSpec.describe 'Manage', :requires_admin, :skip_live_env do
|
||||||
describe '2FA' do
|
describe '2FA' do
|
||||||
let(:owner_user) do
|
let!(:owner_user) do
|
||||||
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_2fa_owner_username_1, Runtime::Env.gitlab_qa_2fa_owner_password_1)
|
Resource::User.fabricate_via_api! do |usr|
|
||||||
|
usr.api_client = admin_api_client
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:sandbox_group) do
|
let(:sandbox_group) do
|
||||||
Resource::Sandbox.fabricate! do |sandbox_group|
|
Resource::Sandbox.fabricate! do |sandbox_group|
|
||||||
sandbox_group.path = "gitlab-qa-2fa-sandbox-group"
|
sandbox_group.path = "gitlab-qa-2fa-sandbox-group-#{SecureRandom.hex(8)}"
|
||||||
sandbox_group.api_client = owner_api_client
|
sandbox_group.api_client = owner_api_client
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -304,6 +304,9 @@ function retry_failed_rspec_examples() {
|
||||||
# Disable Crystalball on retry to not overwrite the existing report
|
# Disable Crystalball on retry to not overwrite the existing report
|
||||||
export CRYSTALBALL="false"
|
export CRYSTALBALL="false"
|
||||||
|
|
||||||
|
# Disable simplecov so retried tests don't override test coverage report
|
||||||
|
export SIMPLECOV=0
|
||||||
|
|
||||||
# Retry only the tests that failed on first try
|
# Retry only the tests that failed on first try
|
||||||
rspec_simple_job "--only-failures --pattern \"${KNAPSACK_TEST_FILE_PATTERN}\"" "${JUNIT_RETRY_FILE}"
|
rspec_simple_job "--only-failures --pattern \"${KNAPSACK_TEST_FILE_PATTERN}\"" "${JUNIT_RETRY_FILE}"
|
||||||
rspec_run_status=$?
|
rspec_run_status=$?
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe Projects::TracingsController do
|
|
||||||
let_it_be(:user) { create(:user) }
|
|
||||||
|
|
||||||
describe 'GET show' do
|
|
||||||
shared_examples 'user with read access' do |visibility_level|
|
|
||||||
let(:project) { create(:project, visibility_level) }
|
|
||||||
|
|
||||||
%w[developer maintainer].each do |role|
|
|
||||||
context "with a #{visibility_level} project and #{role} role" do
|
|
||||||
before do
|
|
||||||
project.add_role(user, role)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders OK' do
|
|
||||||
get :show, params: { namespace_id: project.namespace, project_id: project }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(response).to render_template(:show)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'user without read access' do |visibility_level|
|
|
||||||
let(:project) { create(:project, visibility_level) }
|
|
||||||
|
|
||||||
%w[guest reporter].each do |role|
|
|
||||||
context "with a #{visibility_level} project and #{role} role" do
|
|
||||||
before do
|
|
||||||
project.add_role(user, role)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns 404' do
|
|
||||||
get :show, params: { namespace_id: project.namespace, project_id: project }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with maintainer role' do
|
|
||||||
it_behaves_like 'user with read access', :public
|
|
||||||
it_behaves_like 'user with read access', :internal
|
|
||||||
it_behaves_like 'user with read access', :private
|
|
||||||
|
|
||||||
context 'feature flag disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(monitor_tracing: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'user without read access', :public
|
|
||||||
it_behaves_like 'user without read access', :internal
|
|
||||||
it_behaves_like 'user without read access', :private
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without maintainer role' do
|
|
||||||
it_behaves_like 'user without read access', :public
|
|
||||||
it_behaves_like 'user without read access', :internal
|
|
||||||
it_behaves_like 'user without read access', :private
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,8 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
FactoryBot.define do
|
|
||||||
factory :project_tracing_setting do
|
|
||||||
project
|
|
||||||
external_url { 'https://example.com' }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -59,9 +59,6 @@ FactoryBot.define do
|
||||||
create(:alert_management_http_integration, project: projects[0], name: 'DataCat')
|
create(:alert_management_http_integration, project: projects[0], name: 'DataCat')
|
||||||
create(:alert_management_http_integration, :inactive, project: projects[1], name: 'DataFox')
|
create(:alert_management_http_integration, :inactive, project: projects[1], name: 'DataFox')
|
||||||
|
|
||||||
# Tracing
|
|
||||||
create(:project_tracing_setting, project: projects[0])
|
|
||||||
|
|
||||||
# Alert Issues
|
# Alert Issues
|
||||||
create(:alert_management_alert, issue: issues[0], project: projects[0])
|
create(:alert_management_alert, issue: issues[0], project: projects[0])
|
||||||
create(:alert_management_alert, issue: alert_bot_issues[0], project: projects[0])
|
create(:alert_management_alert, issue: alert_bot_issues[0], project: projects[0])
|
||||||
|
|
|
@ -45,18 +45,6 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
||||||
expect(page).to have_selector('.note:not(.draft-note)', text: 'Line is wrong')
|
expect(page).to have_selector('.note:not(.draft-note)', text: 'Line is wrong')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'publishes single comment' do
|
|
||||||
write_diff_comment
|
|
||||||
|
|
||||||
click_button 'Add comment now'
|
|
||||||
|
|
||||||
wait_for_requests
|
|
||||||
|
|
||||||
expect(page).not_to have_selector('.draft-note-component', text: 'Line is wrong')
|
|
||||||
|
|
||||||
expect(page).to have_selector('.note:not(.draft-note)', text: 'Line is wrong')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'deletes draft note' do
|
it 'deletes draft note' do
|
||||||
write_diff_comment
|
write_diff_comment
|
||||||
|
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'Tracings Content Security Policy' do
|
|
||||||
include ContentSecurityPolicyHelpers
|
|
||||||
|
|
||||||
let_it_be(:project) { create(:project) }
|
|
||||||
let_it_be(:user) { create(:user) }
|
|
||||||
|
|
||||||
subject { response_headers['Content-Security-Policy'] }
|
|
||||||
|
|
||||||
before_all do
|
|
||||||
project.add_maintainer(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there is no global config' do
|
|
||||||
before do
|
|
||||||
setup_csp_for_controller(Projects::TracingsController)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not add CSP directives' do
|
|
||||||
visit project_tracing_path(project)
|
|
||||||
|
|
||||||
is_expected.to be_blank
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when a global CSP config exists' do
|
|
||||||
before do
|
|
||||||
csp = ActionDispatch::ContentSecurityPolicy.new do |p|
|
|
||||||
p.frame_src 'https://global-policy.com'
|
|
||||||
end
|
|
||||||
|
|
||||||
setup_existing_csp_for_controller(Projects::TracingsController, csp)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when external_url is set' do
|
|
||||||
let!(:project_tracing_setting) { create(:project_tracing_setting, project: project) }
|
|
||||||
|
|
||||||
it 'overwrites frame-src' do
|
|
||||||
visit project_tracing_path(project)
|
|
||||||
|
|
||||||
is_expected.to eq("frame-src https://example.com")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when external_url is not set' do
|
|
||||||
it 'uses global policy' do
|
|
||||||
visit project_tracing_path(project)
|
|
||||||
|
|
||||||
is_expected.to eq("frame-src https://global-policy.com")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"url",
|
||||||
|
"created_at",
|
||||||
|
"push_events",
|
||||||
|
"push_events_branch_filter",
|
||||||
|
"tag_push_events",
|
||||||
|
"merge_requests_events",
|
||||||
|
"repository_update_events",
|
||||||
|
"enable_ssl_verification",
|
||||||
|
"project_id",
|
||||||
|
"issues_events",
|
||||||
|
"confidential_issues_events",
|
||||||
|
"note_events",
|
||||||
|
"confidential_note_events",
|
||||||
|
"pipeline_events",
|
||||||
|
"wiki_page_events",
|
||||||
|
"job_events",
|
||||||
|
"deployment_events",
|
||||||
|
"releases_events",
|
||||||
|
"alert_status",
|
||||||
|
"disabled_until",
|
||||||
|
"url_variables"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"project_id": { "type": "integer" },
|
||||||
|
"url": { "type": "string" },
|
||||||
|
"created_at": { "type": "string", "format": "date-time" },
|
||||||
|
"push_events": { "type": "boolean" },
|
||||||
|
"push_events_branch_filter": { "type": ["string", "null"] },
|
||||||
|
"tag_push_events": { "type": "boolean" },
|
||||||
|
"merge_requests_events": { "type": "boolean" },
|
||||||
|
"repository_update_events": { "type": "boolean" },
|
||||||
|
"enable_ssl_verification": { "type": "boolean" },
|
||||||
|
"issues_events": { "type": "boolean" },
|
||||||
|
"confidential_issues_events": { "type": ["boolean", "null"] },
|
||||||
|
"note_events": { "type": "boolean" },
|
||||||
|
"confidential_note_events": { "type": ["boolean", "null"] },
|
||||||
|
"pipeline_events": { "type": "boolean" },
|
||||||
|
"wiki_page_events": { "type": "boolean" },
|
||||||
|
"job_events": { "type": "boolean" },
|
||||||
|
"deployment_events": { "type": "boolean" },
|
||||||
|
"releases_events": { "type": "boolean" },
|
||||||
|
"alert_status": { "type": "string", "enum": ["executable","disabled","temporarily_disabled"] },
|
||||||
|
"disabled_until": { "type": ["string", "null"] },
|
||||||
|
"url_variables": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["key"],
|
||||||
|
"properties": {
|
||||||
|
"key": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties" : {
|
||||||
|
"$ref": "./project_hook.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
"repository_update_events",
|
"repository_update_events",
|
||||||
"enable_ssl_verification",
|
"enable_ssl_verification",
|
||||||
"alert_status",
|
"alert_status",
|
||||||
"disabled_until"
|
"disabled_until",
|
||||||
|
"url_variables"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": { "type": "integer" },
|
"id": { "type": "integer" },
|
||||||
|
@ -22,7 +23,18 @@
|
||||||
"repository_update_events": { "type": "boolean" },
|
"repository_update_events": { "type": "boolean" },
|
||||||
"enable_ssl_verification": { "type": "boolean" },
|
"enable_ssl_verification": { "type": "boolean" },
|
||||||
"alert_status": { "type": "string", "enum": ["executable", "disabled", "temporarily_disabled"] },
|
"alert_status": { "type": "string", "enum": ["executable", "disabled", "temporarily_disabled"] },
|
||||||
"disabled_until": { "type": ["string", "null"] }
|
"disabled_until": { "type": ["string", "null"] },
|
||||||
|
"url_variables": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["key"],
|
||||||
|
"properties": {
|
||||||
|
"key": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,16 @@ describe('Batch comments draft note component', () => {
|
||||||
const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
|
const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
|
||||||
const findAddCommentButton = () => wrapper.findComponent(GlButton);
|
const findAddCommentButton = () => wrapper.findComponent(GlButton);
|
||||||
|
|
||||||
const createComponent = (propsData = { draft }) => {
|
const createComponent = (propsData = { draft }, glFeatures = {}) => {
|
||||||
wrapper = shallowMount(DraftNote, {
|
wrapper = shallowMount(DraftNote, {
|
||||||
store,
|
store,
|
||||||
propsData,
|
propsData,
|
||||||
stubs: {
|
stubs: {
|
||||||
NoteableNote: NoteableNoteStub,
|
NoteableNote: NoteableNoteStub,
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
glFeatures,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation();
|
jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation();
|
||||||
|
@ -96,6 +99,12 @@ describe('Batch comments draft note component', () => {
|
||||||
expect(publishNowButton.props().disabled).toBe(true);
|
expect(publishNowButton.props().disabled).toBe(true);
|
||||||
expect(publishNowButton.props().loading).toBe(false);
|
expect(publishNowButton.props().loading).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('hides button when mr_review_submit_comment is enabled', () => {
|
||||||
|
createComponent({ draft }, { mrReviewSubmitComment: true });
|
||||||
|
|
||||||
|
expect(findAddCommentButton().exists()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('submit review', () => {
|
describe('submit review', () => {
|
||||||
|
|
|
@ -6,13 +6,15 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
|
||||||
import component from '~/packages_and_registries/settings/group/components/dependency_proxy_settings.vue';
|
import component from '~/packages_and_registries/settings/group/components/dependency_proxy_settings.vue';
|
||||||
import { DEPENDENCY_PROXY_HEADER } from '~/packages_and_registries/settings/group/constants';
|
import {
|
||||||
|
DEPENDENCY_PROXY_HEADER,
|
||||||
|
DEPENDENCY_PROXY_DESCRIPTION,
|
||||||
|
} from '~/packages_and_registries/settings/group/constants';
|
||||||
|
|
||||||
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
|
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
|
||||||
import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql';
|
import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql';
|
||||||
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
|
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
|
||||||
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
||||||
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
|
|
||||||
import {
|
import {
|
||||||
updateGroupDependencyProxySettingsOptimisticResponse,
|
updateGroupDependencyProxySettingsOptimisticResponse,
|
||||||
updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
|
updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
|
||||||
|
@ -86,7 +88,6 @@ describe('DependencyProxySettings', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
|
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
|
||||||
const findSettingsTitles = () => wrapper.findComponent(SettingsTitles);
|
|
||||||
const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle');
|
const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle');
|
||||||
const findEnableTtlPoliciesToggle = () =>
|
const findEnableTtlPoliciesToggle = () =>
|
||||||
wrapper.findByTestId('dependency-proxy-ttl-policies-toggle');
|
wrapper.findByTestId('dependency-proxy-ttl-policies-toggle');
|
||||||
|
@ -114,10 +115,11 @@ describe('DependencyProxySettings', () => {
|
||||||
expect(findSettingsBlock().props('defaultExpanded')).toBe(false);
|
expect(findSettingsBlock().props('defaultExpanded')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has the correct header text', () => {
|
it('has the correct header text and description', () => {
|
||||||
mountComponent();
|
mountComponent();
|
||||||
|
|
||||||
expect(wrapper.text()).toContain(DEPENDENCY_PROXY_HEADER);
|
expect(wrapper.text()).toContain(DEPENDENCY_PROXY_HEADER);
|
||||||
|
expect(wrapper.text()).toContain(DEPENDENCY_PROXY_DESCRIPTION);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('enable toggle', () => {
|
describe('enable toggle', () => {
|
||||||
|
@ -158,14 +160,6 @@ describe('DependencyProxySettings', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('storage settings', () => {
|
describe('storage settings', () => {
|
||||||
it('the component has the settings title', () => {
|
|
||||||
mountComponent();
|
|
||||||
|
|
||||||
expect(findSettingsTitles().props()).toMatchObject({
|
|
||||||
title: component.i18n.storageSettingsTitle,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('enable proxy ttl policies', () => {
|
describe('enable proxy ttl policies', () => {
|
||||||
it('exists', () => {
|
it('exists', () => {
|
||||||
mountComponent();
|
mountComponent();
|
||||||
|
|
|
@ -136,6 +136,7 @@ describe('Blob content viewer component', () => {
|
||||||
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
|
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
|
||||||
const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
|
const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
|
||||||
const findCodeIntelligence = () => wrapper.findComponent(CodeIntelligence);
|
const findCodeIntelligence = () => wrapper.findComponent(CodeIntelligence);
|
||||||
|
const findSourceViewer = () => wrapper.findComponent(SourceViewer);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
||||||
|
@ -197,6 +198,16 @@ describe('Blob content viewer component', () => {
|
||||||
expect(mockAxios.history.get[0].url).toBe(legacyViewerUrl);
|
expect(mockAxios.history.get[0].url).toBe(legacyViewerUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('loads a legacy viewer when the source viewer emits an error', async () => {
|
||||||
|
loadViewer.mockReturnValueOnce(SourceViewer);
|
||||||
|
await createComponent();
|
||||||
|
findSourceViewer().vm.$emit('error');
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(mockAxios.history.get).toHaveLength(1);
|
||||||
|
expect(mockAxios.history.get[0].url).toBe(legacyViewerUrl);
|
||||||
|
});
|
||||||
|
|
||||||
it('loads a legacy viewer when a viewer component is not available', async () => {
|
it('loads a legacy viewer when a viewer component is not available', async () => {
|
||||||
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
|
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,16 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
|
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
|
||||||
import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
|
import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
|
||||||
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
||||||
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
|
import {
|
||||||
|
EVENT_ACTION,
|
||||||
|
EVENT_LABEL_VIEWER,
|
||||||
|
EVENT_LABEL_FALLBACK,
|
||||||
|
ROUGE_TO_HLJS_LANGUAGE_MAP,
|
||||||
|
} from '~/vue_shared/components/source_viewer/constants';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
import eventHub from '~/notes/event_hub';
|
import eventHub from '~/notes/event_hub';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
|
|
||||||
jest.mock('~/blob/line_highlighter');
|
jest.mock('~/blob/line_highlighter');
|
||||||
jest.mock('highlight.js/lib/core');
|
jest.mock('highlight.js/lib/core');
|
||||||
|
@ -52,12 +58,33 @@ describe('Source Viewer component', () => {
|
||||||
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
||||||
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
||||||
jest.spyOn(eventHub, '$emit');
|
jest.spyOn(eventHub, '$emit');
|
||||||
|
jest.spyOn(Tracking, 'event');
|
||||||
|
|
||||||
return createComponent();
|
return createComponent();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => wrapper.destroy());
|
afterEach(() => wrapper.destroy());
|
||||||
|
|
||||||
|
describe('event tracking', () => {
|
||||||
|
it('fires a tracking event when the component is created', () => {
|
||||||
|
const eventData = { label: EVENT_LABEL_VIEWER, property: language };
|
||||||
|
expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not emit an error event when the language is supported', () => {
|
||||||
|
expect(wrapper.emitted('error')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fires a tracking event and emits an error when the language is not supported', () => {
|
||||||
|
const unsupportedLanguage = 'apex';
|
||||||
|
const eventData = { label: EVENT_LABEL_FALLBACK, property: unsupportedLanguage };
|
||||||
|
createComponent({ language: unsupportedLanguage });
|
||||||
|
|
||||||
|
expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
|
||||||
|
expect(wrapper.emitted('error')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('highlight.js', () => {
|
describe('highlight.js', () => {
|
||||||
beforeEach(() => createComponent({ language: mappedLanguage }));
|
beforeEach(() => createComponent({ language: mappedLanguage }));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { GlForm, GlFormCombobox } from '@gitlab/ui';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
|
||||||
|
import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
|
||||||
|
import { availableWorkItemsResponse } from '../../mock_data';
|
||||||
|
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
describe('WorkItemLinksForm', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createComponent = async ({ response = availableWorkItemsResponse } = {}) => {
|
||||||
|
wrapper = shallowMountExtended(WorkItemLinksForm, {
|
||||||
|
apolloProvider: createMockApollo([
|
||||||
|
[projectWorkItemsQuery, jest.fn().mockResolvedValue(response)],
|
||||||
|
]),
|
||||||
|
propsData: { issuableId: 1 },
|
||||||
|
provide: {
|
||||||
|
projectPath: 'project/path',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
};
|
||||||
|
|
||||||
|
const findForm = () => wrapper.findComponent(GlForm);
|
||||||
|
const findCombobox = () => wrapper.findComponent(GlFormCombobox);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders form', () => {
|
||||||
|
expect(findForm().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes available work items as prop when typing in combobox', async () => {
|
||||||
|
expect(findCombobox().exists()).toBe(true);
|
||||||
|
expect(findCombobox().props('tokenList').length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -275,3 +275,28 @@ export const workItemHierarchyResponse = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const availableWorkItemsResponse = {
|
||||||
|
data: {
|
||||||
|
workspace: {
|
||||||
|
__typename: 'Project',
|
||||||
|
id: 'gid://gitlab/Project/2',
|
||||||
|
workItems: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'gid://gitlab/WorkItem/458',
|
||||||
|
title: 'Task 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'gid://gitlab/WorkItem/459',
|
||||||
|
title: 'Task 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -44,6 +44,32 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
|
||||||
expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
|
expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
|
||||||
expect(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4])
|
expect(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when batching inserts for tags' do
|
||||||
|
before do
|
||||||
|
stub_const("#{described_class}::TAGS_BATCH_SIZE", 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'inserts tags in batches' do
|
||||||
|
recorder = ActiveRecord::QueryRecorder.new { service.insert! }
|
||||||
|
count = recorder.log.count { |query| query.include?('INSERT INTO "tags"') }
|
||||||
|
|
||||||
|
expect(count).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when batching inserts for taggings' do
|
||||||
|
before do
|
||||||
|
stub_const("#{described_class}::TAGGINGS_BATCH_SIZE", 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'inserts taggings in batches' do
|
||||||
|
recorder = ActiveRecord::QueryRecorder.new { service.insert! }
|
||||||
|
count = recorder.log.count { |query| query.include?('INSERT INTO "taggings"') }
|
||||||
|
|
||||||
|
expect(count).to eq(3)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with tags for only one job' do
|
context 'with tags for only one job' do
|
||||||
|
|
|
@ -557,7 +557,6 @@ project:
|
||||||
- packages
|
- packages
|
||||||
- package_files
|
- package_files
|
||||||
- packages_cleanup_policy
|
- packages_cleanup_policy
|
||||||
- tracing_setting
|
|
||||||
- alerting_setting
|
- alerting_setting
|
||||||
- project_setting
|
- project_setting
|
||||||
- webide_pipelines
|
- webide_pipelines
|
||||||
|
@ -695,8 +694,6 @@ epic_issues:
|
||||||
feature_flag_issues:
|
feature_flag_issues:
|
||||||
- issue
|
- issue
|
||||||
- feature_flag
|
- feature_flag
|
||||||
tracing_setting:
|
|
||||||
- project
|
|
||||||
reviews:
|
reviews:
|
||||||
- project
|
- project
|
||||||
- merge_request
|
- merge_request
|
||||||
|
|
|
@ -564,8 +564,6 @@ Project:
|
||||||
- suggestion_commit_message
|
- suggestion_commit_message
|
||||||
- merge_commit_template
|
- merge_commit_template
|
||||||
- squash_commit_template
|
- squash_commit_template
|
||||||
ProjectTracingSetting:
|
|
||||||
- external_url
|
|
||||||
Author:
|
Author:
|
||||||
- name
|
- name
|
||||||
ProjectFeature:
|
ProjectFeature:
|
||||||
|
|
|
@ -81,7 +81,6 @@ RSpec.describe Project, factory_default: :keep do
|
||||||
it { is_expected.to have_one(:last_event).class_name('Event') }
|
it { is_expected.to have_one(:last_event).class_name('Event') }
|
||||||
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
|
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
|
||||||
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
|
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
|
||||||
it { is_expected.to have_one(:tracing_setting).class_name('ProjectTracingSetting') }
|
|
||||||
it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
|
it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
|
||||||
it { is_expected.to have_one(:project_setting) }
|
it { is_expected.to have_one(:project_setting) }
|
||||||
it { is_expected.to have_one(:alerting_setting).class_name('Alerting::ProjectAlertingSetting') }
|
it { is_expected.to have_one(:alerting_setting).class_name('Alerting::ProjectAlertingSetting') }
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe ProjectTracingSetting do
|
|
||||||
describe '#external_url' do
|
|
||||||
let_it_be(:project) { create(:project) }
|
|
||||||
|
|
||||||
let(:tracing_setting) { project.build_tracing_setting }
|
|
||||||
|
|
||||||
describe 'Validations' do
|
|
||||||
describe 'external_url' do
|
|
||||||
it 'accepts a valid url' do
|
|
||||||
tracing_setting.external_url = 'https://gitlab.com'
|
|
||||||
|
|
||||||
expect(tracing_setting).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'fails with an invalid url' do
|
|
||||||
tracing_setting.external_url = 'gitlab.com'
|
|
||||||
|
|
||||||
expect(tracing_setting).to be_invalid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'fails with a blank string' do
|
|
||||||
tracing_setting.external_url = nil
|
|
||||||
|
|
||||||
expect(tracing_setting).to be_invalid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sanitizes the url' do
|
|
||||||
tracing_setting.external_url = %{https://replaceme.com/'><script>alert(document.cookie)</script>}
|
|
||||||
|
|
||||||
expect(tracing_setting).to be_valid
|
|
||||||
expect(tracing_setting.external_url).to eq(%{https://replaceme.com/'>})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -51,60 +51,93 @@ RSpec.describe API::Internal::Base do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /internal/error_tracking_allowed' do
|
describe 'GET /internal/error_tracking/allowed' do
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
|
|
||||||
let(:params) { { project_id: project.id, public_key: 'key' } }
|
let(:params) { { project_id: project.id, public_key: 'key' } }
|
||||||
|
let(:headers) do
|
||||||
|
{ API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
||||||
|
end
|
||||||
|
|
||||||
|
subject(:send_request) do
|
||||||
|
post api('/internal/error_tracking/allowed'), params: params, headers: headers
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the secret header is missing' do
|
context 'when the secret header is missing' do
|
||||||
|
let(:headers) { {} }
|
||||||
|
|
||||||
it 'responds with unauthorized entity' do
|
it 'responds with unauthorized entity' do
|
||||||
post api("/internal/error_tracking_allowed"), params: params
|
send_request
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when some params are missing' do
|
context 'when some params are missing' do
|
||||||
|
let(:params) { { project_id: project.id } }
|
||||||
|
|
||||||
it 'responds with unprocessable entity' do
|
it 'responds with unprocessable entity' do
|
||||||
post api("/internal/error_tracking_allowed"), params: params.except(:public_key),
|
send_request
|
||||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the error tracking is disabled' do
|
context 'when public_key is unknown' do
|
||||||
it 'returns enabled: false' do
|
it 'returns enabled: false' do
|
||||||
create(:error_tracking_client_key, project: project, active: false)
|
send_request
|
||||||
|
|
||||||
post api("/internal/error_tracking_allowed"), params: params,
|
|
||||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(json_response).to eq({ 'enabled' => false })
|
expect(json_response).to eq('enabled' => false)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the error tracking record does not exist' do
|
context 'when unknown project_id is unknown' do
|
||||||
it 'returns enabled: false' do
|
it 'responds with 404 not found' do
|
||||||
post api("/internal/error_tracking_allowed"), params: params,
|
params[:project_id] = non_existing_record_id
|
||||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
send_request
|
||||||
expect(json_response).to eq({ 'enabled' => false })
|
|
||||||
end
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the error tracking is disabled' do
|
||||||
|
it 'returns enabled: false' do
|
||||||
|
create(:error_tracking_client_key, :disabled, project: project)
|
||||||
|
|
||||||
|
send_request
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response).to eq('enabled' => false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the error tracking is enabled' do
|
context 'when the error tracking is enabled' do
|
||||||
it 'returns enabled: true' do
|
let_it_be(:client_key) { create(:error_tracking_client_key, project: project) }
|
||||||
client_key = create(:error_tracking_client_key, project: project, active: true)
|
|
||||||
params[:public_key] = client_key.public_key
|
|
||||||
|
|
||||||
post api("/internal/error_tracking_allowed"), params: params,
|
before do
|
||||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
params[:public_key] = client_key.public_key
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns enabled: true' do
|
||||||
|
send_request
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(json_response).to eq({ 'enabled' => true })
|
expect(json_response).to eq('enabled' => true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when feature flag use_click_house_database_for_error_tracking is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(use_click_house_database_for_error_tracking: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns enabled: false' do
|
||||||
|
send_request
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response).to eq('enabled' => false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe API::ProjectHooks, 'ProjectHooks' do
|
RSpec.describe API::ProjectHooks, 'ProjectHooks' do
|
||||||
let(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let(:user3) { create(:user) }
|
let_it_be(:user3) { create(:user) }
|
||||||
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
|
let_it_be(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
|
||||||
let!(:hook) do
|
let_it_be_with_refind(:hook) do
|
||||||
create(:project_hook,
|
create(:project_hook,
|
||||||
:all_events_enabled,
|
:all_events_enabled,
|
||||||
project: project,
|
project: project,
|
||||||
|
@ -15,232 +15,55 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
|
||||||
push_events_branch_filter: 'master')
|
push_events_branch_filter: 'master')
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before_all do
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
project.add_developer(user3)
|
project.add_developer(user3)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /projects/:id/hooks" do
|
it_behaves_like 'web-hook API endpoints', '/projects/:id' do
|
||||||
context "authorized user" do
|
let(:unauthorized_user) { user3 }
|
||||||
it "returns project hooks" do
|
|
||||||
get api("/projects/#{project.id}/hooks", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
def scope
|
||||||
expect(json_response).to be_an Array
|
project.hooks
|
||||||
expect(response).to include_pagination_headers
|
|
||||||
expect(json_response.count).to eq(1)
|
|
||||||
expect(json_response.first['url']).to eq("http://example.com")
|
|
||||||
expect(json_response.first['issues_events']).to eq(true)
|
|
||||||
expect(json_response.first['confidential_issues_events']).to eq(true)
|
|
||||||
expect(json_response.first['push_events']).to eq(true)
|
|
||||||
expect(json_response.first['merge_requests_events']).to eq(true)
|
|
||||||
expect(json_response.first['tag_push_events']).to eq(true)
|
|
||||||
expect(json_response.first['note_events']).to eq(true)
|
|
||||||
expect(json_response.first['confidential_note_events']).to eq(true)
|
|
||||||
expect(json_response.first['job_events']).to eq(true)
|
|
||||||
expect(json_response.first['pipeline_events']).to eq(true)
|
|
||||||
expect(json_response.first['wiki_page_events']).to eq(true)
|
|
||||||
expect(json_response.first['deployment_events']).to eq(true)
|
|
||||||
expect(json_response.first['releases_events']).to eq(true)
|
|
||||||
expect(json_response.first['enable_ssl_verification']).to eq(true)
|
|
||||||
expect(json_response.first['push_events_branch_filter']).to eq('master')
|
|
||||||
expect(json_response.first['alert_status']).to eq('executable')
|
|
||||||
expect(json_response.first['disabled_until']).to be_nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "unauthorized user" do
|
def collection_uri
|
||||||
it "does not access project hooks" do
|
"/projects/#{project.id}/hooks"
|
||||||
get api("/projects/#{project.id}/hooks", user3)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:forbidden)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /projects/:id/hooks/:hook_id" do
|
|
||||||
context "authorized user" do
|
|
||||||
it "returns a project hook" do
|
|
||||||
get api("/projects/#{project.id}/hooks/#{hook.id}", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(json_response['url']).to eq(hook.url)
|
|
||||||
expect(json_response['issues_events']).to eq(hook.issues_events)
|
|
||||||
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
|
|
||||||
expect(json_response['push_events']).to eq(hook.push_events)
|
|
||||||
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
|
|
||||||
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
|
|
||||||
expect(json_response['note_events']).to eq(hook.note_events)
|
|
||||||
expect(json_response['confidential_note_events']).to eq(hook.confidential_note_events)
|
|
||||||
expect(json_response['job_events']).to eq(hook.job_events)
|
|
||||||
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
|
|
||||||
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
|
|
||||||
expect(json_response['releases_events']).to eq(hook.releases_events)
|
|
||||||
expect(json_response['deployment_events']).to eq(true)
|
|
||||||
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
|
|
||||||
expect(json_response['alert_status']).to eq(hook.alert_status.to_s)
|
|
||||||
expect(json_response['disabled_until']).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 404 error if hook id is not available" do
|
|
||||||
get api("/projects/#{project.id}/hooks/#{non_existing_record_id}", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "unauthorized user" do
|
def match_collection_schema
|
||||||
it "does not access an existing hook" do
|
match_response_schema('public_api/v4/project_hooks')
|
||||||
get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
|
|
||||||
expect(response).to have_gitlab_http_status(:forbidden)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /projects/:id/hooks" do
|
|
||||||
it "adds hook to project" do
|
|
||||||
expect do
|
|
||||||
post(api("/projects/#{project.id}/hooks", user),
|
|
||||||
params: { url: "http://example.com", issues_events: true,
|
|
||||||
confidential_issues_events: true, wiki_page_events: true,
|
|
||||||
job_events: true, deployment_events: true, releases_events: true,
|
|
||||||
push_events_branch_filter: 'some-feature-branch' })
|
|
||||||
end.to change {project.hooks.count}.by(1)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
expect(json_response['url']).to eq('http://example.com')
|
|
||||||
expect(json_response['issues_events']).to eq(true)
|
|
||||||
expect(json_response['confidential_issues_events']).to eq(true)
|
|
||||||
expect(json_response['push_events']).to eq(true)
|
|
||||||
expect(json_response['merge_requests_events']).to eq(false)
|
|
||||||
expect(json_response['tag_push_events']).to eq(false)
|
|
||||||
expect(json_response['note_events']).to eq(false)
|
|
||||||
expect(json_response['confidential_note_events']).to eq(nil)
|
|
||||||
expect(json_response['job_events']).to eq(true)
|
|
||||||
expect(json_response['pipeline_events']).to eq(false)
|
|
||||||
expect(json_response['wiki_page_events']).to eq(true)
|
|
||||||
expect(json_response['deployment_events']).to eq(true)
|
|
||||||
expect(json_response['releases_events']).to eq(true)
|
|
||||||
expect(json_response['enable_ssl_verification']).to eq(true)
|
|
||||||
expect(json_response['push_events_branch_filter']).to eq('some-feature-branch')
|
|
||||||
expect(json_response).not_to include('token')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "adds the token without including it in the response" do
|
def hook_uri(hook_id = hook.id)
|
||||||
token = "secret token"
|
"/projects/#{project.id}/hooks/#{hook_id}"
|
||||||
|
|
||||||
expect do
|
|
||||||
post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", token: token }
|
|
||||||
end.to change {project.hooks.count}.by(1)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
expect(json_response["url"]).to eq("http://example.com")
|
|
||||||
expect(json_response).not_to include("token")
|
|
||||||
|
|
||||||
hook = project.hooks.find(json_response["id"])
|
|
||||||
|
|
||||||
expect(hook.url).to eq("http://example.com")
|
|
||||||
expect(hook.token).to eq(token)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 400 error if url not given" do
|
def match_hook_schema
|
||||||
post api("/projects/#{project.id}/hooks", user)
|
match_response_schema('public_api/v4/project_hook')
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 422 error if url not valid" do
|
def event_names
|
||||||
post api("/projects/#{project.id}/hooks", user), params: { url: "ftp://example.com" }
|
%i[
|
||||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
push_events
|
||||||
|
tag_push_events
|
||||||
|
merge_requests_events
|
||||||
|
issues_events
|
||||||
|
confidential_issues_events
|
||||||
|
note_events
|
||||||
|
confidential_note_events
|
||||||
|
pipeline_events
|
||||||
|
wiki_page_events
|
||||||
|
job_events
|
||||||
|
deployment_events
|
||||||
|
releases_events
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 422 error if branch filter is not valid" do
|
let(:default_values) do
|
||||||
post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
|
{ push_events: true, confidential_note_events: nil }
|
||||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "PUT /projects/:id/hooks/:hook_id" do
|
|
||||||
it "updates an existing project hook" do
|
|
||||||
put api("/projects/#{project.id}/hooks/#{hook.id}", user),
|
|
||||||
params: { url: 'http://example.org', push_events: false, job_events: true }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(json_response['url']).to eq('http://example.org')
|
|
||||||
expect(json_response['issues_events']).to eq(hook.issues_events)
|
|
||||||
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
|
|
||||||
expect(json_response['push_events']).to eq(false)
|
|
||||||
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
|
|
||||||
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
|
|
||||||
expect(json_response['note_events']).to eq(hook.note_events)
|
|
||||||
expect(json_response['confidential_note_events']).to eq(hook.confidential_note_events)
|
|
||||||
expect(json_response['job_events']).to eq(hook.job_events)
|
|
||||||
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
|
|
||||||
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
|
|
||||||
expect(json_response['releases_events']).to eq(hook.releases_events)
|
|
||||||
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "adds the token without including it in the response" do
|
it_behaves_like 'web-hook API endpoints with branch-filter', '/projects/:id'
|
||||||
token = "secret token"
|
|
||||||
|
|
||||||
put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: "http://example.org", token: token }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(json_response["url"]).to eq("http://example.org")
|
|
||||||
expect(json_response).not_to include("token")
|
|
||||||
|
|
||||||
expect(hook.reload.url).to eq("http://example.org")
|
|
||||||
expect(hook.reload.token).to eq(token)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns 404 error if hook id not found" do
|
|
||||||
put api("/projects/#{project.id}/hooks/#{non_existing_record_id}", user), params: { url: 'http://example.org' }
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns 400 error if url is not given" do
|
|
||||||
put api("/projects/#{project.id}/hooks/#{hook.id}", user)
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 422 error if url is not valid" do
|
|
||||||
put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: 'ftp://example.com' }
|
|
||||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "DELETE /projects/:id/hooks/:hook_id" do
|
|
||||||
it "deletes hook from project" do
|
|
||||||
expect do
|
|
||||||
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:no_content)
|
|
||||||
end.to change {project.hooks.count}.by(-1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 404 error when deleting non existent hook" do
|
|
||||||
delete api("/projects/#{project.id}/hooks/42", user)
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 404 error if hook id not given" do
|
|
||||||
delete api("/projects/#{project.id}/hooks", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 404 if a user attempts to delete project hooks they do not own" do
|
|
||||||
test_user = create(:user)
|
|
||||||
other_project = create(:project)
|
|
||||||
other_project.add_maintainer(test_user)
|
|
||||||
|
|
||||||
delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user)
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
expect(WebHook.exists?(hook.id)).to be_truthy
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like '412 response' do
|
|
||||||
let(:request) { api("/projects/#{project.id}/hooks/#{hook.id}", user) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,221 +3,58 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe API::SystemHooks do
|
RSpec.describe API::SystemHooks do
|
||||||
include StubRequests
|
let_it_be(:non_admin) { create(:user) }
|
||||||
|
let_it_be(:admin) { create(:admin) }
|
||||||
|
let_it_be_with_refind(:hook) { create(:system_hook, url: "http://example.com") }
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
it_behaves_like 'web-hook API endpoints', '' do
|
||||||
let(:admin) { create(:admin) }
|
let(:user) { admin }
|
||||||
let!(:hook) { create(:system_hook, url: "http://example.com") }
|
let(:unauthorized_user) { non_admin }
|
||||||
|
|
||||||
before do
|
def scope
|
||||||
stub_full_request(hook.url, method: :post)
|
SystemHook
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /hooks" do
|
|
||||||
context "when no user" do
|
|
||||||
it "returns authentication error" do
|
|
||||||
get api("/hooks")
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when not an admin" do
|
def collection_uri
|
||||||
it "returns forbidden error" do
|
"/hooks"
|
||||||
get api("/hooks", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:forbidden)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when authenticated as admin" do
|
def match_collection_schema
|
||||||
it "returns an array of hooks" do
|
match_response_schema('public_api/v4/system_hooks')
|
||||||
get api("/hooks", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(response).to include_pagination_headers
|
|
||||||
expect(response).to match_response_schema('public_api/v4/system_hooks')
|
|
||||||
expect(json_response.first).not_to have_key("token")
|
|
||||||
expect(json_response.first['url']).to eq(hook.url)
|
|
||||||
expect(json_response.first['push_events']).to be false
|
|
||||||
expect(json_response.first['tag_push_events']).to be false
|
|
||||||
expect(json_response.first['merge_requests_events']).to be false
|
|
||||||
expect(json_response.first['repository_update_events']).to be true
|
|
||||||
expect(json_response.first['enable_ssl_verification']).to be true
|
|
||||||
expect(json_response.first['disabled_until']).to be nil
|
|
||||||
expect(json_response.first['alert_status']).to eq 'executable'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /hooks/:id" do
|
|
||||||
context "when no user" do
|
|
||||||
it "returns authentication error" do
|
|
||||||
get api("/hooks/#{hook.id}")
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when not an admin" do
|
def hook_uri(hook_id = hook.id)
|
||||||
it "returns forbidden error" do
|
"/hooks/#{hook_id}"
|
||||||
get api("/hooks/#{hook.id}", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:forbidden)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when authenticated as admin" do
|
def match_hook_schema
|
||||||
it "gets a hook", :aggregate_failures do
|
match_response_schema('public_api/v4/system_hook')
|
||||||
get api("/hooks/#{hook.id}", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(response).to match_response_schema('public_api/v4/system_hook')
|
|
||||||
expect(json_response).to match(
|
|
||||||
'id' => be(hook.id),
|
|
||||||
'url' => eq(hook.url),
|
|
||||||
'created_at' => eq(hook.created_at.iso8601(3)),
|
|
||||||
'push_events' => be(hook.push_events),
|
|
||||||
'tag_push_events' => be(hook.tag_push_events),
|
|
||||||
'merge_requests_events' => be(hook.merge_requests_events),
|
|
||||||
'repository_update_events' => be(hook.repository_update_events),
|
|
||||||
'enable_ssl_verification' => be(hook.enable_ssl_verification),
|
|
||||||
'alert_status' => eq(hook.alert_status.to_s),
|
|
||||||
'disabled_until' => eq(hook.disabled_until&.iso8601(3))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'the hook is disabled' do
|
|
||||||
before do
|
|
||||||
hook.disable!
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has the correct alert status", :aggregate_failures do
|
|
||||||
get api("/hooks/#{hook.id}", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(response).to match_response_schema('public_api/v4/system_hook')
|
|
||||||
expect(json_response).to include('alert_status' => 'disabled')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'the hook is backed-off' do
|
|
||||||
before do
|
|
||||||
hook.backoff!
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has the correct alert status", :aggregate_failures do
|
|
||||||
get api("/hooks/#{hook.id}", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(response).to match_response_schema('public_api/v4/system_hook')
|
|
||||||
expect(json_response).to include(
|
|
||||||
'alert_status' => 'temporarily_disabled',
|
|
||||||
'disabled_until' => hook.disabled_until.iso8601(3)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns 404 if the system hook does not exist' do
|
|
||||||
get api("/hooks/#{non_existing_record_id}", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /hooks" do
|
|
||||||
it "creates new hook" do
|
|
||||||
expect do
|
|
||||||
post api("/hooks", admin), params: { url: 'http://example.com' }
|
|
||||||
end.to change { SystemHook.count }.by(1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "responds with 400 if url not given" do
|
def event_names
|
||||||
post api("/hooks", admin)
|
%i[
|
||||||
|
push_events
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
tag_push_events
|
||||||
|
merge_requests_events
|
||||||
|
repository_update_events
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "responds with 400 if url is invalid" do
|
def hook_param_overrides
|
||||||
post api("/hooks", admin), params: { url: 'hp://mep.mep' }
|
{}
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not create new hook without url" do
|
let(:update_params) do
|
||||||
expect do
|
{
|
||||||
post api("/hooks", admin)
|
push_events: false,
|
||||||
end.not_to change { SystemHook.count }
|
tag_push_events: true
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets default values for events' do
|
let(:default_values) do
|
||||||
stub_full_request('http://mep.mep', method: :post)
|
{ repository_update_events: true }
|
||||||
|
|
||||||
post api('/hooks', admin), params: { url: 'http://mep.mep' }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
expect(response).to match_response_schema('public_api/v4/system_hook')
|
|
||||||
expect(json_response['enable_ssl_verification']).to be true
|
|
||||||
expect(json_response['push_events']).to be false
|
|
||||||
expect(json_response['tag_push_events']).to be false
|
|
||||||
expect(json_response['merge_requests_events']).to be false
|
|
||||||
expect(json_response['repository_update_events']).to be true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets explicit values for events' do
|
it_behaves_like 'web-hook API endpoints test hook', ''
|
||||||
stub_full_request('http://mep.mep', method: :post)
|
|
||||||
|
|
||||||
post api('/hooks', admin),
|
|
||||||
params: {
|
|
||||||
url: 'http://mep.mep',
|
|
||||||
enable_ssl_verification: false,
|
|
||||||
push_events: true,
|
|
||||||
tag_push_events: true,
|
|
||||||
merge_requests_events: true,
|
|
||||||
repository_update_events: false
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
expect(response).to match_response_schema('public_api/v4/system_hook')
|
|
||||||
expect(json_response['enable_ssl_verification']).to be false
|
|
||||||
expect(json_response['push_events']).to be true
|
|
||||||
expect(json_response['tag_push_events']).to be true
|
|
||||||
expect(json_response['merge_requests_events']).to be true
|
|
||||||
expect(json_response['repository_update_events']).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'POST /hooks/:id' do
|
|
||||||
it "returns and trigger hook by id" do
|
|
||||||
post api("/hooks/#{hook.id}", admin)
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
expect(json_response['event_name']).to eq('project_create')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns 404 on failure" do
|
|
||||||
post api("/hooks/404", admin)
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "DELETE /hooks/:id" do
|
|
||||||
it "deletes a hook" do
|
|
||||||
expect do
|
|
||||||
delete api("/hooks/#{hook.id}", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:no_content)
|
|
||||||
end.to change { SystemHook.count }.by(-1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns 404 if the system hook does not exist' do
|
|
||||||
delete api("/hooks/#{non_existing_record_id}", admin)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like '412 response' do
|
|
||||||
let(:request) { api("/hooks/#{hook.id}", admin) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -462,93 +462,5 @@ RSpec.describe Projects::Operations::UpdateService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'tracing setting' do
|
|
||||||
context 'with valid params' do
|
|
||||||
let(:params) do
|
|
||||||
{
|
|
||||||
tracing_setting_attributes: {
|
|
||||||
external_url: 'http://some-url.com'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with an existing setting' do
|
|
||||||
before do
|
|
||||||
create(:project_tracing_setting, project: project)
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'setting deletion' do
|
|
||||||
let!(:original_params) { params.deep_dup }
|
|
||||||
|
|
||||||
it 'deletes the setting' do
|
|
||||||
expect(result[:status]).to eq(:success)
|
|
||||||
expect(project.reload.tracing_setting).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not modify original params' do
|
|
||||||
subject.execute
|
|
||||||
|
|
||||||
expect(params).to eq(original_params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates the setting' do
|
|
||||||
expect(project.tracing_setting).not_to be_nil
|
|
||||||
|
|
||||||
expect(result[:status]).to eq(:success)
|
|
||||||
expect(project.reload.tracing_setting.external_url)
|
|
||||||
.to eq('http://some-url.com')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with missing external_url' do
|
|
||||||
before do
|
|
||||||
params[:tracing_setting_attributes].delete(:external_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'setting deletion'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with empty external_url' do
|
|
||||||
before do
|
|
||||||
params[:tracing_setting_attributes][:external_url] = ''
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'setting deletion'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with blank external_url' do
|
|
||||||
before do
|
|
||||||
params[:tracing_setting_attributes][:external_url] = ' '
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'setting deletion'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without an existing setting' do
|
|
||||||
it 'creates a setting' do
|
|
||||||
expect(project.tracing_setting).to be_nil
|
|
||||||
|
|
||||||
expect(result[:status]).to eq(:success)
|
|
||||||
expect(project.reload.tracing_setting.external_url)
|
|
||||||
.to eq('http://some-url.com')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with empty params' do
|
|
||||||
let(:params) { {} }
|
|
||||||
|
|
||||||
let!(:tracing_setting) do
|
|
||||||
create(:project_tracing_setting, project: project)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does nothing' do
|
|
||||||
expect(result[:status]).to eq(:success)
|
|
||||||
expect(project.reload.tracing_setting).to eq(tracing_setting)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ module SimpleCovEnv
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
def start!
|
def start!
|
||||||
return unless ENV['SIMPLECOV']
|
return if !ENV.key?('SIMPLECOV') || ENV['SIMPLECOV'] == '0'
|
||||||
|
|
||||||
configure_profile
|
configure_profile
|
||||||
configure_job
|
configure_job
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_examples 'autocompletes items' do
|
||||||
|
before do
|
||||||
|
if defined?(project)
|
||||||
|
create(:issue, project: project, title: 'My Cool Linked Issue')
|
||||||
|
create(:merge_request, source_project: project, title: 'My Cool Merge Request')
|
||||||
|
create(:label, project: project, title: 'My Cool Label')
|
||||||
|
create(:milestone, project: project, title: 'My Cool Milestone')
|
||||||
|
|
||||||
|
project.add_maintainer(create(:user, name: 'JohnDoe123'))
|
||||||
|
else # group wikis
|
||||||
|
project = create(:project, group: group)
|
||||||
|
|
||||||
|
create(:issue, project: project, title: 'My Cool Linked Issue')
|
||||||
|
create(:merge_request, source_project: project, title: 'My Cool Merge Request')
|
||||||
|
create(:group_label, group: group, title: 'My Cool Label')
|
||||||
|
create(:milestone, group: group, title: 'My Cool Milestone')
|
||||||
|
|
||||||
|
project.add_maintainer(create(:user, name: 'JohnDoe123'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works well for issues, labels, MRs, members, etc' do
|
||||||
|
fill_in :wiki_content, with: "#"
|
||||||
|
expect(page).to have_text 'My Cool Linked Issue'
|
||||||
|
|
||||||
|
fill_in :wiki_content, with: "~"
|
||||||
|
expect(page).to have_text 'My Cool Label'
|
||||||
|
|
||||||
|
fill_in :wiki_content, with: "!"
|
||||||
|
expect(page).to have_text 'My Cool Merge Request'
|
||||||
|
|
||||||
|
fill_in :wiki_content, with: "%"
|
||||||
|
expect(page).to have_text 'My Cool Milestone'
|
||||||
|
|
||||||
|
fill_in :wiki_content, with: "@"
|
||||||
|
expect(page).to have_text 'JohnDoe123'
|
||||||
|
|
||||||
|
fill_in :wiki_content, with: ':smil'
|
||||||
|
expect(page).to have_text 'smile_cat'
|
||||||
|
end
|
||||||
|
end
|
|
@ -146,6 +146,8 @@ RSpec.shared_examples 'User updates wiki page' do
|
||||||
it_behaves_like 'edits content using the content editor'
|
it_behaves_like 'edits content using the content editor'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'autocompletes items'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the page is in a subdir', :js do
|
context 'when the page is in a subdir', :js do
|
||||||
|
|
|
@ -0,0 +1,415 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_examples 'web-hook API endpoints test hook' do |prefix|
|
||||||
|
describe "POST #{prefix}/:hook_id" do
|
||||||
|
it 'tests the hook' do
|
||||||
|
expect(WebHookService)
|
||||||
|
.to receive(:new).with(hook, anything, String, force: false)
|
||||||
|
.and_return(instance_double(WebHookService, execute: nil))
|
||||||
|
|
||||||
|
post api(hook_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'web-hook API endpoints with branch-filter' do |prefix|
|
||||||
|
describe "POST #{prefix}/hooks" do
|
||||||
|
it "returns a 422 error if branch filter is not valid" do
|
||||||
|
post api(collection_uri, user),
|
||||||
|
params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'web-hook API endpoints' do |prefix|
|
||||||
|
def hooks_count
|
||||||
|
scope.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def hook_param_overrides
|
||||||
|
if defined?(super)
|
||||||
|
super
|
||||||
|
else
|
||||||
|
{ push_events_branch_filter: 'some-feature-branch' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:hook_params) do
|
||||||
|
event_names.to_h { [_1, true] }.merge(hook_param_overrides).merge(
|
||||||
|
url: "http://example.com",
|
||||||
|
url_variables: [
|
||||||
|
{ key: 'token', value: 'very-secret' },
|
||||||
|
{ key: 'abc', value: 'other value' }
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:update_params) do
|
||||||
|
{
|
||||||
|
push_events: false,
|
||||||
|
job_events: true,
|
||||||
|
push_events_branch_filter: 'updated-branch-filter'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:default_values) { {} }
|
||||||
|
|
||||||
|
describe "GET #{prefix}/hooks" do
|
||||||
|
context "authorized user" do
|
||||||
|
it "returns all hooks" do
|
||||||
|
get api(collection_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response).to match_collection_schema
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is forbidden" do
|
||||||
|
it "prevents access to hooks" do
|
||||||
|
get api(collection_uri, unauthorized_user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is unauthorized" do
|
||||||
|
it "prevents access to hooks" do
|
||||||
|
get api(collection_uri, nil)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the hook has URL variables' do
|
||||||
|
before do
|
||||||
|
hook.update!(url_variables: { 'token' => 'supers3cret' })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the names of the url variables' do
|
||||||
|
get api(collection_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response).to contain_exactly(
|
||||||
|
a_hash_including(
|
||||||
|
'url_variables' => [{ 'key' => 'token' }]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET #{prefix}/hooks/:hook_id" do
|
||||||
|
context "authorized user" do
|
||||||
|
it "returns a project hook" do
|
||||||
|
get api(hook_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response).to match_hook_schema
|
||||||
|
|
||||||
|
expect(json_response['url']).to eq(hook.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 404 error if hook id is not available" do
|
||||||
|
get api(hook_uri(non_existing_record_id), user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the hook is disabled' do
|
||||||
|
before do
|
||||||
|
hook.disable!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has the correct alert status", :aggregate_failures do
|
||||||
|
get api(hook_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response).to include('alert_status' => 'disabled')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the hook is backed-off' do
|
||||||
|
before do
|
||||||
|
hook.backoff!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has the correct alert status", :aggregate_failures do
|
||||||
|
get api(hook_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response).to include(
|
||||||
|
'alert_status' => 'temporarily_disabled',
|
||||||
|
'disabled_until' => hook.disabled_until.iso8601(3)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is forbidden" do
|
||||||
|
it "does not access an existing hook" do
|
||||||
|
get api(hook_uri, unauthorized_user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is unauthorized" do
|
||||||
|
it "does not access an existing hook" do
|
||||||
|
get api(hook_uri, nil)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "POST #{prefix}/hooks" do
|
||||||
|
let(:hook_creation_params) { hook_params }
|
||||||
|
|
||||||
|
it "adds hook", :aggregate_failures do
|
||||||
|
expect do
|
||||||
|
post api(collection_uri, user),
|
||||||
|
params: hook_creation_params
|
||||||
|
end.to change { hooks_count }.by(1)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(response).to match_hook_schema
|
||||||
|
|
||||||
|
expect(json_response['url']).to eq(hook_creation_params[:url])
|
||||||
|
hook_param_overrides.each do |k, v|
|
||||||
|
expect(json_response[k.to_s]).to eq(v)
|
||||||
|
end
|
||||||
|
event_names.each do |name|
|
||||||
|
expect(json_response[name.to_s]).to eq(true), name
|
||||||
|
end
|
||||||
|
expect(json_response['url_variables']).to match_array [
|
||||||
|
{ 'key' => 'token' },
|
||||||
|
{ 'key' => 'abc' }
|
||||||
|
]
|
||||||
|
expect(json_response).not_to include('token')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds the token without including it in the response" do
|
||||||
|
token = "secret token"
|
||||||
|
|
||||||
|
expect do
|
||||||
|
post api(collection_uri, user),
|
||||||
|
params: { url: "http://example.com", token: token }
|
||||||
|
end.to change { hooks_count }.by(1)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(json_response["url"]).to eq("http://example.com")
|
||||||
|
expect(json_response).not_to include("token")
|
||||||
|
|
||||||
|
hook = scope.find(json_response["id"])
|
||||||
|
|
||||||
|
expect(hook.url).to eq("http://example.com")
|
||||||
|
expect(hook.token).to eq(token)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 400 error if url not given" do
|
||||||
|
post api(collection_uri, user), params: { event_names.first => true }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 400 error if no parameters are provided" do
|
||||||
|
post api(collection_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets default values for events', :aggregate_failures do
|
||||||
|
post api(collection_uri, user), params: { url: 'http://mep.mep' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(response).to match_hook_schema
|
||||||
|
expect(json_response['enable_ssl_verification']).to be true
|
||||||
|
event_names.each do |name|
|
||||||
|
expect(json_response[name.to_s]).to eq(default_values.fetch(name, false)), name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 error if token not valid" do
|
||||||
|
post api(collection_uri, user),
|
||||||
|
params: { url: "http://example.com", token: "foo\nbar" }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 error if url not valid" do
|
||||||
|
post api(collection_uri, user), params: { url: "ftp://example.com" }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "PUT #{prefix}/hooks/:hook_id" do
|
||||||
|
it "updates an existing hook" do
|
||||||
|
put api(hook_uri, user), params: update_params
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response).to match_hook_schema
|
||||||
|
|
||||||
|
update_params.each do |k, v|
|
||||||
|
expect(json_response[k.to_s]).to eq(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the URL variables' do
|
||||||
|
hook.update!(url_variables: { 'abc' => 'some value' })
|
||||||
|
|
||||||
|
put api(hook_uri, user),
|
||||||
|
params: { url_variables: [{ key: 'def', value: 'other value' }] }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response['url_variables']).to match_array [
|
||||||
|
{ 'key' => 'abc' },
|
||||||
|
{ 'key' => 'def' }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds the token without including it in the response" do
|
||||||
|
token = "secret token"
|
||||||
|
|
||||||
|
put api(hook_uri, user), params: { url: "http://example.org", token: token }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response["url"]).to eq("http://example.org")
|
||||||
|
expect(json_response).not_to include("token")
|
||||||
|
|
||||||
|
expect(hook.reload.url).to eq("http://example.org")
|
||||||
|
expect(hook.reload.token).to eq(token)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 404 error if hook id not found" do
|
||||||
|
put api(hook_uri(non_existing_record_id), user), params: { url: 'http://example.org' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 400 error if no parameters are provided" do
|
||||||
|
put api(hook_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 error if url is not valid" do
|
||||||
|
put api(hook_uri, user), params: { url: 'ftp://example.com' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 error if token is not valid" do
|
||||||
|
put api(hook_uri, user), params: { token: %w[foo bar].join("\n") }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "DELETE /projects/:id/hooks/:hook_id" do
|
||||||
|
it "deletes hook from project" do
|
||||||
|
expect do
|
||||||
|
delete api(hook_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
|
end.to change { hooks_count }.by(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 404 error when deleting non existent hook" do
|
||||||
|
delete api(hook_uri(non_existing_record_id), user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 404 error if hook id not given" do
|
||||||
|
delete api(collection_uri, user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns forbidden if a user attempts to delete hooks they do not own" do
|
||||||
|
delete api(hook_uri, unauthorized_user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
expect(WebHook.exists?(hook.id)).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like '412 response' do
|
||||||
|
let(:request) { api(hook_uri, user) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "PUT #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do
|
||||||
|
it 'sets the variable' do
|
||||||
|
expect do
|
||||||
|
put api("#{hook_uri}/url_variables/abc", user),
|
||||||
|
params: { value: 'some secret value' }
|
||||||
|
end.to change { hook.reload.url_variables }.to(eq('abc' => 'some secret value'))
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'overwrites existing values' do
|
||||||
|
hook.update!(url_variables: { 'abc' => 'xyz', 'def' => 'other value' })
|
||||||
|
|
||||||
|
put api("#{hook_uri}/url_variables/abc", user),
|
||||||
|
params: { value: 'some secret value' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
|
expect(hook.reload.url_variables).to eq('abc' => 'some secret value', 'def' => 'other value')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 404 error when editing non existent hook" do
|
||||||
|
put api("#{hook_uri(non_existing_record_id)}/url_variables/abc", user),
|
||||||
|
params: { value: 'xyz' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 error when the key is illegal" do
|
||||||
|
put api("#{hook_uri}/url_variables/abc%20def", user),
|
||||||
|
params: { value: 'xyz' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 422 error when the value is illegal" do
|
||||||
|
put api("#{hook_uri}/url_variables/abc", user),
|
||||||
|
params: { value: '' }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "DELETE #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do
|
||||||
|
before do
|
||||||
|
hook.update!(url_variables: { 'abc' => 'prior value', 'def' => 'other value' })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'unsets the variable' do
|
||||||
|
expect do
|
||||||
|
delete api("#{hook_uri}/url_variables/abc", user)
|
||||||
|
end.to change { hook.reload.url_variables }.to(eq({ 'def' => 'other value' }))
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:no_content)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 404 for keys that do not exist' do
|
||||||
|
hook.update!(url_variables: { 'def' => 'other value' })
|
||||||
|
|
||||||
|
delete api("#{hook_uri}/url_variables/abc", user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 404 error when deleting a variable from a non existent hook" do
|
||||||
|
delete api(hook_uri(non_existing_record_id) + "/url_variables/abc", user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,59 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'projects/tracings/show' do
|
|
||||||
let_it_be_with_reload(:project) { create(:project) }
|
|
||||||
let_it_be(:error_tracking_setting) { create(:project_error_tracking_setting, project: project) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
assign(:project, project)
|
|
||||||
allow(view).to receive(:error_tracking_setting)
|
|
||||||
.and_return(error_tracking_setting)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with project.tracing_external_url' do
|
|
||||||
let_it_be(:tracing_url) { 'https://tracing.url' }
|
|
||||||
let_it_be(:tracing_setting) { create(:project_tracing_setting, project: project, external_url: tracing_url) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(view).to receive(:can?).and_return(true)
|
|
||||||
allow(view).to receive(:tracing_setting).and_return(tracing_setting)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders iframe' do
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to match(/iframe/)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with malicious external_url' do
|
|
||||||
let(:malicious_tracing_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
|
|
||||||
let(:cleaned_url) { "https://replaceme.com/'>" }
|
|
||||||
|
|
||||||
before do
|
|
||||||
tracing_setting.update_column(:external_url, malicious_tracing_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sanitizes external_url' do
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(tracing_setting.external_url).to eq(malicious_tracing_url)
|
|
||||||
expect(rendered).to have_xpath("//iframe[@src=\"#{cleaned_url}\"]")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without project.tracing_external_url' do
|
|
||||||
before do
|
|
||||||
allow(view).to receive(:can?).and_return(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders empty state' do
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to have_link('Add Jaeger URL')
|
|
||||||
expect(rendered).not_to match(/iframe/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -111,6 +111,7 @@ module Tooling
|
||||||
%r{\Atooling/} => :tooling,
|
%r{\Atooling/} => :tooling,
|
||||||
%r{(CODEOWNERS)} => :tooling,
|
%r{(CODEOWNERS)} => :tooling,
|
||||||
%r{(tests.yml)} => :tooling,
|
%r{(tests.yml)} => :tooling,
|
||||||
|
%r{\A\.gitpod\.yml} => :tooling,
|
||||||
|
|
||||||
%r{\Alib/gitlab/ci/templates} => :ci_template,
|
%r{\Alib/gitlab/ci/templates} => :ci_template,
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue