summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo/gitlab/namespaced_class.yml1
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/batch_comments/components/draft_note.vue9
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue9
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/constants.js3
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/constants.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue31
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/index.js4
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue47
-rw-r--r--app/assets/javascripts/work_items/graphql/project_work_items.query.graphql13
-rw-r--r--app/controllers/projects/tracings_controller.rb30
-rw-r--r--app/helpers/projects_helper.rb1
-rw-r--r--app/models/project.rb7
-rw-r--r--app/models/project_tracing_setting.rb15
-rw-r--r--app/services/projects/operations/update_service.rb10
-rw-r--r--app/views/projects/issues/_work_item_links.html.haml2
-rw-r--r--app/views/projects/tracings/_tracing_button.html.haml2
-rw-r--r--app/views/projects/tracings/show.html.haml50
-rw-r--r--app/views/shared/wikis/edit.html.haml2
-rw-r--r--config/feature_flags/development/import_release_authors_from_github.yml2
-rw-r--r--config/feature_flags/development/issues_full_text_search.yml2
-rw-r--r--config/feature_flags/development/use_click_house_database_for_error_tracking.yml8
-rw-r--r--danger/roulette/Dangerfile4
-rw-r--r--doc/development/internal_api/index.md7
-rw-r--r--doc/development/pipelines.md2
-rw-r--r--doc/user/infrastructure/iac/terraform_state.md3
-rw-r--r--lib/api/entities/hook.rb5
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/helpers/web_hooks_helpers.rb70
-rw-r--r--lib/api/hooks/test.rb21
-rw-r--r--lib/api/hooks/url_variables.rb45
-rw-r--r--lib/api/internal/base.rb6
-rw-r--r--lib/api/project_hooks.rb53
-rw-r--r--lib/api/system_hooks.rb79
-rw-r--r--lib/gitlab/ci/tags/bulk_insert.rb2
-rw-r--r--locale/gitlab.pot33
-rw-r--r--qa/qa/resource/runner.rb7
-rw-r--r--qa/qa/runtime/env.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb8
-rw-r--r--scripts/rspec_helpers.sh3
-rw-r--r--spec/controllers/projects/tracings_controller_spec.rb72
-rw-r--r--spec/factories/project_tracing_settings.rb8
-rw-r--r--spec/factories/usage_data.rb3
-rw-r--r--spec/features/merge_request/batch_comments_spec.rb12
-rw-r--r--spec/features/projects/tracings_spec.rb60
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/project_hook.json62
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/project_hooks.json10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/system_hook.json16
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js11
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js18
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js11
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js29
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js49
-rw-r--r--spec/frontend/work_items/mock_data.js25
-rw-r--r--spec/lib/gitlab/ci/tags/bulk_insert_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/project_tracing_setting_spec.rb40
-rw-r--r--spec/requests/api/internal/base_spec.rb77
-rw-r--r--spec/requests/api/project_hooks_spec.rb247
-rw-r--r--spec/requests/api/system_hooks_spec.rb229
-rw-r--r--spec/services/projects/operations/update_service_spec.rb88
-rw-r--r--spec/simplecov_env.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/hooks_shared_examples.rb415
-rw-r--r--spec/views/projects/tracing/show.html.haml_spec.rb59
-rw-r--r--tooling/danger/project_helper.rb1
72 files changed, 1207 insertions, 1050 deletions
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index 23ff86ba289..103628c577c 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -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'
diff --git a/Gemfile b/Gemfile
index 04a3327aa45..8ca232fc1e6 100644
--- a/Gemfile
+++ b/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
diff --git a/Gemfile.lock b/Gemfile.lock
index e9ebf663153..2c6a35fe072 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue
index 2b1ab911fbe..300a81caa5c 100644
--- a/app/assets/javascripts/batch_comments/components/draft_note.vue
+++ b/app/assets/javascripts/batch_comments/components/draft_note.vue
@@ -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"
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
index 130d6977936..4c17b555d1b 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
@@ -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>
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/constants.js b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
index 0249b475e46..0355ad39241 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
@@ -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
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 280455c3fed..bf4f19504f0 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -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"
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
index 0d78530d878..5ba0ea68d80 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
@@ -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 = [
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
index f819a9e5be2..b5c66365836 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
@@ -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);
diff --git a/app/assets/javascripts/work_items/components/work_item_links/index.js b/app/assets/javascripts/work_items/components/work_item_links/index.js
index 320a4a213e3..7ac9395c725 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/index.js
+++ b/app/assets/javascripts/work_items/components/work_item_links/index.js
@@ -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: {
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index 22728f58026..9b981460f99 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -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>
diff --git a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
new file mode 100644
index 00000000000..173a29be6a9
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
@@ -0,0 +1,13 @@
+query projectWorkItems($searchTerm: String, $projectPath: ID!) {
+ workspace: project(fullPath: $projectPath) {
+ id
+ workItems(search: $searchTerm) {
+ edges {
+ node {
+ id
+ title
+ }
+ }
+ }
+ }
+}
diff --git a/app/controllers/projects/tracings_controller.rb b/app/controllers/projects/tracings_controller.rb
deleted file mode 100644
index b5c1354c4a9..00000000000
--- a/app/controllers/projects/tracings_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 6112d05f37d..004075ed636 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -683,7 +683,6 @@ module ProjectsHelper
product_analytics
metrics_dashboard
feature_flags
- tracings
terraform
]
end
diff --git a/app/models/project.rb b/app/models/project.rb
index dca47911d20..039cfdef2c5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
diff --git a/app/models/project_tracing_setting.rb b/app/models/project_tracing_setting.rb
deleted file mode 100644
index 93fa80aed67..00000000000
--- a/app/models/project_tracing_setting.rb
+++ /dev/null
@@ -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
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index d01e96a1a2d..d0e5f0de97c 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -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
diff --git a/app/views/projects/issues/_work_item_links.html.haml b/app/views/projects/issues/_work_item_links.html.haml
index 55a8eb720b6..5d478784350 100644
--- a/app/views/projects/issues/_work_item_links.html.haml
+++ b/app/views/projects/issues/_work_item_links.html.haml
@@ -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 } }
diff --git a/app/views/projects/tracings/_tracing_button.html.haml b/app/views/projects/tracings/_tracing_button.html.haml
deleted file mode 100644
index fe3af1c6a1a..00000000000
--- a/app/views/projects/tracings/_tracing_button.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= link_to project_settings_operations_path(@project), title: _('Configure Tracing'), class: 'gl-button btn btn-confirm' do
- = _('Add Jaeger URL')
diff --git a/app/views/projects/tracings/show.html.haml b/app/views/projects/tracings/show.html.haml
deleted file mode 100644
index 61f2cd8ac7f..00000000000
--- a/app/views/projects/tracings/show.html.haml
+++ /dev/null
@@ -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'
diff --git a/app/views/shared/wikis/edit.html.haml b/app/views/shared/wikis/edit.html.haml
index 6bbce6b80d8..fc56a191cad 100644
--- a/app/views/shared/wikis/edit.html.haml
+++ b/app/views/shared/wikis/edit.html.haml
@@ -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) } }
diff --git a/config/feature_flags/development/import_release_authors_from_github.yml b/config/feature_flags/development/import_release_authors_from_github.yml
index b0ddca12d87..c263892fbf8 100644
--- a/config/feature_flags/development/import_release_authors_from_github.yml
+++ b/config/feature_flags/development/import_release_authors_from_github.yml
@@ -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
diff --git a/config/feature_flags/development/issues_full_text_search.yml b/config/feature_flags/development/issues_full_text_search.yml
index 354dbede75f..31fe543e35e 100644
--- a/config/feature_flags/development/issues_full_text_search.yml
+++ b/config/feature_flags/development/issues_full_text_search.yml
@@ -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
diff --git a/config/feature_flags/development/use_click_house_database_for_error_tracking.yml b/config/feature_flags/development/use_click_house_database_for_error_tracking.yml
new file mode 100644
index 00000000000..15d31568fb0
--- /dev/null
+++ b/config/feature_flags/development/use_click_house_database_for_error_tracking.yml
@@ -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
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 527cdf58391..0e6af5792cd 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -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?
diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md
index 288c0056821..13e095b4a83 100644
--- a/doc/development/internal_api/index.md
+++ b/doc/development/internal_api/index.md
@@ -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:
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 436977a7f38..596af5b777b 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -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:
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index e8637abce91..e29228c7a4c 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -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>`.
diff --git a/lib/api/entities/hook.rb b/lib/api/entities/hook.rb
index d176e76b321..95924321221 100644
--- a/lib/api/entities/hook.rb
+++ b/lib/api/entities/hook.rb
@@ -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
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index fc1037131d8..c73a5482cac 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -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
diff --git a/lib/api/helpers/web_hooks_helpers.rb b/lib/api/helpers/web_hooks_helpers.rb
new file mode 100644
index 00000000000..a71e56af4c3
--- /dev/null
+++ b/lib/api/helpers/web_hooks_helpers.rb
@@ -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
diff --git a/lib/api/hooks/test.rb b/lib/api/hooks/test.rb
new file mode 100644
index 00000000000..4871955c6e0
--- /dev/null
+++ b/lib/api/hooks/test.rb
@@ -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
diff --git a/lib/api/hooks/url_variables.rb b/lib/api/hooks/url_variables.rb
new file mode 100644
index 00000000000..708b78134e5
--- /dev/null
+++ b/lib/api/hooks/url_variables.rb
@@ -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
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 3edd38a0108..eb52e8d2c0f 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -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 }
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 431ba199131..466e80d68c8 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -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)
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 7c91fbd36d9..804cedfefe9 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -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
diff --git a/lib/gitlab/ci/tags/bulk_insert.rb b/lib/gitlab/ci/tags/bulk_insert.rb
index 29f3731a9b4..870bd0fc0a2 100644
--- a/lib/gitlab/ci/tags/bulk_insert.rb
+++ b/lib/gitlab/ci/tags/bulk_insert.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 34f0bd2b1e6..d3e55ff9df3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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,13 +12368,13 @@ msgstr ""
msgid "DependencyProxy|Enable Dependency Proxy"
msgstr ""
-msgid "DependencyProxy|Image list"
+msgid "DependencyProxy|Enable the Dependency Proxy and settings for clearing the cache."
msgstr ""
-msgid "DependencyProxy|Scheduled for deletion"
+msgid "DependencyProxy|Image list"
msgstr ""
-msgid "DependencyProxy|Storage settings"
+msgid "DependencyProxy|Scheduled for deletion"
msgstr ""
msgid "DependencyProxy|There are no images in the cache"
@@ -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 ""
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 278bdd1cabd..6d5ff71b2ba 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -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!
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 8ad5b107c08..dd0713bc2e8 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -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
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb
index 9ba41626d5f..f459c0c71eb 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb
@@ -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
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
index 64614ed654f..c5efa833f04 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
@@ -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
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index a67e1b8ac66..b31e3663eaa 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -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=$?
diff --git a/spec/controllers/projects/tracings_controller_spec.rb b/spec/controllers/projects/tracings_controller_spec.rb
deleted file mode 100644
index 80e21349e20..00000000000
--- a/spec/controllers/projects/tracings_controller_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/factories/project_tracing_settings.rb b/spec/factories/project_tracing_settings.rb
deleted file mode 100644
index 05c1529c18e..00000000000
--- a/spec/factories/project_tracing_settings.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :project_tracing_setting do
- project
- external_url { 'https://example.com' }
- end
-end
diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb
index 316e0c2b8d6..8a43ea64390 100644
--- a/spec/factories/usage_data.rb
+++ b/spec/factories/usage_data.rb
@@ -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])
diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb
index f03c812ebb5..9c4fb9a426b 100644
--- a/spec/features/merge_request/batch_comments_spec.rb
+++ b/spec/features/merge_request/batch_comments_spec.rb
@@ -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
diff --git a/spec/features/projects/tracings_spec.rb b/spec/features/projects/tracings_spec.rb
deleted file mode 100644
index b79a0427ef6..00000000000
--- a/spec/features/projects/tracings_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/fixtures/api/schemas/public_api/v4/project_hook.json b/spec/fixtures/api/schemas/public_api/v4/project_hook.json
new file mode 100644
index 00000000000..6070f3a55f9
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/project_hook.json
@@ -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
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/project_hooks.json b/spec/fixtures/api/schemas/public_api/v4/project_hooks.json
new file mode 100644
index 00000000000..8c542ebe3ad
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/project_hooks.json
@@ -0,0 +1,10 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "$ref": "./project_hook.json"
+ }
+ }
+}
+
diff --git a/spec/fixtures/api/schemas/public_api/v4/system_hook.json b/spec/fixtures/api/schemas/public_api/v4/system_hook.json
index 3fe3e0d658e..b6f56b948a0 100644
--- a/spec/fixtures/api/schemas/public_api/v4/system_hook.json
+++ b/spec/fixtures/api/schemas/public_api/v4/system_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
}
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index 6a997ebaaa8..ccca4a2c3e9 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -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', () => {
diff --git a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
index e60989b0949..5a836894b51 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
@@ -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();
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index d498b6f0c4f..2b70cb84c67 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -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' } });
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index bb0945a1f3e..9d389db4060 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -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 }));
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
new file mode 100644
index 00000000000..f784f10ba28
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
@@ -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);
+ });
+});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index bf3f4e1364d..91dfc61198c 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -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',
+ },
+ },
+ ],
+ },
+ },
+ },
+};
diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
index 6c4f69fb036..063376499e2 100644
--- a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
+++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 9d516c8d7ac..92634777251 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -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
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index d7f07a1eadf..bd60bb53d49 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -564,8 +564,6 @@ Project:
- suggestion_commit_message
- merge_commit_template
- squash_commit_template
-ProjectTracingSetting:
-- external_url
Author:
- name
ProjectFeature:
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2d84c1b843e..d2e281234fb 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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') }
diff --git a/spec/models/project_tracing_setting_spec.rb b/spec/models/project_tracing_setting_spec.rb
deleted file mode 100644
index a7e4e557b25..00000000000
--- a/spec/models/project_tracing_setting_spec.rb
+++ /dev/null
@@ -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/'&gt;})
- end
- end
- end
- end
-end
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 93e4e72f78f..faff5fdd6a7 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -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)
+ let_it_be(:client_key) { create(:error_tracking_client_key, project: project) }
+
+ before do
params[:public_key] = client_key.public_key
+ end
- post api("/internal/error_tracking_allowed"), params: params,
- headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
+ 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
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 26e0adc11b3..2d925620a91 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -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
- 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 collection_uri
+ "/projects/#{project.id}/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 match_collection_schema
+ match_response_schema('public_api/v4/project_hooks')
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 hook_uri(hook_id = hook.id)
+ "/projects/#{project.id}/hooks/#{hook_id}"
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 match_hook_schema
+ match_response_schema('public_api/v4/project_hook')
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)
+ 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
- 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
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 2460a98129f..0f1dbea2e73 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -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
+ def match_collection_schema
+ match_response_schema('public_api/v4/system_hooks')
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 hook_uri(hook_id = hook.id)
+ "/hooks/#{hook_id}"
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
- 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
+ def match_hook_schema
+ match_response_schema('public_api/v4/system_hook')
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 event_names
+ %i[
+ push_events
+ tag_push_events
+ merge_requests_events
+ repository_update_events
+ ]
end
- it "responds with 400 if url not given" do
- post api("/hooks", admin)
-
- expect(response).to have_gitlab_http_status(:bad_request)
+ def hook_param_overrides
+ {}
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)
+ let(:update_params) do
+ {
+ push_events: false,
+ tag_push_events: true
+ }
end
- it "does not create new hook without url" do
- expect do
- post api("/hooks", admin)
- end.not_to change { SystemHook.count }
+ let(:default_values) do
+ { repository_update_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
- 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
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index 3ee867ba6f2..bee91c358ce 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -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
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index da4a0e8da80..dbaecc6a233 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -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
diff --git a/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb b/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb
new file mode 100644
index 00000000000..79de2aedf3b
--- /dev/null
+++ b/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb
@@ -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
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 12a4c6d7583..79c7c1891ac 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -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
diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
new file mode 100644
index 00000000000..013945bd578
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
@@ -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
diff --git a/spec/views/projects/tracing/show.html.haml_spec.rb b/spec/views/projects/tracing/show.html.haml_spec.rb
deleted file mode 100644
index 96dc6a18fc7..00000000000
--- a/spec/views/projects/tracing/show.html.haml_spec.rb
+++ /dev/null
@@ -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/'&gt;" }
-
- 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
diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb
index dbe3e6edd98..64837c45862 100644
--- a/tooling/danger/project_helper.rb
+++ b/tooling/danger/project_helper.rb
@@ -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,