Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-23 12:09:30 +00:00
parent 17f2e5035c
commit f46d20e508
72 changed files with 1208 additions and 1051 deletions

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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>

View File

@ -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

View File

@ -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"

View File

@ -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 = [

View File

@ -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);

View File

@ -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: {

View File

@ -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>

View File

@ -0,0 +1,13 @@
query projectWorkItems($searchTerm: String, $projectPath: ID!) {
workspace: project(fullPath: $projectPath) {
id
workItems(search: $searchTerm) {
edges {
node {
id
title
}
}
}
}
}

View File

@ -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

View File

@ -683,7 +683,6 @@ module ProjectsHelper
product_analytics
metrics_dashboard
feature_flags
tracings
terraform
]
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 } }

View File

@ -1,2 +0,0 @@
= link_to project_settings_operations_path(@project), title: _('Configure Tracing'), class: 'gl-button btn btn-confirm' do
= _('Add Jaeger URL')

View File

@ -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'

View File

@ -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) } }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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:

View File

@ -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:

View File

@ -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>`.

View File

@ -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

View File

@ -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

View File

@ -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

21
lib/api/hooks/test.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -2043,9 +2043,6 @@ msgstr ""
msgid "Add CONTRIBUTING"
msgstr ""
msgid "Add Jaeger URL"
msgstr ""
msgid "Add Kubernetes cluster"
msgstr ""
@ -2076,9 +2073,6 @@ msgstr ""
msgid "Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
msgstr ""
msgid "Add a Jaeger URL to replace this page with a link to your Jaeger server. You first need to %{link_start_tag}install Jaeger%{link_end_tag}."
msgstr ""
msgid "Add a Terms of Service agreement and Privacy Policy for users of this GitLab instance."
msgstr ""
@ -7819,6 +7813,9 @@ msgstr ""
msgid "Child issues and epics"
msgstr ""
msgid "Children"
msgstr ""
msgid "Chinese language support using"
msgstr ""
@ -9463,9 +9460,6 @@ msgstr ""
msgid "Configure Sentry integration for error tracking"
msgstr ""
msgid "Configure Tracing"
msgstr ""
msgid "Configure a %{codeStart}.gitlab-webide.yml%{codeEnd} file in the %{codeStart}.gitlab%{codeEnd} directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
msgstr ""
@ -12374,15 +12368,15 @@ msgstr ""
msgid "DependencyProxy|Enable Dependency Proxy"
msgstr ""
msgid "DependencyProxy|Enable the Dependency Proxy and settings for clearing the cache."
msgstr ""
msgid "DependencyProxy|Image list"
msgstr ""
msgid "DependencyProxy|Scheduled for deletion"
msgstr ""
msgid "DependencyProxy|Storage settings"
msgstr ""
msgid "DependencyProxy|There are no images in the cache"
msgstr ""
@ -12802,9 +12796,6 @@ msgstr ""
msgid "Deprecations|The logs and tracing features were deprecated in GitLab 14.7 and are %{epicStart} scheduled for removal %{epicEnd} in GitLab 15.0."
msgstr ""
msgid "Deprecations|The logs and tracing features were deprecated in GitLab 14.7, and are %{removal_link_start} scheduled for removal %{link_end} in GitLab 15.0. For information on a possible replacement, %{opstrace_link_start} learn more about Opstrace %{link_end}."
msgstr ""
msgid "Deprecations|The metrics feature was deprecated in GitLab 14.7."
msgstr ""
@ -17313,9 +17304,6 @@ msgstr ""
msgid "GitLab username"
msgstr ""
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
msgstr ""
msgid "GitLab uses %{linkStart}Sidekiq%{linkEnd} to process background jobs"
msgstr ""
@ -40278,9 +40266,6 @@ msgstr ""
msgid "TotalRefCountIndicator|1000+"
msgstr ""
msgid "Tracing"
msgstr ""
msgid "Track groups of issues that share a theme, across projects and milestones"
msgstr ""
@ -40503,9 +40488,6 @@ msgstr ""
msgid "Trigger|invalid"
msgstr ""
msgid "Troubleshoot and monitor your application with tracing"
msgstr ""
msgid "Trusted"
msgstr ""
@ -44471,9 +44453,6 @@ msgstr ""
msgid "Your new comment"
msgstr ""
msgid "Your password isn't required to view this page. If a password or any other personal details are requested, please contact your administrator to report abuse."
msgstr ""
msgid "Your password reset token has expired."
msgstr ""

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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=$?

View File

@ -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

View File

@ -1,8 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :project_tracing_setting do
project
external_url { 'https://example.com' }
end
end

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -0,0 +1,10 @@
{
"type": "array",
"items": {
"type": "object",
"properties" : {
"$ref": "./project_hook.json"
}
}
}

View File

@ -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
}

View File

@ -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', () => {

View File

@ -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();

View File

@ -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' } });

View File

@ -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 }));

View File

@ -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);
});
});

View File

@ -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',
},
},
],
},
},
},
};

View File

@ -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

View File

@ -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

View File

@ -564,8 +564,6 @@ Project:
- suggestion_commit_message
- merge_commit_template
- squash_commit_template
ProjectTracingSetting:
- external_url
Author:
- name
ProjectFeature:

View File

@ -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') }

View File

@ -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

View File

@ -51,60 +51,93 @@ RSpec.describe API::Internal::Base do
end
end
describe 'GET /internal/error_tracking_allowed' do
describe 'GET /internal/error_tracking/allowed' do
let_it_be(:project) { create(:project) }
let(:params) { { project_id: project.id, public_key: 'key' } }
let(:headers) do
{ API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
end
subject(:send_request) do
post api('/internal/error_tracking/allowed'), params: params, headers: headers
end
context 'when the secret header is missing' do
let(:headers) { {} }
it 'responds with unauthorized entity' do
post api("/internal/error_tracking_allowed"), params: params
send_request
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when some params are missing' do
let(:params) { { project_id: project.id } }
it 'responds with unprocessable entity' do
post api("/internal/error_tracking_allowed"), params: params.except(:public_key),
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
send_request
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
context 'when the error tracking is disabled' do
context 'when public_key is unknown' do
it 'returns enabled: false' do
create(:error_tracking_client_key, project: project, active: false)
post api("/internal/error_tracking_allowed"), params: params,
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
send_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ 'enabled' => false })
expect(json_response).to eq('enabled' => false)
end
end
context 'when the error tracking record does not exist' do
it 'returns enabled: false' do
post api("/internal/error_tracking_allowed"), params: params,
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
context 'when unknown project_id is unknown' do
it 'responds with 404 not found' do
params[:project_id] = non_existing_record_id
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ 'enabled' => false })
end
send_request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the error tracking is disabled' do
it 'returns enabled: false' do
create(:error_tracking_client_key, :disabled, project: project)
send_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq('enabled' => false)
end
end
context 'when the error tracking is enabled' do
it 'returns enabled: true' do
client_key = create(:error_tracking_client_key, project: project, active: true)
params[:public_key] = client_key.public_key
let_it_be(:client_key) { create(:error_tracking_client_key, project: project) }
post api("/internal/error_tracking_allowed"), params: params,
headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
before do
params[:public_key] = client_key.public_key
end
it 'returns enabled: true' do
send_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ 'enabled' => true })
expect(json_response).to eq('enabled' => true)
end
context 'when feature flag use_click_house_database_for_error_tracking is disabled' do
before do
stub_feature_flags(use_click_house_database_for_error_tracking: false)
end
it 'returns enabled: false' do
send_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq('enabled' => false)
end
end
end
end

View File

@ -3,10 +3,10 @@
require 'spec_helper'
RSpec.describe API::ProjectHooks, 'ProjectHooks' do
let(:user) { create(:user) }
let(:user3) { create(:user) }
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:hook) do
let_it_be(:user) { create(:user) }
let_it_be(:user3) { create(:user) }
let_it_be(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let_it_be_with_refind(:hook) do
create(:project_hook,
:all_events_enabled,
project: project,
@ -15,232 +15,55 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
push_events_branch_filter: 'master')
end
before do
before_all do
project.add_maintainer(user)
project.add_developer(user3)
end
describe "GET /projects/:id/hooks" do
context "authorized user" do
it "returns project hooks" do
get api("/projects/#{project.id}/hooks", user)
it_behaves_like 'web-hook API endpoints', '/projects/:id' do
let(:unauthorized_user) { user3 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(response).to include_pagination_headers
expect(json_response.count).to eq(1)
expect(json_response.first['url']).to eq("http://example.com")
expect(json_response.first['issues_events']).to eq(true)
expect(json_response.first['confidential_issues_events']).to eq(true)
expect(json_response.first['push_events']).to eq(true)
expect(json_response.first['merge_requests_events']).to eq(true)
expect(json_response.first['tag_push_events']).to eq(true)
expect(json_response.first['note_events']).to eq(true)
expect(json_response.first['confidential_note_events']).to eq(true)
expect(json_response.first['job_events']).to eq(true)
expect(json_response.first['pipeline_events']).to eq(true)
expect(json_response.first['wiki_page_events']).to eq(true)
expect(json_response.first['deployment_events']).to eq(true)
expect(json_response.first['releases_events']).to eq(true)
expect(json_response.first['enable_ssl_verification']).to eq(true)
expect(json_response.first['push_events_branch_filter']).to eq('master')
expect(json_response.first['alert_status']).to eq('executable')
expect(json_response.first['disabled_until']).to be_nil
end
def scope
project.hooks
end
context "unauthorized user" do
it "does not access project hooks" do
get api("/projects/#{project.id}/hooks", user3)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe "GET /projects/:id/hooks/:hook_id" do
context "authorized user" do
it "returns a project hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['url']).to eq(hook.url)
expect(json_response['issues_events']).to eq(hook.issues_events)
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
expect(json_response['push_events']).to eq(hook.push_events)
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
expect(json_response['confidential_note_events']).to eq(hook.confidential_note_events)
expect(json_response['job_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['releases_events']).to eq(hook.releases_events)
expect(json_response['deployment_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
expect(json_response['alert_status']).to eq(hook.alert_status.to_s)
expect(json_response['disabled_until']).to be_nil
end
it "returns a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/#{non_existing_record_id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
def collection_uri
"/projects/#{project.id}/hooks"
end
context "unauthorized user" do
it "does not access an existing hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe "POST /projects/:id/hooks" do
it "adds hook to project" do
expect do
post(api("/projects/#{project.id}/hooks", user),
params: { url: "http://example.com", issues_events: true,
confidential_issues_events: true, wiki_page_events: true,
job_events: true, deployment_events: true, releases_events: true,
push_events_branch_filter: 'some-feature-branch' })
end.to change {project.hooks.count}.by(1)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['url']).to eq('http://example.com')
expect(json_response['issues_events']).to eq(true)
expect(json_response['confidential_issues_events']).to eq(true)
expect(json_response['push_events']).to eq(true)
expect(json_response['merge_requests_events']).to eq(false)
expect(json_response['tag_push_events']).to eq(false)
expect(json_response['note_events']).to eq(false)
expect(json_response['confidential_note_events']).to eq(nil)
expect(json_response['job_events']).to eq(true)
expect(json_response['pipeline_events']).to eq(false)
expect(json_response['wiki_page_events']).to eq(true)
expect(json_response['deployment_events']).to eq(true)
expect(json_response['releases_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(true)
expect(json_response['push_events_branch_filter']).to eq('some-feature-branch')
expect(json_response).not_to include('token')
def match_collection_schema
match_response_schema('public_api/v4/project_hooks')
end
it "adds the token without including it in the response" do
token = "secret token"
expect do
post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", token: token }
end.to change {project.hooks.count}.by(1)
expect(response).to have_gitlab_http_status(:created)
expect(json_response["url"]).to eq("http://example.com")
expect(json_response).not_to include("token")
hook = project.hooks.find(json_response["id"])
expect(hook.url).to eq("http://example.com")
expect(hook.token).to eq(token)
def hook_uri(hook_id = hook.id)
"/projects/#{project.id}/hooks/#{hook_id}"
end
it "returns a 400 error if url not given" do
post api("/projects/#{project.id}/hooks", user)
expect(response).to have_gitlab_http_status(:bad_request)
def match_hook_schema
match_response_schema('public_api/v4/project_hook')
end
it "returns a 422 error if url not valid" do
post api("/projects/#{project.id}/hooks", user), params: { url: "ftp://example.com" }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
def event_names
%i[
push_events
tag_push_events
merge_requests_events
issues_events
confidential_issues_events
note_events
confidential_note_events
pipeline_events
wiki_page_events
job_events
deployment_events
releases_events
]
end
it "returns a 422 error if branch filter is not valid" do
post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
describe "PUT /projects/:id/hooks/:hook_id" do
it "updates an existing project hook" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user),
params: { url: 'http://example.org', push_events: false, job_events: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['url']).to eq('http://example.org')
expect(json_response['issues_events']).to eq(hook.issues_events)
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
expect(json_response['push_events']).to eq(false)
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
expect(json_response['confidential_note_events']).to eq(hook.confidential_note_events)
expect(json_response['job_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['releases_events']).to eq(hook.releases_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
let(:default_values) do
{ push_events: true, confidential_note_events: nil }
end
it "adds the token without including it in the response" do
token = "secret token"
put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: "http://example.org", token: token }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["url"]).to eq("http://example.org")
expect(json_response).not_to include("token")
expect(hook.reload.url).to eq("http://example.org")
expect(hook.reload.token).to eq(token)
end
it "returns 404 error if hook id not found" do
put api("/projects/#{project.id}/hooks/#{non_existing_record_id}", user), params: { url: 'http://example.org' }
expect(response).to have_gitlab_http_status(:not_found)
end
it "returns 400 error if url is not given" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user)
expect(response).to have_gitlab_http_status(:bad_request)
end
it "returns a 422 error if url is not valid" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: 'ftp://example.com' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
describe "DELETE /projects/:id/hooks/:hook_id" do
it "deletes hook from project" do
expect do
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
end.to change {project.hooks.count}.by(-1)
end
it "returns a 404 error when deleting non existent hook" do
delete api("/projects/#{project.id}/hooks/42", user)
expect(response).to have_gitlab_http_status(:not_found)
end
it "returns a 404 error if hook id not given" do
delete api("/projects/#{project.id}/hooks", user)
expect(response).to have_gitlab_http_status(:not_found)
end
it "returns a 404 if a user attempts to delete project hooks they do not own" do
test_user = create(:user)
other_project = create(:project)
other_project.add_maintainer(test_user)
delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user)
expect(response).to have_gitlab_http_status(:not_found)
expect(WebHook.exists?(hook.id)).to be_truthy
end
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/hooks/#{hook.id}", user) }
end
it_behaves_like 'web-hook API endpoints with branch-filter', '/projects/:id'
end
end

View File

@ -3,221 +3,58 @@
require 'spec_helper'
RSpec.describe API::SystemHooks do
include StubRequests
let_it_be(:non_admin) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be_with_refind(:hook) { create(:system_hook, url: "http://example.com") }
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let!(:hook) { create(:system_hook, url: "http://example.com") }
it_behaves_like 'web-hook API endpoints', '' do
let(:user) { admin }
let(:unauthorized_user) { non_admin }
before do
stub_full_request(hook.url, method: :post)
end
describe "GET /hooks" do
context "when no user" do
it "returns authentication error" do
get api("/hooks")
expect(response).to have_gitlab_http_status(:unauthorized)
end
def scope
SystemHook
end
context "when not an admin" do
it "returns forbidden error" do
get api("/hooks", user)
expect(response).to have_gitlab_http_status(:forbidden)
end
def collection_uri
"/hooks"
end
context "when authenticated as admin" do
it "returns an array of hooks" do
get api("/hooks", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('public_api/v4/system_hooks')
expect(json_response.first).not_to have_key("token")
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
expect(json_response.first['merge_requests_events']).to be false
expect(json_response.first['repository_update_events']).to be true
expect(json_response.first['enable_ssl_verification']).to be true
expect(json_response.first['disabled_until']).to be nil
expect(json_response.first['alert_status']).to eq 'executable'
end
end
end
describe "GET /hooks/:id" do
context "when no user" do
it "returns authentication error" do
get api("/hooks/#{hook.id}")
expect(response).to have_gitlab_http_status(:unauthorized)
end
def match_collection_schema
match_response_schema('public_api/v4/system_hooks')
end
context "when not an admin" do
it "returns forbidden error" do
get api("/hooks/#{hook.id}", user)
expect(response).to have_gitlab_http_status(:forbidden)
end
def hook_uri(hook_id = hook.id)
"/hooks/#{hook_id}"
end
context "when authenticated as admin" do
it "gets a hook", :aggregate_failures do
get api("/hooks/#{hook.id}", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response).to match(
'id' => be(hook.id),
'url' => eq(hook.url),
'created_at' => eq(hook.created_at.iso8601(3)),
'push_events' => be(hook.push_events),
'tag_push_events' => be(hook.tag_push_events),
'merge_requests_events' => be(hook.merge_requests_events),
'repository_update_events' => be(hook.repository_update_events),
'enable_ssl_verification' => be(hook.enable_ssl_verification),
'alert_status' => eq(hook.alert_status.to_s),
'disabled_until' => eq(hook.disabled_until&.iso8601(3))
)
end
context 'the hook is disabled' do
before do
hook.disable!
end
it "has the correct alert status", :aggregate_failures do
get api("/hooks/#{hook.id}", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response).to include('alert_status' => 'disabled')
end
end
context 'the hook is backed-off' do
before do
hook.backoff!
end
it "has the correct alert status", :aggregate_failures do
get api("/hooks/#{hook.id}", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response).to include(
'alert_status' => 'temporarily_disabled',
'disabled_until' => hook.disabled_until.iso8601(3)
)
end
end
it 'returns 404 if the system hook does not exist' do
get api("/hooks/#{non_existing_record_id}", admin)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe "POST /hooks" do
it "creates new hook" do
expect do
post api("/hooks", admin), params: { url: 'http://example.com' }
end.to change { SystemHook.count }.by(1)
def match_hook_schema
match_response_schema('public_api/v4/system_hook')
end
it "responds with 400 if url not given" do
post api("/hooks", admin)
expect(response).to have_gitlab_http_status(:bad_request)
def event_names
%i[
push_events
tag_push_events
merge_requests_events
repository_update_events
]
end
it "responds with 400 if url is invalid" do
post api("/hooks", admin), params: { url: 'hp://mep.mep' }
expect(response).to have_gitlab_http_status(:bad_request)
def hook_param_overrides
{}
end
it "does not create new hook without url" do
expect do
post api("/hooks", admin)
end.not_to change { SystemHook.count }
let(:update_params) do
{
push_events: false,
tag_push_events: true
}
end
it 'sets default values for events' do
stub_full_request('http://mep.mep', method: :post)
post api('/hooks', admin), params: { url: 'http://mep.mep' }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response['enable_ssl_verification']).to be true
expect(json_response['push_events']).to be false
expect(json_response['tag_push_events']).to be false
expect(json_response['merge_requests_events']).to be false
expect(json_response['repository_update_events']).to be true
let(:default_values) do
{ repository_update_events: true }
end
it 'sets explicit values for events' do
stub_full_request('http://mep.mep', method: :post)
post api('/hooks', admin),
params: {
url: 'http://mep.mep',
enable_ssl_verification: false,
push_events: true,
tag_push_events: true,
merge_requests_events: true,
repository_update_events: false
}
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response['enable_ssl_verification']).to be false
expect(json_response['push_events']).to be true
expect(json_response['tag_push_events']).to be true
expect(json_response['merge_requests_events']).to be true
expect(json_response['repository_update_events']).to be false
end
end
describe 'POST /hooks/:id' do
it "returns and trigger hook by id" do
post api("/hooks/#{hook.id}", admin)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['event_name']).to eq('project_create')
end
it "returns 404 on failure" do
post api("/hooks/404", admin)
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe "DELETE /hooks/:id" do
it "deletes a hook" do
expect do
delete api("/hooks/#{hook.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { SystemHook.count }.by(-1)
end
it 'returns 404 if the system hook does not exist' do
delete api("/hooks/#{non_existing_record_id}", admin)
expect(response).to have_gitlab_http_status(:not_found)
end
it_behaves_like '412 response' do
let(:request) { api("/hooks/#{hook.id}", admin) }
end
it_behaves_like 'web-hook API endpoints test hook', ''
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,