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_statistics.rb'
|
||||
- 'app/models/project_team.rb'
|
||||
- 'app/models/project_tracing_setting.rb'
|
||||
- 'app/models/project_wiki.rb'
|
||||
- 'app/models/prometheus_alert.rb'
|
||||
- 'app/models/prometheus_alert_event.rb'
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -407,7 +407,7 @@ group :development, :test do
|
|||
end
|
||||
|
||||
group :development, :test, :danger do
|
||||
gem 'gitlab-dangerfiles', '~> 3.3.0', require: false
|
||||
gem 'gitlab-dangerfiles', '~> 3.4.1', require: false
|
||||
end
|
||||
|
||||
group :development, :test, :coverage do
|
||||
|
|
|
@ -475,7 +475,7 @@ GEM
|
|||
terminal-table (~> 1.5, >= 1.5.1)
|
||||
gitlab-chronic (0.10.5)
|
||||
numerizer (~> 0.2)
|
||||
gitlab-dangerfiles (3.3.0)
|
||||
gitlab-dangerfiles (3.4.1)
|
||||
danger (>= 8.4.5)
|
||||
danger-gitlab (>= 8.0.0)
|
||||
rake
|
||||
|
@ -1534,7 +1534,7 @@ DEPENDENCIES
|
|||
gitaly (~> 15.1.0.pre.rc1)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 3.3.0)
|
||||
gitlab-dangerfiles (~> 3.4.1)
|
||||
gitlab-experiment (~> 0.7.1)
|
||||
gitlab-fog-azure-rm (~> 1.3.0)
|
||||
gitlab-labkit (~> 0.23.0)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlButton, GlSafeHtmlDirective, GlBadge } from '@gitlab/ui';
|
||||
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 PublishButton from './publish_button.vue';
|
||||
|
||||
|
@ -14,6 +15,7 @@ export default {
|
|||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
draft: {
|
||||
type: Object,
|
||||
|
@ -92,6 +94,7 @@ export default {
|
|||
:note="draft"
|
||||
:line="line"
|
||||
:discussion-root="true"
|
||||
:class="{ 'gl-mb-0!': glFeatures.mrReviewSubmitComment }"
|
||||
class="draft-note"
|
||||
@handleEdit="handleEditing"
|
||||
@cancelForm="handleNotEditing"
|
||||
|
@ -113,7 +116,11 @@ export default {
|
|||
class="referenced-commands draft-note-commands"
|
||||
></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
|
||||
:show-count="true"
|
||||
:should-publish="false"
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { GlToggle, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
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 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';
|
||||
|
@ -13,6 +12,7 @@ import {
|
|||
|
||||
import {
|
||||
DEPENDENCY_PROXY_HEADER,
|
||||
DEPENDENCY_PROXY_DESCRIPTION,
|
||||
DEPENDENCY_PROXY_DOCS_PATH,
|
||||
} from '~/packages_and_registries/settings/group/constants';
|
||||
|
||||
|
@ -23,15 +23,14 @@ export default {
|
|||
GlSprintf,
|
||||
GlLink,
|
||||
SettingsBlock,
|
||||
SettingsTitles,
|
||||
},
|
||||
i18n: {
|
||||
DEPENDENCY_PROXY_HEADER,
|
||||
DEPENDENCY_PROXY_DESCRIPTION,
|
||||
enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'),
|
||||
enabledProxyHelpText: s__(
|
||||
'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'),
|
||||
ttlPolicyEnabledHelpText: s__(
|
||||
'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"
|
||||
>
|
||||
<template #title> {{ $options.i18n.DEPENDENCY_PROXY_HEADER }} </template>
|
||||
<template #description> {{ $options.i18n.DEPENDENCY_PROXY_DESCRIPTION }} </template>
|
||||
<template #default>
|
||||
<div>
|
||||
<gl-toggle
|
||||
|
@ -156,13 +156,12 @@ export default {
|
|||
</span>
|
||||
</template>
|
||||
</gl-toggle>
|
||||
|
||||
<settings-titles :title="$options.i18n.storageSettingsTitle" class="gl-my-6" />
|
||||
<gl-toggle
|
||||
v-model="ttlEnabled"
|
||||
:disabled="isLoading"
|
||||
:label="$options.i18n.ttlPolicyEnabledLabel"
|
||||
:help="$options.i18n.ttlPolicyEnabledHelpText"
|
||||
class="gl-mt-6"
|
||||
data-testid="dependency-proxy-ttl-policies-toggle"
|
||||
/>
|
||||
</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_DESCRIPTION = s__(
|
||||
'DependencyProxy|Enable the Dependency Proxy and settings for clearing the cache.',
|
||||
);
|
||||
|
||||
// Parameters
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ export default {
|
|||
project: DEFAULT_BLOB_INFO.project,
|
||||
gitpodEnabled: DEFAULT_BLOB_INFO.gitpodEnabled,
|
||||
currentUser: DEFAULT_BLOB_INFO.currentUser,
|
||||
useFallback: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -130,7 +131,7 @@ export default {
|
|||
},
|
||||
shouldLoadLegacyViewer() {
|
||||
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() {
|
||||
return (
|
||||
|
@ -173,6 +174,10 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
onError() {
|
||||
this.useFallback = true;
|
||||
this.loadLegacyViewer();
|
||||
},
|
||||
loadLegacyViewer() {
|
||||
if (this.legacyViewerLoaded) {
|
||||
return;
|
||||
|
@ -303,7 +308,7 @@ export default {
|
|||
:loading="isLoadingLegacyViewer"
|
||||
: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
|
||||
v-if="blobViewer || legacyViewerLoaded"
|
||||
:code-navigation-path="blobInfo.codeNavigationPath"
|
||||
|
|
|
@ -112,6 +112,12 @@ export const ROUGE_TO_HLJS_LANGUAGE_MAP = {
|
|||
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 BIDI_CHARS = [
|
||||
|
|
|
@ -3,7 +3,14 @@ import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
|
|||
import LineHighlighter from '~/blob/line_highlighter';
|
||||
import eventHub from '~/notes/event_hub';
|
||||
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 { registerPlugins } from './plugins/index';
|
||||
|
||||
|
@ -23,6 +30,7 @@ export default {
|
|||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
props: {
|
||||
blob: {
|
||||
type: Object,
|
||||
|
@ -49,8 +57,22 @@ export default {
|
|||
lineNumbers() {
|
||||
return this.splitContent.length;
|
||||
},
|
||||
unsupportedLanguage() {
|
||||
const supportedLanguages = Object.keys(languageLoader);
|
||||
return (
|
||||
!supportedLanguages.includes(this.language) &&
|
||||
!supportedLanguages.includes(this.blob.language)
|
||||
);
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.trackEvent(EVENT_LABEL_VIEWER);
|
||||
|
||||
if (this.unsupportedLanguage) {
|
||||
this.handleUnsupportedLanguage();
|
||||
return;
|
||||
}
|
||||
|
||||
this.generateFirstChunk();
|
||||
this.hljs = await this.loadHighlightJS();
|
||||
|
||||
|
@ -70,6 +92,13 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
trackEvent(label) {
|
||||
this.track(EVENT_ACTION, { label, property: this.blob.language });
|
||||
},
|
||||
handleUnsupportedLanguage() {
|
||||
this.trackEvent(EVENT_LABEL_FALLBACK);
|
||||
this.$emit('error');
|
||||
},
|
||||
generateFirstChunk() {
|
||||
const lines = this.splitContent.splice(0, LINES_PER_CHUNK);
|
||||
this.firstChunk = this.createChunk(lines);
|
||||
|
|
|
@ -19,6 +19,7 @@ export default function initWorkItemLinks() {
|
|||
if (!workItemLinksRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: workItemLinksRoot,
|
||||
|
@ -27,6 +28,9 @@ export default function initWorkItemLinks() {
|
|||
components: {
|
||||
workItemLinks: WorkItemLinks,
|
||||
},
|
||||
provide: {
|
||||
projectPath: workItemLinksRoot.dataset.projectPath,
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement('work-item-links', {
|
||||
props: {
|
||||
|
|
|
@ -1,23 +1,64 @@
|
|||
<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 {
|
||||
components: {
|
||||
GlForm,
|
||||
GlFormInput,
|
||||
GlFormCombobox,
|
||||
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() {
|
||||
return {
|
||||
relatedWorkItem: '',
|
||||
availableWorkItems: [],
|
||||
search: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getIdFromGraphQLId,
|
||||
},
|
||||
i18n: {
|
||||
inputLabel: __('Children'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
{{ s__('WorkItem|Add') }}
|
||||
</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
|
||||
metrics_dashboard
|
||||
feature_flags
|
||||
tracings
|
||||
terraform
|
||||
]
|
||||
end
|
||||
|
|
|
@ -247,7 +247,6 @@ class Project < ApplicationRecord
|
|||
has_many :export_jobs, class_name: 'ProjectExportJob'
|
||||
has_many :bulk_import_exports, class_name: 'BulkImports::Export', 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 :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
|
||||
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
|
||||
|
@ -434,7 +433,6 @@ class Project < ApplicationRecord
|
|||
allow_destroy: true,
|
||||
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 :error_tracking_setting, update_only: 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 :imported_from, -> (type) { where(import_type: type) }
|
||||
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_service_desk_key, -> (key) do
|
||||
|
@ -2762,10 +2759,6 @@ class Project < ApplicationRecord
|
|||
instance.token
|
||||
end
|
||||
|
||||
def tracing_external_url
|
||||
tracing_setting&.external_url
|
||||
end
|
||||
|
||||
override :git_garbage_collect_worker_klass
|
||||
def git_garbage_collect_worker_klass
|
||||
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(prometheus_integration_params)
|
||||
.merge(incident_management_setting_params)
|
||||
.merge(tracing_setting_params)
|
||||
end
|
||||
|
||||
def alerting_setting_params
|
||||
|
@ -131,15 +130,6 @@ module Projects
|
|||
|
||||
{ incident_management_setting_attributes: attrs }
|
||||
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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
- 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')
|
||||
- add_page_specific_style 'page_bundles/wiki'
|
||||
- @gfm_form = true
|
||||
- @noteable_type = 'Wiki'
|
||||
|
||||
- if @error
|
||||
#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'
|
||||
type: development
|
||||
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'
|
||||
type: development
|
||||
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)
|
||||
end
|
||||
|
||||
roulette.required_approvals.each do |approval|
|
||||
rows << markdown_row_for_spin(approval.category, approval.spin)
|
||||
end
|
||||
|
||||
markdown(REVIEW_ROULETTE_SECTION)
|
||||
|
||||
if rows.empty?
|
||||
|
|
|
@ -334,14 +334,15 @@ Example response:
|
|||
## Authenticate Error Tracking requests
|
||||
|
||||
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 |
|
||||
|:-------------|:--------|:---------|:-------------------------------------------------------------------|
|
||||
| `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
|
||||
POST /internal/error_tracking_allowed
|
||||
POST /internal/error_tracking/allowed
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
@ -349,7 +350,7 @@ Example request:
|
|||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--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:
|
||||
|
|
|
@ -292,7 +292,7 @@ fail.
|
|||
### Troubleshooting `rspec:undercoverage` failures
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
using `data.terraform_remote_state.example.outputs.<OUTPUT-NAME>`.
|
||||
|
|
|
@ -8,6 +8,11 @@ module API
|
|||
|
||||
expose :alert_status
|
||||
expose :disabled_until
|
||||
expose :url_variables
|
||||
|
||||
def url_variables
|
||||
object.url_variables.keys.map { { key: _1 } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -476,9 +476,9 @@ module API
|
|||
render_api_error!('202 Accepted', 202)
|
||||
end
|
||||
|
||||
def render_validation_error!(model)
|
||||
def render_validation_error!(model, status = 400)
|
||||
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
|
||||
|
||||
|
|
|
@ -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)
|
||||
end
|
||||
|
||||
post '/error_tracking_allowed', feature_category: :error_tracking do
|
||||
post '/error_tracking/allowed', feature_category: :error_tracking do
|
||||
public_key = params[:public_key]
|
||||
project_id = params[:project_id]
|
||||
|
||||
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
|
||||
{ enabled: enabled }
|
||||
|
|
|
@ -9,16 +9,21 @@ module API
|
|||
|
||||
feature_category :integrations
|
||||
|
||||
helpers ::API::Helpers::WebHooksHelpers
|
||||
|
||||
helpers do
|
||||
params :project_hook_properties do
|
||||
requires :url, type: String, desc: "The URL to send the request to"
|
||||
def hook_scope
|
||||
user_project.hooks
|
||||
end
|
||||
|
||||
params :common_hook_parameters do
|
||||
optional :push_events, type: Boolean, desc: "Trigger hook on push 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 :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 :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 :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 :job_events, type: Boolean, desc: "Trigger hook on job events"
|
||||
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline 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 :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"
|
||||
use :url_variables
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -34,6 +40,10 @@ module API
|
|||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
namespace ':id/hooks' do
|
||||
mount ::API::Hooks::UrlVariables
|
||||
end
|
||||
|
||||
desc 'Get project hooks' do
|
||||
success Entities::ProjectHook
|
||||
end
|
||||
|
@ -59,43 +69,26 @@ module API
|
|||
success Entities::ProjectHook
|
||||
end
|
||||
params do
|
||||
use :project_hook_properties
|
||||
use :requires_url
|
||||
use :common_hook_parameters
|
||||
end
|
||||
post ":id/hooks" do
|
||||
hook_params = declared_params(include_missing: false)
|
||||
|
||||
hook_params = create_hook_params
|
||||
hook = user_project.hooks.new(hook_params)
|
||||
|
||||
if hook.save
|
||||
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
|
||||
save_hook(hook, Entities::ProjectHook)
|
||||
end
|
||||
|
||||
desc 'Update an existing project hook' do
|
||||
desc 'Update an existing hook' do
|
||||
success Entities::ProjectHook
|
||||
end
|
||||
params do
|
||||
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
|
||||
put ":id/hooks/:hook_id" do
|
||||
hook = user_project.hooks.find(params.delete(:hook_id))
|
||||
|
||||
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
|
||||
update_hook(entity: Entities::ProjectHook)
|
||||
end
|
||||
|
||||
desc 'Deletes project hook' do
|
||||
|
@ -105,7 +98,7 @@ module API
|
|||
requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
|
||||
end
|
||||
delete ":id/hooks/:hook_id" do
|
||||
hook = user_project.hooks.find(params.delete(:hook_id))
|
||||
hook = find_hook
|
||||
|
||||
destroy_conditionally!(hook) do
|
||||
WebHooks::DestroyService.new(current_user).execute(hook)
|
||||
|
|
|
@ -11,7 +11,27 @@ module API
|
|||
authenticated_as_admin!
|
||||
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
|
||||
mount ::API::Hooks::UrlVariables
|
||||
|
||||
desc 'Get the list of system hooks' do
|
||||
success Entities::Hook
|
||||
end
|
||||
|
@ -26,70 +46,63 @@ module API
|
|||
success Entities::Hook
|
||||
end
|
||||
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
|
||||
get ":id" do
|
||||
hook = SystemHook.find(params[:id])
|
||||
|
||||
present hook, with: Entities::Hook
|
||||
get ":hook_id" do
|
||||
present find_hook, with: Entities::Hook
|
||||
end
|
||||
|
||||
desc 'Create a new system hook' do
|
||||
success Entities::Hook
|
||||
end
|
||||
params do
|
||||
requires :url, type: String, desc: "The URL to send the request to"
|
||||
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 :requires_url
|
||||
use :hook_parameters
|
||||
end
|
||||
post do
|
||||
hook = SystemHook.new(declared_params(include_missing: false))
|
||||
hook_params = create_hook_params
|
||||
hook = SystemHook.new(hook_params)
|
||||
|
||||
if hook.save
|
||||
present hook, with: Entities::Hook
|
||||
else
|
||||
render_validation_error!(hook)
|
||||
end
|
||||
save_hook(hook, Entities::Hook)
|
||||
end
|
||||
|
||||
desc 'Test a hook'
|
||||
desc 'Update an existing system hook' do
|
||||
success Entities::Hook
|
||||
end
|
||||
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
|
||||
post ":id" do
|
||||
hook = SystemHook.find(params[:id])
|
||||
data = {
|
||||
put ":hook_id" do
|
||||
update_hook(entity: Entities::Hook)
|
||||
end
|
||||
|
||||
mount ::API::Hooks::Test, with: {
|
||||
data: {
|
||||
event_name: "project_create",
|
||||
name: "Ruby",
|
||||
path: "ruby",
|
||||
project_id: 1,
|
||||
owner_name: "Someone",
|
||||
owner_email: "example@gitlabhq.com"
|
||||
}
|
||||
hook.execute(data, 'system_hooks')
|
||||
data
|
||||
end
|
||||
},
|
||||
kind: 'system_hooks'
|
||||
}
|
||||
|
||||
desc 'Delete a hook' do
|
||||
success Entities::Hook
|
||||
end
|
||||
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
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
delete ":id" do
|
||||
hook = SystemHook.find_by(id: params[:id])
|
||||
not_found!('System hook') unless hook
|
||||
delete ":hook_id" do
|
||||
hook = find_hook
|
||||
|
||||
destroy_conditionally!(hook) do
|
||||
WebHooks::DestroyService.new(current_user).execute(hook)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,7 +42,7 @@ module Gitlab
|
|||
return false if taggings.empty?
|
||||
|
||||
taggings.each_slice(TAGGINGS_BATCH_SIZE) do |taggings_slice|
|
||||
ActsAsTaggableOn::Tagging.insert_all!(taggings)
|
||||
ActsAsTaggableOn::Tagging.insert_all!(taggings_slice)
|
||||
end
|
||||
|
||||
true
|
||||
|
|
|
@ -2043,9 +2043,6 @@ msgstr ""
|
|||
msgid "Add CONTRIBUTING"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add Jaeger URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2076,9 +2073,6 @@ msgstr ""
|
|||
msgid "Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -7819,6 +7813,9 @@ msgstr ""
|
|||
msgid "Child issues and epics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Children"
|
||||
msgstr ""
|
||||
|
||||
msgid "Chinese language support using"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9463,9 +9460,6 @@ msgstr ""
|
|||
msgid "Configure Sentry integration for error tracking"
|
||||
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}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12374,15 +12368,15 @@ msgstr ""
|
|||
msgid "DependencyProxy|Enable Dependency Proxy"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Enable the Dependency Proxy and settings for clearing the cache."
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Image list"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Scheduled for deletion"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Storage settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|There are no images in the cache"
|
||||
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."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -17313,9 +17304,6 @@ msgstr ""
|
|||
msgid "GitLab username"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab uses %{linkStart}Sidekiq%{linkEnd} to process background jobs"
|
||||
msgstr ""
|
||||
|
||||
|
@ -40278,9 +40266,6 @@ msgstr ""
|
|||
msgid "TotalRefCountIndicator|1000+"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tracing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Track groups of issues that share a theme, across projects and milestones"
|
||||
msgstr ""
|
||||
|
||||
|
@ -40503,9 +40488,6 @@ msgstr ""
|
|||
msgid "Trigger|invalid"
|
||||
msgstr ""
|
||||
|
||||
msgid "Troubleshoot and monitor your application with tracing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Trusted"
|
||||
msgstr ""
|
||||
|
||||
|
@ -44471,9 +44453,6 @@ msgstr ""
|
|||
msgid "Your new comment"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -74,12 +74,7 @@ module QA
|
|||
|
||||
def list_of_runners(tag_list: nil)
|
||||
url = tag_list ? "#{api_post_path}?tag_list=#{tag_list.compact.join(',')}" : api_post_path
|
||||
response = get(request_url(url, per_page: '100'))
|
||||
|
||||
# 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)
|
||||
auto_paginated_response(request_url(url))
|
||||
end
|
||||
|
||||
def reload!
|
||||
|
|
|
@ -263,14 +263,6 @@ module QA
|
|||
ENV['GITLAB_QA_PASSWORD_6']
|
||||
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
|
||||
ENV['GITLAB_QA_1P_EMAIL']
|
||||
end
|
||||
|
|
|
@ -4,10 +4,9 @@ module QA
|
|||
RSpec.describe 'Manage', :requires_admin, :skip_live_env, :reliable do
|
||||
describe '2FA' 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
|
||||
|
||||
let(:developer_user) do
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
module QA
|
||||
RSpec.describe 'Manage', :requires_admin, :skip_live_env do
|
||||
describe '2FA' 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)
|
||||
let!(:owner_user) do
|
||||
Resource::User.fabricate_via_api! do |usr|
|
||||
usr.api_client = admin_api_client
|
||||
end
|
||||
end
|
||||
|
||||
let(:sandbox_group) do
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -304,6 +304,9 @@ function retry_failed_rspec_examples() {
|
|||
# Disable Crystalball on retry to not overwrite the existing report
|
||||
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
|
||||
rspec_simple_job "--only-failures --pattern \"${KNAPSACK_TEST_FILE_PATTERN}\"" "${JUNIT_RETRY_FILE}"
|
||||
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, :inactive, project: projects[1], name: 'DataFox')
|
||||
|
||||
# Tracing
|
||||
create(:project_tracing_setting, project: projects[0])
|
||||
|
||||
# Alert Issues
|
||||
create(:alert_management_alert, issue: 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')
|
||||
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
|
||||
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",
|
||||
"enable_ssl_verification",
|
||||
"alert_status",
|
||||
"disabled_until"
|
||||
"disabled_until",
|
||||
"url_variables"
|
||||
],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
|
@ -22,7 +23,18 @@
|
|||
"repository_update_events": { "type": "boolean" },
|
||||
"enable_ssl_verification": { "type": "boolean" },
|
||||
"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
|
||||
}
|
||||
|
|
|
@ -33,13 +33,16 @@ describe('Batch comments draft note component', () => {
|
|||
const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
|
||||
const findAddCommentButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
const createComponent = (propsData = { draft }) => {
|
||||
const createComponent = (propsData = { draft }, glFeatures = {}) => {
|
||||
wrapper = shallowMount(DraftNote, {
|
||||
store,
|
||||
propsData,
|
||||
stubs: {
|
||||
NoteableNote: NoteableNoteStub,
|
||||
},
|
||||
provide: {
|
||||
glFeatures,
|
||||
},
|
||||
});
|
||||
|
||||
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().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', () => {
|
||||
|
|
|
@ -6,13 +6,15 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
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 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 SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
||||
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
|
||||
import {
|
||||
updateGroupDependencyProxySettingsOptimisticResponse,
|
||||
updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
|
||||
|
@ -86,7 +88,6 @@ describe('DependencyProxySettings', () => {
|
|||
});
|
||||
|
||||
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
|
||||
const findSettingsTitles = () => wrapper.findComponent(SettingsTitles);
|
||||
const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle');
|
||||
const findEnableTtlPoliciesToggle = () =>
|
||||
wrapper.findByTestId('dependency-proxy-ttl-policies-toggle');
|
||||
|
@ -114,10 +115,11 @@ describe('DependencyProxySettings', () => {
|
|||
expect(findSettingsBlock().props('defaultExpanded')).toBe(false);
|
||||
});
|
||||
|
||||
it('has the correct header text', () => {
|
||||
it('has the correct header text and description', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(wrapper.text()).toContain(DEPENDENCY_PROXY_HEADER);
|
||||
expect(wrapper.text()).toContain(DEPENDENCY_PROXY_DESCRIPTION);
|
||||
});
|
||||
|
||||
describe('enable toggle', () => {
|
||||
|
@ -158,14 +160,6 @@ describe('DependencyProxySettings', () => {
|
|||
});
|
||||
|
||||
describe('storage settings', () => {
|
||||
it('the component has the settings title', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findSettingsTitles().props()).toMatchObject({
|
||||
title: component.i18n.storageSettingsTitle,
|
||||
});
|
||||
});
|
||||
|
||||
describe('enable proxy ttl policies', () => {
|
||||
it('exists', () => {
|
||||
mountComponent();
|
||||
|
|
|
@ -136,6 +136,7 @@ describe('Blob content viewer component', () => {
|
|||
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
|
||||
const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
|
||||
const findCodeIntelligence = () => wrapper.findComponent(CodeIntelligence);
|
||||
const findSourceViewer = () => wrapper.findComponent(SourceViewer);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
||||
|
@ -197,6 +198,16 @@ describe('Blob content viewer component', () => {
|
|||
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 () => {
|
||||
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 { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
|
||||
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 LineHighlighter from '~/blob/line_highlighter';
|
||||
import eventHub from '~/notes/event_hub';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
jest.mock('~/blob/line_highlighter');
|
||||
jest.mock('highlight.js/lib/core');
|
||||
|
@ -52,12 +58,33 @@ describe('Source Viewer component', () => {
|
|||
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
||||
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
jest.spyOn(Tracking, 'event');
|
||||
|
||||
return createComponent();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
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(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4])
|
||||
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
|
||||
|
||||
context 'with tags for only one job' do
|
||||
|
|
|
@ -557,7 +557,6 @@ project:
|
|||
- packages
|
||||
- package_files
|
||||
- packages_cleanup_policy
|
||||
- tracing_setting
|
||||
- alerting_setting
|
||||
- project_setting
|
||||
- webide_pipelines
|
||||
|
@ -695,8 +694,6 @@ epic_issues:
|
|||
feature_flag_issues:
|
||||
- issue
|
||||
- feature_flag
|
||||
tracing_setting:
|
||||
- project
|
||||
reviews:
|
||||
- project
|
||||
- merge_request
|
||||
|
|
|
@ -564,8 +564,6 @@ Project:
|
|||
- suggestion_commit_message
|
||||
- merge_commit_template
|
||||
- squash_commit_template
|
||||
ProjectTracingSetting:
|
||||
- external_url
|
||||
Author:
|
||||
- name
|
||||
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(:forked_from_project).through(:fork_network_member) }
|
||||
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(:project_setting) }
|
||||
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
|
||||
|
||||
describe 'GET /internal/error_tracking_allowed' do
|
||||
describe 'GET /internal/error_tracking/allowed' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
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
|
||||
let(:headers) { {} }
|
||||
|
||||
it 'responds with unauthorized entity' do
|
||||
post api("/internal/error_tracking_allowed"), params: params
|
||||
send_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when some params are missing' do
|
||||
let(:params) { { project_id: project.id } }
|
||||
|
||||
it 'responds with unprocessable entity' do
|
||||
post api("/internal/error_tracking_allowed"), params: params.except(:public_key),
|
||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
||||
send_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the error tracking is disabled' do
|
||||
context 'when public_key is unknown' do
|
||||
it 'returns enabled: false' do
|
||||
create(:error_tracking_client_key, project: project, active: false)
|
||||
|
||||
post api("/internal/error_tracking_allowed"), params: params,
|
||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
||||
send_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to eq({ 'enabled' => false })
|
||||
expect(json_response).to eq('enabled' => false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the error tracking record does not exist' do
|
||||
it 'returns enabled: false' do
|
||||
post api("/internal/error_tracking_allowed"), params: params,
|
||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
||||
context 'when unknown project_id is unknown' do
|
||||
it 'responds with 404 not found' do
|
||||
params[:project_id] = non_existing_record_id
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to eq({ 'enabled' => false })
|
||||
end
|
||||
send_request
|
||||
|
||||
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
|
||||
|
||||
context 'when the error tracking is enabled' do
|
||||
it 'returns enabled: true' do
|
||||
client_key = create(:error_tracking_client_key, project: project, active: true)
|
||||
params[:public_key] = client_key.public_key
|
||||
let_it_be(:client_key) { create(:error_tracking_client_key, project: project) }
|
||||
|
||||
post api("/internal/error_tracking_allowed"), params: params,
|
||||
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
|
||||
before do
|
||||
params[:public_key] = client_key.public_key
|
||||
end
|
||||
|
||||
it 'returns enabled: true' do
|
||||
send_request
|
||||
|
||||
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
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ProjectHooks, 'ProjectHooks' do
|
||||
let(:user) { create(:user) }
|
||||
let(:user3) { create(:user) }
|
||||
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
|
||||
let!(:hook) do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:user3) { create(:user) }
|
||||
let_it_be(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
|
||||
let_it_be_with_refind(:hook) do
|
||||
create(:project_hook,
|
||||
:all_events_enabled,
|
||||
project: project,
|
||||
|
@ -15,232 +15,55 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
|
|||
push_events_branch_filter: 'master')
|
||||
end
|
||||
|
||||
before do
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
project.add_developer(user3)
|
||||
end
|
||||
|
||||
describe "GET /projects/:id/hooks" do
|
||||
context "authorized user" do
|
||||
it "returns project hooks" do
|
||||
get api("/projects/#{project.id}/hooks", user)
|
||||
it_behaves_like 'web-hook API endpoints', '/projects/:id' do
|
||||
let(:unauthorized_user) { user3 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_an Array
|
||||
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
|
||||
def scope
|
||||
project.hooks
|
||||
end
|
||||
|
||||
context "unauthorized user" do
|
||||
it "does not access project hooks" do
|
||||
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
|
||||
def collection_uri
|
||||
"/projects/#{project.id}/hooks"
|
||||
end
|
||||
|
||||
context "unauthorized user" do
|
||||
it "does not access an existing hook" do
|
||||
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')
|
||||
def match_collection_schema
|
||||
match_response_schema('public_api/v4/project_hooks')
|
||||
end
|
||||
|
||||
it "adds the token without including it in the response" do
|
||||
token = "secret token"
|
||||
|
||||
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)
|
||||
def hook_uri(hook_id = hook.id)
|
||||
"/projects/#{project.id}/hooks/#{hook_id}"
|
||||
end
|
||||
|
||||
it "returns a 400 error if url not given" do
|
||||
post api("/projects/#{project.id}/hooks", user)
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
def match_hook_schema
|
||||
match_response_schema('public_api/v4/project_hook')
|
||||
end
|
||||
|
||||
it "returns a 422 error if url not valid" do
|
||||
post api("/projects/#{project.id}/hooks", user), params: { url: "ftp://example.com" }
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
def event_names
|
||||
%i[
|
||||
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
|
||||
|
||||
it "returns a 422 error if branch filter is not valid" do
|
||||
post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
|
||||
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)
|
||||
let(:default_values) do
|
||||
{ push_events: true, confidential_note_events: nil }
|
||||
end
|
||||
|
||||
it "adds the token without including it in the response" do
|
||||
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
|
||||
it_behaves_like 'web-hook API endpoints with branch-filter', '/projects/:id'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,221 +3,58 @@
|
|||
require 'spec_helper'
|
||||
|
||||
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) }
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:hook) { create(:system_hook, url: "http://example.com") }
|
||||
it_behaves_like 'web-hook API endpoints', '' do
|
||||
let(:user) { admin }
|
||||
let(:unauthorized_user) { non_admin }
|
||||
|
||||
before do
|
||||
stub_full_request(hook.url, method: :post)
|
||||
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
|
||||
def scope
|
||||
SystemHook
|
||||
end
|
||||
|
||||
context "when not an admin" do
|
||||
it "returns forbidden error" do
|
||||
get api("/hooks", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
def collection_uri
|
||||
"/hooks"
|
||||
end
|
||||
|
||||
context "when authenticated as admin" do
|
||||
it "returns an array of hooks" do
|
||||
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
|
||||
def match_collection_schema
|
||||
match_response_schema('public_api/v4/system_hooks')
|
||||
end
|
||||
|
||||
context "when not an admin" do
|
||||
it "returns forbidden error" do
|
||||
get api("/hooks/#{hook.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
def hook_uri(hook_id = hook.id)
|
||||
"/hooks/#{hook_id}"
|
||||
end
|
||||
|
||||
context "when authenticated as admin" do
|
||||
it "gets a hook", :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 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)
|
||||
def match_hook_schema
|
||||
match_response_schema('public_api/v4/system_hook')
|
||||
end
|
||||
|
||||
it "responds with 400 if url not given" do
|
||||
post api("/hooks", admin)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
def event_names
|
||||
%i[
|
||||
push_events
|
||||
tag_push_events
|
||||
merge_requests_events
|
||||
repository_update_events
|
||||
]
|
||||
end
|
||||
|
||||
it "responds with 400 if url is invalid" do
|
||||
post api("/hooks", admin), params: { url: 'hp://mep.mep' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
def hook_param_overrides
|
||||
{}
|
||||
end
|
||||
|
||||
it "does not create new hook without url" do
|
||||
expect do
|
||||
post api("/hooks", admin)
|
||||
end.not_to change { SystemHook.count }
|
||||
let(:update_params) do
|
||||
{
|
||||
push_events: false,
|
||||
tag_push_events: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets default values for events' do
|
||||
stub_full_request('http://mep.mep', method: :post)
|
||||
|
||||
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
|
||||
let(:default_values) do
|
||||
{ repository_update_events: true }
|
||||
end
|
||||
|
||||
it 'sets explicit values for events' do
|
||||
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
|
||||
it_behaves_like 'web-hook API endpoints test hook', ''
|
||||
end
|
||||
end
|
||||
|
|
|
@ -462,93 +462,5 @@ RSpec.describe Projects::Operations::UpdateService do
|
|||
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
|
||||
|
|
|
@ -9,7 +9,7 @@ module SimpleCovEnv
|
|||
extend self
|
||||
|
||||
def start!
|
||||
return unless ENV['SIMPLECOV']
|
||||
return if !ENV.key?('SIMPLECOV') || ENV['SIMPLECOV'] == '0'
|
||||
|
||||
configure_profile
|
||||
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'
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'autocompletes items'
|
||||
end
|
||||
|
||||
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{(CODEOWNERS)} => :tooling,
|
||||
%r{(tests.yml)} => :tooling,
|
||||
%r{\A\.gitpod\.yml} => :tooling,
|
||||
|
||||
%r{\Alib/gitlab/ci/templates} => :ci_template,
|
||||
|
||||
|
|
Loading…
Reference in New Issue