Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2848e0bf51
commit
ab5132651a
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -2,6 +2,27 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 18.0.2 (2025-06-11)
|
||||
|
||||
### Fixed (5 changes)
|
||||
|
||||
- [Fix Upgrade to 18.0: No such column](https://gitlab.com/gitlab-org/security/gitlab/-/commit/bde20c3f31d324493d032be57be4465f0919760e)
|
||||
- [Fix IDE links returns about:blank in old code dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/633864727f574f9d9b93826bb76d66a790382915)
|
||||
- [Fix the title/body issue for todo apis when it is a duo todo](https://gitlab.com/gitlab-org/security/gitlab/-/commit/d8080ea15af34cf804ce024b207f2fa4817c87a6) **GitLab Enterprise Edition**
|
||||
- [Fix gitpod button is missing in the edit dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/4bbef760c63924f2821233d98dc04c1982751430)
|
||||
- [Move fork_networks organization_id NOT NULL to post-migrate](https://gitlab.com/gitlab-org/security/gitlab/-/commit/2bbea09c16044981bf316dd43544a87e4bf67147)
|
||||
|
||||
### Security (8 changes)
|
||||
|
||||
- [Protect webhook from excessive payload lengths](https://gitlab.com/gitlab-org/security/gitlab/-/commit/990fae5b6be86c6769c2086578ae2096762e21a8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5059))
|
||||
- [Endless Redirect Loop in any project when query param "format" is "git"](https://gitlab.com/gitlab-org/security/gitlab/-/commit/fdbfb6cd14973800abeec182823bcfa647a1a5a8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5038))
|
||||
- [Backport for "Add validation for board name length" to 18-0 stable](https://gitlab.com/gitlab-org/security/gitlab/-/commit/ba616a03359751fc3add6f8504c79f4381efa703) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5044))
|
||||
- [Fix # #1329 - IDOR in compliance framework export endpoint](https://gitlab.com/gitlab-org/security/gitlab/-/commit/ffea57e8e171b120f5f66fe81da39a21e5ab0258) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5041))
|
||||
- [Fix authorization for compliance frameworks projects](https://gitlab.com/gitlab-org/security/gitlab/-/commit/0d783852162009bc5286a939534f2a5e2f1ae7ef) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5034))
|
||||
- [security: Git redirection inconsistency](https://gitlab.com/gitlab-org/security/gitlab/-/commit/3fb95759edb3e7729b981bf48140ef9a05a32761) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5020))
|
||||
- [Fix XSS with CSP bypass in JSON tables](https://gitlab.com/gitlab-org/security/gitlab/-/commit/fcfebf2f188ed90eea3f7db92ebeedcbadc6504d) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5022))
|
||||
- [Limit HTTP response size](https://gitlab.com/gitlab-org/security/gitlab/-/commit/f8bf80825e1bd802be7be374905600059abd2726) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5014))
|
||||
|
||||
## 18.0.1 (2025-05-21)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
|
@ -856,6 +877,24 @@ entry.
|
|||
- [Finalize migration BackfillContainerRepositoryStatesProjectId](https://gitlab.com/gitlab-org/gitlab/-/commit/78f333c76a39d0a85938318b3be49905c19074e6) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185869))
|
||||
- [Finalize migration BackfillPackagesRpmMetadataProjectId](https://gitlab.com/gitlab-org/gitlab/-/commit/d066d88be1fff7cfcf64017124af797e085a4b4f) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/184553))
|
||||
|
||||
## 17.11.4 (2025-06-11)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Fix gitpod button is missing in the edit dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/813a005dc240c1bfafc313ded694317a96f1a877)
|
||||
- [Attempt to migrate ci_runner_taggings table (try 2)](https://gitlab.com/gitlab-org/security/gitlab/-/commit/706a075f79838d5d8421c5eae2e96a7601164201)
|
||||
|
||||
### Security (8 changes)
|
||||
|
||||
- [Protect webhook from excessive payload lengths](https://gitlab.com/gitlab-org/security/gitlab/-/commit/a0d74cdeed26661b221446efc90fb5bd19b54d95) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5060))
|
||||
- [Endless Redirect Loop in any project when query param "format" is "git"](https://gitlab.com/gitlab-org/security/gitlab/-/commit/24d25f0b270337679bcfe282370ad169d137471f) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5039))
|
||||
- [Backport for "Add validation for board name length" to 17-11-stable](https://gitlab.com/gitlab-org/security/gitlab/-/commit/5ed051286369ec256431faeb44a16c848b6d0edc) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5045))
|
||||
- [Fix # #1329 - IDOR in compliance framework export endpoint](https://gitlab.com/gitlab-org/security/gitlab/-/commit/071c88429e0974fdf1c0d67e7ba9d1f419843244) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5043))
|
||||
- [security: Git redirection inconsistency](https://gitlab.com/gitlab-org/security/gitlab/-/commit/373f9840af59eae05b14ea200fa10c1e4ecd7367) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5019))
|
||||
- [Fix XSS with CSP bypass in JSON tables](https://gitlab.com/gitlab-org/security/gitlab/-/commit/862a14acb446e9f7ce962404d8d472b19d832ff8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4987))
|
||||
- [Limit HTTP response size](https://gitlab.com/gitlab-org/security/gitlab/-/commit/94d20db29203681d75da5642fe4d1da51238863e) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5015))
|
||||
- [Fix authorization for compliance frameworks projects](https://gitlab.com/gitlab-org/security/gitlab/-/commit/0eecdfe1df4254e2674efe9c0e309d9325db5c4b) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5035))
|
||||
|
||||
## 17.11.3 (2025-05-21)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
|
@ -1683,6 +1722,23 @@ entry.
|
|||
- [Remove feature flag allow_merge_request_pipelines_from_fork](https://gitlab.com/gitlab-org/gitlab/-/commit/b62f9187a57cc5ba66ce26889516cc55a425181a) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182862))
|
||||
- [Finalize migration BackfillNewAuditEventTables](https://gitlab.com/gitlab-org/gitlab/-/commit/1bc0f07ffd3af5b9fab8a0ea0b1af5f2759d25db) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181881))
|
||||
|
||||
## 17.10.8 (2025-06-11)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Fix gitpod button is missing in the edit dropdown](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c3ad6f66e6f17a5bf8fa2489a7335dfa58fc55a6)
|
||||
- [Attempt to migrate ci_runner_taggings table (try 2)](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c2520ea439dcb4fee531fcc39efc85ab4b607a6c)
|
||||
|
||||
### Security (7 changes)
|
||||
|
||||
- [Protect webhook from excessive payload lengths](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1fb7390786ae5c22ec7f1bc172423a76835aa14c) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5061))
|
||||
- [Endless Redirect Loop in any project when query param "format" is "git"](https://gitlab.com/gitlab-org/security/gitlab/-/commit/fddb00a30506eb534dc9e1f5c1923eee3e33c0b3) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5040))
|
||||
- [Backport for "Add validation for board name length" to 17-10-stable](https://gitlab.com/gitlab-org/security/gitlab/-/commit/a69cf8ef367ef1897158af0619cd537fe5d2a5df) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5046))
|
||||
- [Fix # #1329 - IDOR in compliance framework export endpoint](https://gitlab.com/gitlab-org/security/gitlab/-/commit/7b4f9e9fb7411a18185ada44dc88dd264e6a228b) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5042))
|
||||
- [security: Git redirection inconsistency](https://gitlab.com/gitlab-org/security/gitlab/-/commit/12003cbfb9b4081a352724922e6ed9aa97656ace) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4900))
|
||||
- [Fix XSS with CSP bypass in JSON tables](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1b02f9ed79b3a999baae5c02fa4f26c487927cba) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4988))
|
||||
- [Limit HTTP response size](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1411cb581f68400b5370d694cce3c67e5f0e2294) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5016))
|
||||
|
||||
## 17.10.7 (2025-05-21)
|
||||
|
||||
### Security (9 changes)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3a09d6c84e9ac6d32832372afd9c6100a56a2b24
|
||||
060d5cb6dbc76ef1344a8fabf21cbae551ecab90
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { GlTable, GlFormInput } from '@gitlab/ui';
|
||||
import { memoize } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import { sanitize, defaultConfig } from '~/lib/dompurify';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
|
@ -74,6 +74,11 @@ export default {
|
|||
return `cell(${field.key})`;
|
||||
},
|
||||
},
|
||||
safeHtmlConfig: {
|
||||
...defaultConfig,
|
||||
FORBID_ATTR: [...defaultConfig.FORBID_ATTR, 'class', 'style'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -92,11 +97,11 @@ export default {
|
|||
class="!gl-mt-0"
|
||||
>
|
||||
<template v-if="isHtmlSafe" #cell()="data">
|
||||
<div v-safe-html="data.value"></div>
|
||||
<div v-safe-html:[$options.safeHtmlConfig]="data.value"></div>
|
||||
</template>
|
||||
<template v-else #cell()="data">{{ data.value }}</template>
|
||||
<template v-if="caption" #table-caption>
|
||||
<small v-if="isHtmlSafe" v-safe-html="caption"></small>
|
||||
<small v-if="isHtmlSafe" v-safe-html:[$options.safeHtmlConfig]="caption"></small>
|
||||
<small v-else>{{ caption }}</small>
|
||||
</template>
|
||||
</gl-table>
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ const mountParseError = (element) => {
|
|||
});
|
||||
};
|
||||
|
||||
const mountJSONTableVueComponent = (userData, element) => {
|
||||
const { fields = [], items = [], filter, caption, isHtmlSafe } = userData;
|
||||
const mountJSONTableVueComponent = (userData, element, isHtmlSafe = false) => {
|
||||
const { fields = [], items = [], filter, caption } = userData;
|
||||
const container = document.createElement('div');
|
||||
|
||||
element.classList.add('js-json-table');
|
||||
|
|
@ -97,7 +97,7 @@ const renderTableHTML = (element) => {
|
|||
),
|
||||
);
|
||||
|
||||
mountJSONTableVueComponent({ fields, filter, caption, items, isHtmlSafe: markdown }, parent);
|
||||
mountJSONTableVueComponent({ fields, filter, caption, items }, parent, Boolean(markdown));
|
||||
} catch (e) {
|
||||
mountParseError(parent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
i18n: {
|
||||
inputs: {
|
||||
name: {
|
||||
placeholder: __('My awesome group'),
|
||||
placeholder: __('My group'),
|
||||
description: s__(
|
||||
'Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.',
|
||||
),
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
],
|
||||
inputAttrs: {
|
||||
width: { md: 'lg' },
|
||||
placeholder: __('My awesome group'),
|
||||
placeholder: __('My group'),
|
||||
},
|
||||
groupAttrs: {
|
||||
description: s__(
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ export default {
|
|||
:pattern="$options.projectNamePattern"
|
||||
name="name"
|
||||
required
|
||||
:placeholder="s__('ProjectsNew|My awesome project')"
|
||||
:placeholder="s__('ProjectsNew|My project')"
|
||||
data-testid="project-name"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ export default {
|
|||
i18n: {
|
||||
settingsBlock: {
|
||||
title: __('Advanced'),
|
||||
description: s__('Organization|Perform advanced options such as deleting the organization.'),
|
||||
description: s__(
|
||||
'Organization|Perform advanced options such as changing the organization URL.',
|
||||
),
|
||||
},
|
||||
},
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ export default {
|
|||
inject: ['organization'],
|
||||
i18n: {
|
||||
cardHeaderTitle: s__('Organization|Change organization URL'),
|
||||
cardHeaderDescription: s__(
|
||||
"Organization|Changing an organization's URL can have unintended side effects.",
|
||||
),
|
||||
submitButtonText: s__('Organization|Change organization URL'),
|
||||
errorMessage: s__(
|
||||
'Organization|An error occurred changing your organization URL. Please try again.',
|
||||
|
|
@ -45,11 +42,6 @@ export default {
|
|||
errors: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isSubmitButtonDisabled() {
|
||||
return this.formValues.path === this.organization.path;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
this.errors = [];
|
||||
|
|
@ -84,7 +76,6 @@ export default {
|
|||
]);
|
||||
} catch (error) {
|
||||
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
|
@ -100,7 +91,6 @@ export default {
|
|||
<div class="gl-flex gl-grow">
|
||||
<h4 class="gl-m-0 gl-text-base gl-leading-24">{{ $options.i18n.cardHeaderTitle }}</h4>
|
||||
</div>
|
||||
<p class="gl-m-0 gl-text-sm gl-text-subtle">{{ $options.i18n.cardHeaderDescription }}</p>
|
||||
</template>
|
||||
<gl-form :id="$options.formId">
|
||||
<gl-form-fields
|
||||
|
|
@ -120,14 +110,9 @@ export default {
|
|||
</template>
|
||||
</gl-form-fields>
|
||||
<div class="gl-flex gl-gap-3">
|
||||
<gl-button
|
||||
type="submit"
|
||||
variant="danger"
|
||||
class="js-no-auto-disable"
|
||||
:loading="loading"
|
||||
:disabled="isSubmitButtonDisabled"
|
||||
>{{ $options.i18n.submitButtonText }}</gl-button
|
||||
>
|
||||
<gl-button type="submit" variant="danger" class="js-no-auto-disable" :loading="loading">{{
|
||||
$options.i18n.submitButtonText
|
||||
}}</gl-button>
|
||||
</div>
|
||||
</gl-form>
|
||||
</gl-card>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import { visitUrlWithAlerts } from '~/lib/utils/url_utility';
|
||||
|
|
@ -11,19 +12,22 @@ import {
|
|||
} from '~/organizations/shared/constants';
|
||||
import FormErrorsAlert from '~/organizations/shared/components/errors_alert.vue';
|
||||
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
||||
import HelpPageLink from '~/vue_shared/components/help_page_link/help_page_link.vue';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPE_ORGANIZATION } from '~/graphql_shared/constants';
|
||||
import organizationUpdateMutation from '../graphql/mutations/organization_update.mutation.graphql';
|
||||
|
||||
export default {
|
||||
name: 'OrganizationSettings',
|
||||
components: { NewEditForm, SettingsBlock, FormErrorsAlert },
|
||||
components: { NewEditForm, SettingsBlock, FormErrorsAlert, HelpPageLink, GlSprintf },
|
||||
inject: ['organization'],
|
||||
i18n: {
|
||||
submitButtonText: __('Save changes'),
|
||||
settingsBlock: {
|
||||
title: s__('Organization|Organization settings'),
|
||||
description: s__('Organization|Update your organization name, description, and avatar.'),
|
||||
description: s__(
|
||||
'Organization|Update your organization name, description, and avatar. %{linkStart}Learn more about organizations%{linkEnd}.',
|
||||
),
|
||||
},
|
||||
errorMessage: s__(
|
||||
'Organization|An error occurred updating your organization. Please try again.',
|
||||
|
|
@ -116,7 +120,15 @@ export default {
|
|||
:title="$options.i18n.settingsBlock.title"
|
||||
@toggle-expand="$emit('toggle-expand', $event)"
|
||||
>
|
||||
<template #description>{{ $options.i18n.settingsBlock.description }}</template>
|
||||
<template #description>
|
||||
<gl-sprintf :message="$options.i18n.settingsBlock.description">
|
||||
<template #link="{ content }">
|
||||
<help-page-link href="user/organization/_index.md" target="_blank">{{
|
||||
content
|
||||
}}</help-page-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
<template #default>
|
||||
<form-errors-alert v-model="errors" :scroll-on-error="true" />
|
||||
<new-edit-form
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export default {
|
|||
},
|
||||
inputAttrs: {
|
||||
class: 'gl-md-form-input-lg',
|
||||
placeholder: s__('ProjectsNewEdit|My awesome project'),
|
||||
placeholder: s__('ProjectsNewEdit|My project'),
|
||||
},
|
||||
},
|
||||
[FORM_FIELD_ID]: {
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export default {
|
|||
:state="form.fields['project[name]'].state"
|
||||
name="project[name]"
|
||||
required
|
||||
:placeholder="s__('ProjectsNew|My awesome project')"
|
||||
:placeholder="s__('ProjectsNew|My project')"
|
||||
data-testid="project-name-input"
|
||||
@input="updateSlug"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { GlLink, GlLabel } from '@gitlab/ui';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mapState } from 'vuex';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import GlSafeHtmlDirective from '~/vue_shared/directives/safe_html';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
|
|
@ -58,19 +59,21 @@ export default {
|
|||
computed: {
|
||||
...mapState(['query']),
|
||||
gfmCopyText() {
|
||||
return `\`${this.filePath}\``;
|
||||
return `\`${this.purePath(this.filePath)}\``;
|
||||
},
|
||||
highlightedFilePath() {
|
||||
const cleanFilePath = this.purePath(this.filePath);
|
||||
|
||||
if (!this?.query?.search) {
|
||||
return this.filePath;
|
||||
return cleanFilePath;
|
||||
}
|
||||
|
||||
if (containsPotentialRegex(this.query.search)) {
|
||||
return this.filePath;
|
||||
return cleanFilePath;
|
||||
}
|
||||
|
||||
const regex = new RegExp(`(${this.query.search})`, 'g');
|
||||
return this.filePath.replace(
|
||||
return cleanFilePath.replace(
|
||||
regex,
|
||||
(match, p1) =>
|
||||
`<span class="highlight-search-term ${this.systemMatchCodeTheme}">${p1}</span>`,
|
||||
|
|
@ -93,6 +96,9 @@ export default {
|
|||
trackHeaderClick() {
|
||||
this.trackEvent(EVENT_CLICK_HEADER_LINK);
|
||||
},
|
||||
purePath(path) {
|
||||
return sanitize(path, { ALLOWED_TAGS: [] });
|
||||
},
|
||||
},
|
||||
DEFAULT_HEADER_LABEL_COLOR,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -287,14 +287,16 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
const selectedWorkItemType = this.workItemTypes?.find(
|
||||
(workItemType) => workItemType.name === this.preselectedWorkItemType,
|
||||
);
|
||||
const selectedWorkItemType = this.findWorkItemType(this.preselectedWorkItemType);
|
||||
|
||||
if (selectedWorkItemType) {
|
||||
this.selectedWorkItemTypeId = selectedWorkItemType?.id;
|
||||
} else {
|
||||
this.showWorkItemTypeSelect = true;
|
||||
const defaultSelectedWorkItemType =
|
||||
this.findWorkItemType(WORK_ITEM_TYPE_NAME_ISSUE) || this.workItemTypes?.at(0);
|
||||
this.selectedWorkItemTypeId = defaultSelectedWorkItemType?.id;
|
||||
this.$emit('changeType', defaultSelectedWorkItemType);
|
||||
}
|
||||
},
|
||||
error() {
|
||||
|
|
@ -606,6 +608,9 @@ export default {
|
|||
document.removeEventListener('keydown', this.handleKeydown);
|
||||
},
|
||||
methods: {
|
||||
findWorkItemType(workItemTypeName) {
|
||||
return this.workItemTypes?.find((workItemType) => workItemType.name === workItemTypeName);
|
||||
},
|
||||
initialSelectedProject() {
|
||||
if (this.relatedItem) {
|
||||
return this.relatedItem.reference.substring(0, this.relatedItem.reference.lastIndexOf('#'));
|
||||
|
|
@ -913,12 +918,14 @@ export default {
|
|||
v-if="showProjectSelector"
|
||||
class="gl-max-w-26 gl-flex-grow"
|
||||
:label="__('Project')"
|
||||
label-for="create-work-item-project"
|
||||
>
|
||||
<work-item-projects-listbox
|
||||
v-model="selectedProjectFullPath"
|
||||
:full-path="fullPath"
|
||||
:is-group="isGroup"
|
||||
:current-project-name="namespaceFullName"
|
||||
toggle-id="create-work-item-project"
|
||||
/>
|
||||
</gl-form-group>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ export default {
|
|||
return {
|
||||
fullPath: this.rootPath,
|
||||
projectSearch: this.searchKey,
|
||||
includeArchived: false,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default {
|
|||
return __('Epics');
|
||||
}
|
||||
|
||||
return this.isGroup ? s__('WorkItem|Work items') : __('Issues');
|
||||
return __('Issues');
|
||||
},
|
||||
issueAsWorkItem() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ export default {
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
toggleId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -57,14 +62,15 @@ export default {
|
|||
return {
|
||||
fullPath: this.fullPath,
|
||||
projectSearch: this.searchKey,
|
||||
includeArchived: false,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return data.namespace?.projects?.nodes;
|
||||
},
|
||||
result() {
|
||||
this.selectedProject = this.findSelectedProject(this.selectedProjectFullPath);
|
||||
this.selectedProject =
|
||||
this.findSelectedProject(this.selectedProjectFullPath) || this.projects?.at(0);
|
||||
this.$emit('selectProject', this.selectedProject?.fullPath);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -78,7 +84,7 @@ export default {
|
|||
* name_with_namespace doesn't exist. Therefore we rely on
|
||||
* namespace directly.
|
||||
* */
|
||||
return this.selectedProject.nameWithNamespace || this.selectedProject.namespace;
|
||||
return this.selectedProject.name || this.selectedProject.namespace;
|
||||
}
|
||||
return this.selectedProjectFullPath && this.currentProjectName
|
||||
? this.currentProjectName
|
||||
|
|
@ -213,6 +219,7 @@ export default {
|
|||
is-check-centered
|
||||
:items="listItems"
|
||||
:selected="selectedProjectFullPath"
|
||||
:toggle-id="toggleId"
|
||||
:toggle-text="dropdownToggleText"
|
||||
:searching="projectsLoading"
|
||||
fluid-width
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
query namespaceProjectsForLinksWidget(
|
||||
$fullPath: ID!
|
||||
$projectSearch: String
|
||||
$includeArchived: Boolean = false
|
||||
) {
|
||||
query namespaceProjectsForLinksWidget($fullPath: ID!, $projectSearch: String) {
|
||||
namespace(fullPath: $fullPath) {
|
||||
id
|
||||
projects(
|
||||
|
|
@ -10,7 +6,8 @@ query namespaceProjectsForLinksWidget(
|
|||
includeSubgroups: true
|
||||
includeSiblingProjects: true
|
||||
sort: ACTIVITY_DESC
|
||||
includeArchived: $includeArchived
|
||||
includeArchived: false
|
||||
withIssuesEnabled: true
|
||||
) {
|
||||
nodes {
|
||||
id
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import { apolloProvider } from '~/graphql_shared/issuable_client';
|
|||
import App from './components/app.vue';
|
||||
import WorkItemBreadcrumb from './components/work_item_breadcrumb.vue';
|
||||
import activeDiscussionQuery from './components/design_management/graphql/client/active_design_discussion.query.graphql';
|
||||
import { WORK_ITEM_TYPE_NAME_EPIC } from './constants';
|
||||
import { createRouter } from './router';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
export const initWorkItemsRoot = ({ workspaceType, withTabs } = {}) => {
|
||||
export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {}) => {
|
||||
const el = document.querySelector('#js-work-items');
|
||||
|
||||
if (!el) {
|
||||
|
|
@ -46,7 +47,6 @@ export const initWorkItemsRoot = ({ workspaceType, withTabs } = {}) => {
|
|||
defaultBranch,
|
||||
initialSort,
|
||||
isSignedIn,
|
||||
workItemType,
|
||||
hasEpicsFeature,
|
||||
showNewWorkItem,
|
||||
canCreateEpic,
|
||||
|
|
@ -71,7 +71,7 @@ export const initWorkItemsRoot = ({ workspaceType, withTabs } = {}) => {
|
|||
|
||||
const breadcrumbParams = { workItemType, isGroup };
|
||||
|
||||
if (isGroup) {
|
||||
if (workItemType === WORK_ITEM_TYPE_NAME_EPIC) {
|
||||
listPath = epicsListPath;
|
||||
breadcrumbParams.listPath = epicsListPath;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import {
|
|||
ROUTES,
|
||||
RELATED_ITEM_ID_URL_QUERY_PARAM,
|
||||
BASE_ALLOWED_CREATE_TYPES,
|
||||
WORK_ITEM_TYPE_NAME_ISSUE,
|
||||
WORK_ITEM_TYPE_NAME_EPIC,
|
||||
WORK_ITEM_TYPE_NAME_INCIDENT,
|
||||
WORK_ITEM_TYPE_NAME_ISSUE,
|
||||
WORK_ITEM_TYPE_NAME_TASK,
|
||||
} from '../constants';
|
||||
import workItemRelatedItemQuery from '../graphql/work_item_related_item.query.graphql';
|
||||
|
|
@ -74,6 +75,9 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
isEpic() {
|
||||
return this.workItemType === WORK_ITEM_TYPE_NAME_EPIC;
|
||||
},
|
||||
isIncident() {
|
||||
return this.workItemType === WORK_ITEM_TYPE_NAME_INCIDENT;
|
||||
},
|
||||
|
|
@ -90,13 +94,16 @@ export default {
|
|||
|
||||
return [];
|
||||
},
|
||||
isNewGroupWorkItem() {
|
||||
return !this.isEpic && this.isGroup;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateWorkItemType(type) {
|
||||
this.workItemType = type;
|
||||
},
|
||||
workItemCreated({ workItem, numberOfDiscussionsResolved }) {
|
||||
if (this.$router && !this.isIncident) {
|
||||
if (this.$router && !this.isIncident && !this.isNewGroupWorkItem) {
|
||||
const routerPushObject = {
|
||||
name: ROUTES.workItem,
|
||||
params: { iid: workItem.iid },
|
||||
|
|
@ -159,7 +166,8 @@ export default {
|
|||
:is-group="isGroup"
|
||||
:related-item="relatedItem"
|
||||
:should-discard-draft="shouldDiscardDraft"
|
||||
:always-show-work-item-type-select="!isGroup"
|
||||
:always-show-work-item-type-select="!isEpic"
|
||||
:show-project-selector="isNewGroupWorkItem"
|
||||
:allowed-work-item-types="allowedWorkItemTypes"
|
||||
@updateType="updateWorkItemType($event)"
|
||||
@confirmCancel="handleConfirmCancellation"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module RapidDiffs
|
|||
end
|
||||
|
||||
def viewer_component
|
||||
return Viewers::NoPreviewComponent if empty_diff?
|
||||
return Viewers::NoPreviewComponent if @diff_file.no_preview?
|
||||
|
||||
if @diff_file.diffable_text?
|
||||
return Viewers::Text::ParallelViewComponent if @parallel_view
|
||||
|
|
@ -39,16 +39,12 @@ module RapidDiffs
|
|||
Viewers::NoPreviewComponent
|
||||
end
|
||||
|
||||
def empty_diff?
|
||||
@diff_file.collapsed? || !@diff_file.modified_file?
|
||||
end
|
||||
|
||||
def default_header
|
||||
render RapidDiffs::DiffFileHeaderComponent.new(diff_file: @diff_file)
|
||||
end
|
||||
|
||||
def total_rows
|
||||
return 0 unless !empty_diff? && @diff_file.diffable_text?
|
||||
return 0 unless !@diff_file.no_preview? && @diff_file.diffable_text?
|
||||
|
||||
count = 0
|
||||
@diff_file.viewer_hunks.each do |hunk|
|
||||
|
|
|
|||
|
|
@ -74,34 +74,36 @@ module RapidDiffs
|
|||
|
||||
return render_empty_state if diff_files.empty?
|
||||
|
||||
each_growing_slice(diff_files, 5, 2) do |slice|
|
||||
response.stream.write(render_diff_files_collection(slice, view_context))
|
||||
skipped = []
|
||||
diff_files.each do |diff_file|
|
||||
if diff_file.no_preview?
|
||||
skipped << diff_file
|
||||
else
|
||||
unless skipped.empty?
|
||||
response.stream.write(diff_files_collection(skipped).render_in(view_context))
|
||||
skipped = []
|
||||
end
|
||||
|
||||
response.stream.write(diff_file_component(diff_file).render_in(view_context))
|
||||
end
|
||||
end
|
||||
|
||||
response.stream.write(diff_files_collection(skipped).render_in(view_context)) unless skipped.empty?
|
||||
end
|
||||
|
||||
def each_growing_slice(collection, initial_size, growth_factor = 2)
|
||||
position = 0
|
||||
size = initial_size
|
||||
total = collection.size
|
||||
|
||||
while position < total
|
||||
yield collection.drop(position).first(size)
|
||||
|
||||
position = [position + size, total].min
|
||||
size = (size * growth_factor).to_i
|
||||
end
|
||||
def diff_file_component(diff_file)
|
||||
::RapidDiffs::DiffFileComponent.new(diff_file: diff_file, parallel_view: view == :parallel)
|
||||
end
|
||||
|
||||
def render_diff_files_collection(diff_files, view_context)
|
||||
def diff_files_collection(diff_files)
|
||||
::RapidDiffs::DiffFileComponent.with_collection(diff_files, parallel_view: view == :parallel)
|
||||
.render_in(view_context)
|
||||
end
|
||||
|
||||
def stream_diff_blobs(options, view_context)
|
||||
return render_empty_state if resource.diffs_for_streaming(options).count == 0
|
||||
|
||||
resource.diffs_for_streaming(options) do |diff_files_batch|
|
||||
response.stream.write(render_diff_files_collection(diff_files_batch, view_context))
|
||||
response.stream.write(diff_files_collection(diff_files_batch).render_in(view_context))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ module Glql
|
|||
end
|
||||
|
||||
def logs
|
||||
super.map do |log|
|
||||
graphql_logs = super.presence || [{}]
|
||||
|
||||
graphql_logs.map do |log|
|
||||
log.merge(
|
||||
glql_referer: request.headers["Referer"],
|
||||
glql_query_sha: query_sha
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ class Groups::UploadsController < Groups::ApplicationController
|
|||
before_action :disallow_new_uploads!, only: :show
|
||||
|
||||
feature_category :portfolio_management
|
||||
urgency :low, [:show]
|
||||
|
||||
private
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,9 @@ module Groups
|
|||
def handle_new_work_item_path
|
||||
return unless show_params[:iid] == 'new'
|
||||
|
||||
if group.supports_group_work_items?
|
||||
render :show
|
||||
else
|
||||
not_found
|
||||
end
|
||||
authenticate_user!
|
||||
|
||||
render :show
|
||||
end
|
||||
|
||||
def show_params
|
||||
|
|
|
|||
|
|
@ -88,8 +88,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
:can_create_branch, :create_merge_request
|
||||
]
|
||||
urgency :low, [
|
||||
:index, :calendar, :show, :new, :create, :edit, :update,
|
||||
:destroy, :move, :reorder, :designs, :toggle_subscription,
|
||||
:index, :calendar, :show, :new, :update, :move, :reorder, :designs, :toggle_subscription,
|
||||
:discussions, :bulk_update, :realtime_changes,
|
||||
:toggle_award_emoji, :mark_as_spam, :related_branches,
|
||||
:can_create_branch, :create_merge_request
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@ module Projects
|
|||
@merge_request
|
||||
end
|
||||
|
||||
def render_diff_files_collection(diff_files, view_context)
|
||||
def diff_file_component(diff_file)
|
||||
::RapidDiffs::MergeRequestDiffFileComponent
|
||||
.new(diff_file: diff_file, merge_request: @merge_request, parallel_view: view == :parallel)
|
||||
end
|
||||
|
||||
def diff_files_collection(diff_files)
|
||||
::RapidDiffs::MergeRequestDiffFileComponent
|
||||
.with_collection(diff_files, merge_request: @merge_request, parallel_view: view == :parallel)
|
||||
.render_in(view_context)
|
||||
end
|
||||
|
||||
def sorted?
|
||||
|
|
|
|||
|
|
@ -603,13 +603,16 @@ class ProjectsController < Projects::ApplicationController
|
|||
def redirect_git_extension
|
||||
return unless params[:format] == 'git'
|
||||
|
||||
git_extension_regex = %r{\.git/?\Z}
|
||||
return unless request.path.match?(git_extension_regex)
|
||||
|
||||
# `project` calls `find_routable!`, so this will trigger the usual not-found
|
||||
# behaviour when the user isn't authorized to see the project
|
||||
return if project.nil? || performed?
|
||||
|
||||
uri = URI(request.original_url)
|
||||
# Strip the '.git' part from the path
|
||||
uri.path = uri.path.sub(%r{\.git/?\Z}, '')
|
||||
uri.path = uri.path.sub(git_extension_regex, '')
|
||||
|
||||
redirect_to(uri.to_s)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ module Mutations
|
|||
required: true,
|
||||
description: 'Name of the integration.'
|
||||
|
||||
argument :type, Types::AlertManagement::IntegrationTypeEnum,
|
||||
as: :type_identifier,
|
||||
required: false,
|
||||
default_value: :http,
|
||||
replace_null_with_default: true,
|
||||
description: 'Type of integration to create. Cannot be changed after creation.'
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: true,
|
||||
description: 'Whether the integration is receiving alerts.'
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module Mutations
|
|||
field :integration,
|
||||
Types::AlertManagement::HttpIntegrationType,
|
||||
null: true,
|
||||
description: "HTTP integration."
|
||||
description: "Alerting integration."
|
||||
|
||||
authorize :admin_operations
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ module Mutations
|
|||
|
||||
# overriden in EE
|
||||
def http_integration_params(_project, args)
|
||||
args.slice(:name, :active)
|
||||
args.slice(:name, :active, :type_identifier)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -377,6 +377,8 @@ module ApplicationSettingsHelper
|
|||
:max_export_size,
|
||||
:max_github_response_size_limit,
|
||||
:max_github_response_json_value_count,
|
||||
:max_http_decompressed_size,
|
||||
:max_http_response_size_limit,
|
||||
:max_import_size,
|
||||
:max_import_remote_file_size,
|
||||
:max_login_attempts,
|
||||
|
|
|
|||
|
|
@ -655,6 +655,8 @@ class ApplicationSetting < ApplicationRecord
|
|||
:max_export_size,
|
||||
:max_github_response_size_limit,
|
||||
:max_github_response_json_value_count,
|
||||
:max_http_decompressed_size,
|
||||
:max_http_response_size_limit,
|
||||
:max_import_remote_file_size,
|
||||
:max_import_size,
|
||||
:max_pages_custom_domains_per_project,
|
||||
|
|
@ -706,6 +708,8 @@ class ApplicationSetting < ApplicationRecord
|
|||
validates :clickhouse, json_schema: { filename: "application_setting_clickhouse" }
|
||||
|
||||
jsonb_accessor :response_limits,
|
||||
max_http_response_size_limit: [:integer, { default: 100 }],
|
||||
max_http_decompressed_size: [:integer, { default: 100 }],
|
||||
max_github_response_size_limit: [:integer, { default: 8 }],
|
||||
max_github_response_json_value_count: [:integer, { default: 250_000 }]
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,8 @@ module ApplicationSettingImplementation
|
|||
max_export_size: 0,
|
||||
max_github_response_size_limit: 8,
|
||||
max_github_response_json_value_count: 250_000,
|
||||
max_http_decompressed_size: 100,
|
||||
max_http_response_size_limit: 100,
|
||||
max_import_size: 0,
|
||||
max_import_remote_file_size: 10240,
|
||||
max_login_attempts: nil,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class Board < ApplicationRecord
|
|||
has_many :lists, -> { ordered }, dependent: :delete_all, inverse_of: :board # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :destroyable_lists, -> { destroyable.ordered }, class_name: "List", inverse_of: :board
|
||||
|
||||
validates :name, presence: true
|
||||
validates :name, presence: true, length: { maximum: 255, if: :name_changed? }
|
||||
validates :project, presence: true, if: :project_needed?
|
||||
validates :group, presence: true, unless: :project
|
||||
validates :group, absence: {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ module Integrations
|
|||
|
||||
begin
|
||||
story_on_task_url = format(STORY_URL_TEMPLATE, task_gid: task_id)
|
||||
Gitlab::HTTP.post(
|
||||
Clients::HTTP.post(
|
||||
story_on_task_url,
|
||||
headers: { "Authorization" => "Bearer #{api_key}" },
|
||||
body: { text: "#{push_msg} #{message}" }
|
||||
|
|
@ -120,7 +120,8 @@ module Integrations
|
|||
|
||||
if prepended_text.match?(proceded_keyword_finder)
|
||||
task_url = format(TASK_URL_TEMPLATE, task_gid: task_id)
|
||||
Gitlab::HTTP.put(task_url, headers: { "Authorization" => "Bearer #{api_key}" }, body: { completed: true })
|
||||
Clients::HTTP.put(task_url, headers: { "Authorization" => "Bearer #{api_key}" },
|
||||
body: { completed: true })
|
||||
end
|
||||
rescue StandardError => e
|
||||
log_error(e.message)
|
||||
|
|
@ -130,7 +131,7 @@ module Integrations
|
|||
end
|
||||
|
||||
def test(_)
|
||||
result = Gitlab::HTTP.get(PERSONAL_ACCESS_TOKEN_TEST_URL, headers: { "Authorization" => "Bearer #{api_key}" })
|
||||
result = Clients::HTTP.get(PERSONAL_ACCESS_TOKEN_TEST_URL, headers: { "Authorization" => "Bearer #{api_key}" })
|
||||
|
||||
if result.success?
|
||||
{ success: true }
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ module Integrations
|
|||
url = "https://atlas.assembla.com/spaces/#{URI.encode_www_form_component(subdomain)}/github_tool?secret_key=#{URI.encode_www_form_component(token)}"
|
||||
body = { payload: data }
|
||||
|
||||
Gitlab::HTTP.post(
|
||||
Clients::HTTP.post(
|
||||
url,
|
||||
body: Gitlab::Json.dump(body),
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
|
|
|
|||
|
|
@ -154,11 +154,11 @@ module Integrations
|
|||
params = build_get_params(query_params)
|
||||
params[:extra_log_info] = { project_id: project_id }
|
||||
|
||||
Gitlab::HTTP.try_get(build_url(path), params)
|
||||
Clients::HTTP.try_get(build_url(path), params)
|
||||
end
|
||||
|
||||
def get_path(path, query_params = {})
|
||||
Gitlab::HTTP.get(build_url(path), build_get_params(query_params))
|
||||
Clients::HTTP.get(build_url(path), build_get_params(query_params))
|
||||
end
|
||||
|
||||
def build_url(path)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module Integrations
|
|||
end
|
||||
|
||||
def execute(_data)
|
||||
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
|
||||
response = Clients::HTTP.get(properties['external_wiki_url'], verify: true)
|
||||
response.body if response.code == 200
|
||||
rescue StandardError
|
||||
nil
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ module Integrations
|
|||
key = parse_thread_key(message)
|
||||
payload = { text: parse_simple_text_message(message), thread: { threadKey: key }.compact }.compact_blank!
|
||||
|
||||
Gitlab::HTTP.post(
|
||||
Clients::HTTP.post(
|
||||
url,
|
||||
body: payload.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ module Integrations
|
|||
result = false
|
||||
|
||||
begin
|
||||
response = Gitlab::HTTP.head(project_url, verify: true)
|
||||
response = Clients::HTTP.head(project_url, verify: true)
|
||||
|
||||
if response
|
||||
message = "#{type} received response #{response.code} when attempting to connect to #{project_url}"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ module Integrations
|
|||
# # => 'running'
|
||||
#
|
||||
def commit_status(sha, _ref)
|
||||
response = Gitlab::HTTP.get(commit_status_path(sha), verify: enable_ssl_verification)
|
||||
response = Clients::HTTP.get(commit_status_path(sha), verify: enable_ssl_verification)
|
||||
read_commit_status(response)
|
||||
rescue Errno::ECONNREFUSED
|
||||
:error
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ module Integrations
|
|||
'message' => commit[:message]
|
||||
}
|
||||
}
|
||||
Gitlab::HTTP.post(
|
||||
Clients::HTTP.post(
|
||||
API_ENDPOINT,
|
||||
body: Gitlab::Json.dump(message),
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ module Integrations
|
|||
|
||||
def notify(message, _opts)
|
||||
header = { 'Content-Type' => 'application/json' }
|
||||
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump({ text: message.summary }))
|
||||
response = Clients::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump({ text: message.summary }))
|
||||
|
||||
response if response.success?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ module Integrations
|
|||
# Sound parameter MUST NOT be sent to API if not selected
|
||||
pushover_data[:sound] = sound if sound
|
||||
|
||||
Gitlab::HTTP.post('/messages.json', base_uri: BASE_URI, body: pushover_data)
|
||||
Clients::HTTP.post('/messages.json', base_uri: BASE_URI, body: pushover_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ module Integrations
|
|||
end
|
||||
|
||||
def get_path(path)
|
||||
Gitlab::HTTP.try_get(
|
||||
Clients::HTTP.try_get(
|
||||
build_url(path),
|
||||
verify: enable_ssl_verification,
|
||||
basic_auth: basic_auth,
|
||||
|
|
@ -176,7 +176,7 @@ module Integrations
|
|||
end
|
||||
|
||||
def post_to_build_queue(_data, branch)
|
||||
Gitlab::HTTP.post(
|
||||
Clients::HTTP.post(
|
||||
build_url('httpAuth/app/rest/buildQueue'),
|
||||
body: "<build branchName=#{branch.encode(xml: :attr)}>" \
|
||||
"<buildType id=#{build_type.encode(xml: :attr)}/>" \
|
||||
|
|
|
|||
|
|
@ -113,13 +113,13 @@ module Integrations
|
|||
}.compact_blank
|
||||
|
||||
header = { 'Content-Type' => 'application/json' }
|
||||
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
|
||||
response = Clients::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
|
||||
|
||||
# We're retrying the request with a different format to ensure accurate formatting and
|
||||
# avoid receiving a 400 response due to invalid markdown.
|
||||
if response.bad_request?
|
||||
body.except!(:parse_mode)
|
||||
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
|
||||
response = Clients::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
|
||||
end
|
||||
|
||||
response if response.success?
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ module Integrations
|
|||
markdown: true
|
||||
}
|
||||
|
||||
response = Gitlab::HTTP.post(webhook, body: Gitlab::Json.dump(body))
|
||||
response = Clients::HTTP.post(webhook, body: Gitlab::Json.dump(body))
|
||||
|
||||
response if response.success?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ module Integrations
|
|||
|
||||
def notify(message, _opts)
|
||||
header = { 'Content-Type' => 'application/json' }
|
||||
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump({ markdown: message.summary }))
|
||||
response = Clients::HTTP.post(webhook, headers: header,
|
||||
body: Gitlab::Json.dump({ markdown: message.summary }))
|
||||
|
||||
response if response.success?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ module Integrations
|
|||
class HTTPClient
|
||||
def self.post(uri, params = {})
|
||||
params.delete(:http_options) # these are internal to the client and we do not want them
|
||||
Gitlab::HTTP.post(uri, body: params)
|
||||
Clients::HTTP.post(uri, body: params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module WebHooks
|
|||
InterpolationError = Class.new(StandardError)
|
||||
|
||||
SECRET_MASK = '************'
|
||||
MAX_PARAM_LENGTH = 8192
|
||||
|
||||
# See app/validators/json_schemas/web_hooks_url_variables.json
|
||||
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/
|
||||
|
|
@ -45,9 +46,14 @@ module WebHooks
|
|||
encode_iv: false
|
||||
|
||||
validates :url, presence: true
|
||||
validates :url, public_url: true, if: ->(hook) { hook.validate_public_url? && !hook.url_variables? }
|
||||
validates :url, length: { maximum: MAX_PARAM_LENGTH }
|
||||
validates :url, public_url: true, if: ->(hook) {
|
||||
# Apply the validation up to the point where the length validation above would make the record invalid.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/524020#note_2529307579
|
||||
(hook.url&.length&.<= MAX_PARAM_LENGTH) && hook.validate_public_url? && !hook.url_variables?
|
||||
}
|
||||
|
||||
validates :token, format: { without: /\n/ }
|
||||
validates :token, length: { maximum: MAX_PARAM_LENGTH }, format: { without: /\n/ }
|
||||
|
||||
after_initialize :initialize_url_variables
|
||||
after_initialize :initialize_custom_headers
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ module Integrations
|
|||
end
|
||||
|
||||
def calculate_reactive_cache(sha, ref)
|
||||
response = Gitlab::HTTP.try_get(commit_status_path(sha), request_options)
|
||||
response = Clients::HTTP.try_get(commit_status_path(sha), request_options)
|
||||
|
||||
status =
|
||||
if response&.code == 200 && response['status']
|
||||
|
|
|
|||
|
|
@ -109,14 +109,14 @@ module Integrations
|
|||
}
|
||||
}
|
||||
}
|
||||
res = Gitlab::HTTP.post(path, base_uri: base_uri, **auth.merge(body))
|
||||
res = Clients::HTTP.post(path, base_uri: base_uri, **auth.merge(body))
|
||||
res.code == 201 ? res : nil
|
||||
end
|
||||
|
||||
# Returns a list of rooms, or [].
|
||||
# https://github.com/basecamp/campfire-api/blob/master/sections/rooms.md#get-rooms
|
||||
def rooms(auth)
|
||||
res = Gitlab::HTTP.get("/rooms.json", base_uri: base_uri, **auth)
|
||||
res = Clients::HTTP.get("/rooms.json", base_uri: base_uri, **auth)
|
||||
res.code == 200 ? res["rooms"] : []
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ module Integrations
|
|||
end
|
||||
|
||||
def calculate_reactive_cache(sha, ref)
|
||||
response = Gitlab::HTTP.try_get(
|
||||
response = Clients::HTTP.try_get(
|
||||
commit_status_path(sha, ref),
|
||||
verify: enable_ssl_verification,
|
||||
extra_log_info: { project_id: project_id }
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ module Integrations
|
|||
}
|
||||
url = URI.parse(webhook)
|
||||
url.path << (Time.current.to_f * 1000).round.to_s
|
||||
response = Gitlab::HTTP.put(url, headers: header, body: Gitlab::Json.dump(body))
|
||||
response = Clients::HTTP.put(url, headers: header, body: Gitlab::Json.dump(body))
|
||||
|
||||
response if response.success?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ module Import
|
|||
def headers
|
||||
return {} if file_url.blank?
|
||||
|
||||
@headers ||= Gitlab::HTTP.head(file_url, timeout: 1.second).headers
|
||||
@headers ||= Clients::HTTP.head(file_url, timeout: 1.second).headers
|
||||
rescue StandardError => e
|
||||
errors.add(:base, "Failed to retrive headers: #{e.message}")
|
||||
errors.add(:base, "Failed to retrieve headers: #{e.message}")
|
||||
|
||||
{}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ module Integrations
|
|||
redirect_uri: redirect_uri
|
||||
}
|
||||
|
||||
Gitlab::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
|
||||
Clients::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
|
||||
end
|
||||
|
||||
# Due to our modelling (mentioned in epic 9418) we create a SlackIntegration record
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ module Integrations
|
|||
request_body = Gitlab::Json.dump(close_request_body)
|
||||
response_url = params.dig(:view, :private_metadata)
|
||||
|
||||
Gitlab::HTTP.post(response_url, body: request_body, headers: headers)
|
||||
Clients::HTTP.post(response_url, body: request_body, headers: headers)
|
||||
end
|
||||
|
||||
def close_request_body
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ module Integrations
|
|||
text: text
|
||||
}
|
||||
|
||||
Gitlab::HTTP.post(
|
||||
Integrations::Clients::HTTP.post(
|
||||
response_url,
|
||||
body: Gitlab::Json.dump(body),
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module JiraConnectInstallations
|
|||
attr_reader :installation, :event
|
||||
|
||||
def send_hook
|
||||
Gitlab::HTTP.post(hook_uri, body: body)
|
||||
Integrations::Clients::HTTP.post(hook_uri, body: body)
|
||||
end
|
||||
|
||||
def hook_uri
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@ module Projects
|
|||
end
|
||||
|
||||
def download_links_for(oids)
|
||||
response = Gitlab::HTTP.post(remote_uri, body: request_body(oids), headers: headers)
|
||||
response = ::Import::Clients::HTTP.post(remote_uri, body: request_body(oids), headers: headers)
|
||||
|
||||
raise DownloadLinksRequestEntityTooLargeError if response.request_entity_too_large?
|
||||
raise DownloadLinksError, response.message unless response.success?
|
||||
|
||||
# Since the LFS Batch API may return a Content-Ttpe of
|
||||
# Since the LFS Batch API may return a Content-Type of
|
||||
# application/vnd.git-lfs+json
|
||||
# (https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md#requests),
|
||||
# HTTParty does not know this is actually JSON.
|
||||
|
|
|
|||
|
|
@ -79,7 +79,10 @@ module Projects
|
|||
end
|
||||
|
||||
def download_options
|
||||
http_options = { headers: lfs_headers, stream_body: true }
|
||||
# Set accept-encoding to identity to request web servers not to send a compressed response to avoid using too
|
||||
# much memory to decompress the file. In case the response is encoded, the response size will be limited by
|
||||
# `max_http_decompressed_size application` application setting.
|
||||
http_options = { headers: lfs_headers.merge('accept-encoding' => 'identity'), stream_body: true }
|
||||
|
||||
return http_options if lfs_download_object.has_authorization_header?
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ class WebHookService
|
|||
headers: build_custom_headers.merge(build_headers),
|
||||
verify: hook.enable_ssl_verification,
|
||||
basic_auth: basic_auth,
|
||||
max_bytes: Gitlab::CurrentSettings.max_http_response_size_limit.megabytes,
|
||||
**request_options)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,16 @@
|
|||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Maximum allowed object count for GitHub API responses. Count is an estimate based on the number of : , { and [ occurrences in the response."
|
||||
},
|
||||
"max_http_decompressed_size": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Maximum allowed size in MB for Gzip-compressed HTTP responses after decompression."
|
||||
},
|
||||
"max_http_response_size_limit": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Maximum allowed size in MB for HTTP responses."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
- add_page_specific_style 'page_bundles/labels'
|
||||
- page_description = s_('AdminLabels|Labels created here will be automatically added to new projects.')
|
||||
|
||||
%h1.gl-sr-only= page_title
|
||||
|
||||
%div{ data: { event_tracking_load: 'true', event_tracking: 'view_admin_labels_pageload' } }
|
||||
|
||||
- if @labels.present?
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
|
||||
- add_page_specific_style 'page_bundles/labels'
|
||||
|
||||
%h1.gl-sr-only= page_title
|
||||
|
||||
- if labels_or_filters
|
||||
#js-promote-label-modal
|
||||
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.row
|
||||
.form-group.project-name.col-sm-12
|
||||
= label_tag :name, _('Project name'), class: 'label-bold'
|
||||
= text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { testid: 'project-name-field' }
|
||||
= text_field_tag :name, @name, placeholder: s_('ProjectsNew|My project'), class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { testid: 'project-name-field' }
|
||||
.form-group.col-12.col-sm-6.gl-pr-0
|
||||
= label_tag :namespace_id, _('Project URL'), class: 'label-bold'
|
||||
.input-group.gl-flex-nowrap
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
.form-group.gl-form-group.project-name.col-sm-12
|
||||
= f.label :name, class: 'label-bold' do
|
||||
%span= _("Project name")
|
||||
= f.text_field :name, placeholder: "My awesome project", class: "form-control gl-form-input input-lg", data: { testid: 'project-name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true, describedby: 'js-project-name-description' }
|
||||
= f.text_field :name, placeholder: s_('ProjectsNew|My project'), class: "form-control gl-form-input input-lg", data: { testid: 'project-name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true, describedby: 'js-project-name-description' }
|
||||
%small#js-project-name-description.form-text.gl-text-subtle
|
||||
= s_("ProjectsNew|Must start with a lowercase or uppercase letter, digit, emoji, or underscore. Can also contain dots, pluses, dashes, or spaces.")
|
||||
#js-project-name-error.gl-field-error.gl-mt-2.gl-hidden
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
|
||||
- add_page_specific_style 'page_bundles/labels'
|
||||
|
||||
%h1.gl-sr-only= page_title
|
||||
|
||||
- if labels_or_filters
|
||||
#js-promote-label-modal
|
||||
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
|
||||
|
|
@ -23,8 +25,8 @@
|
|||
body_options: { class: '!gl-m-0' },
|
||||
description: _('Drag to reorder prioritized labels and change their relative priority.')) do |c|
|
||||
- c.with_body do
|
||||
.js-prioritized-labels.gl-rounded-base.manage-labels-list{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
|
||||
#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
|
||||
%ul.js-prioritized-labels.gl-rounded-base.manage-labels-list{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
|
||||
%li#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
|
||||
= render 'shared/empty_states/priority_labels'
|
||||
- if @prioritized_labels.any?
|
||||
= render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project }
|
||||
|
|
@ -35,7 +37,7 @@
|
|||
- if @labels.any?
|
||||
= render ::Layouts::CrudComponent.new(hide ? _('Labels') : _('Other labels'), options: { class: 'other-labels' }, count: number_with_delimiter(@labels.total_count), icon: 'label', body_options: { class: '!gl-m-0' }) do |c|
|
||||
- c.with_body do
|
||||
.js-other-labels.manage-labels-list
|
||||
%ul.js-other-labels.manage-labels-list
|
||||
= render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project }
|
||||
- c.with_pagination do
|
||||
= paginate @labels, theme: 'gitlab'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.text-center.gl-mt-1.gl-mb-5
|
||||
.svg-content{ data: { testid: 'label-svg-content' } }
|
||||
= image_tag 'illustrations/empty-state/empty-labels-starred-md.svg'
|
||||
= image_tag 'illustrations/empty-state/empty-labels-starred-md.svg', role: 'presentation'
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
%h5.gl-my-0
|
||||
%h3.gl-heading-5.gl-my-0
|
||||
= _("No prioritized labels yet!")
|
||||
%p.gl-text-subtle
|
||||
= _("Star labels to start sorting by priority.")
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module JiraConnect
|
|||
worker_has_external_dependencies!
|
||||
|
||||
def perform(proxy_url, jwt, attempts = 3)
|
||||
r = Gitlab::HTTP.post(proxy_url, headers: { 'Authorization' => "JWT #{jwt}" })
|
||||
r = Integrations::Clients::HTTP.post(proxy_url, headers: { 'Authorization' => "JWT #{jwt}" })
|
||||
|
||||
self.class.perform_in(1.hour, proxy_url, jwt, attempts - 1) if r.code >= 400 && attempts > 0
|
||||
rescue *Gitlab::HTTP::HTTP_ERRORS
|
||||
|
|
|
|||
|
|
@ -1,24 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
MONKEY_PATCH_METHOD_CHECKSUM = "7fd222f9cffc7ab25d4782d380fdb0b5e83b35d495f9e2fbd8e5057891c73108"
|
||||
|
||||
unless Rails.env.production?
|
||||
source = "Net::HTTPResponse::Inflater".safe_constantize&.instance_method(:inflate_adapter)&.source.to_s.strip
|
||||
|
||||
unless OpenSSL::Digest::SHA256.hexdigest(source) == MONKEY_PATCH_METHOD_CHECKSUM
|
||||
raise "Original Net::HTTPResponse::Inflater code was modified. Please update the patch accordingly, then update" \
|
||||
"the checksum."
|
||||
end
|
||||
end
|
||||
|
||||
module Net
|
||||
class HTTPResponse
|
||||
module FinishOverride
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables -- This is a Monkey Patch
|
||||
def finish
|
||||
if Gitlab.config.gitlab.log_decompressed_response_bytesize > 0 &&
|
||||
@inflate.total_out > Gitlab.config.gitlab.log_decompressed_response_bytesize
|
||||
Gitlab::AppJsonLogger.debug(message: 'net/http: response decompressed', size: @inflate.total_out,
|
||||
caller: Gitlab::BacktraceCleaner.clean_backtrace(caller))
|
||||
# This is a monkey patch over an existing method of net/http to limit maximum decompression size
|
||||
#
|
||||
# To disable the decomposition limit validation see `::Gitlab::HTTP.without_decompression_limit`.
|
||||
#
|
||||
# Original code from
|
||||
# https://github.com/ruby/ruby/blob/d5f94941d87743d6563fa1a038665917dea70201/lib/net/http/response.rb#L693-L707
|
||||
class Inflater
|
||||
def inflate_adapter(dest)
|
||||
if dest.respond_to?(:set_encoding)
|
||||
dest.set_encoding(Encoding::ASCII_8BIT)
|
||||
elsif dest.respond_to?(:force_encoding)
|
||||
dest.force_encoding(Encoding::ASCII_8BIT)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
block = proc do |compressed_chunk|
|
||||
@inflate.inflate(compressed_chunk) do |chunk|
|
||||
compressed_chunk.clear
|
||||
|
||||
# Limit the maximum decompression size
|
||||
class Inflater
|
||||
prepend FinishOverride
|
||||
if validate_decompressed_size? && @inflate.total_out > max_http_decompressed_size
|
||||
Gitlab::AppJsonLogger.error(message: 'Net::HTTP - Response size too large', size: @inflate.total_out,
|
||||
caller: Gitlab::BacktraceCleaner.clean_backtrace(caller))
|
||||
|
||||
raise Gitlab::HTTP::MaxDecompressionSizeError, "Response size over #{max_http_decompressed_size} bytes"
|
||||
end
|
||||
|
||||
dest << chunk
|
||||
end
|
||||
end
|
||||
|
||||
Net::ReadAdapter.new(block)
|
||||
end
|
||||
|
||||
def validate_decompressed_size?
|
||||
Gitlab::CurrentSettings.max_http_decompressed_size > 0 &&
|
||||
!Gitlab::SafeRequestStore[:disable_net_http_decompression]
|
||||
end
|
||||
|
||||
def max_http_decompressed_size
|
||||
Gitlab::CurrentSettings.max_http_decompressed_size.megabytes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ feature_category: importers
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180432
|
||||
milestone: '17.9'
|
||||
queued_migration_version: 20250205194220
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: '20250610152957'
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ feature_category: geo_replication
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180426
|
||||
milestone: '17.9'
|
||||
queued_migration_version: 20250205193115
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: 20250611100647
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ feature_category: team_planning
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180837
|
||||
milestone: '17.9'
|
||||
queued_migration_version: 20250209005146
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: 20250611095947
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ feature_category: incident_management
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175719
|
||||
milestone: '17.8'
|
||||
queued_migration_version: 20241213142263
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: '20250605204337'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeBackfillIssuableSlasNamespaceId < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.1'
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillIssuableSlasNamespaceId',
|
||||
table_name: :issuable_slas,
|
||||
column_name: :id,
|
||||
job_arguments: [:namespace_id, :issues, :namespace_id, :issue_id],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeBackfillBulkImportExportBatchesGroupId < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.1'
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillBulkImportExportBatchesGroupId',
|
||||
table_name: :bulk_import_export_batches,
|
||||
column_name: :id,
|
||||
job_arguments: [:group_id, :bulk_import_exports, :group_id, :export_id],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeBackfillDesignUserMentionsNamespaceId < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.1'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillDesignUserMentionsNamespaceId',
|
||||
table_name: :design_user_mentions,
|
||||
column_name: :id,
|
||||
job_arguments: [:namespace_id, :design_management_designs, :namespace_id, :design_id],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeBackfillDesignManagementRepositoryStatesNamespaceId < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.1'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'BackfillDesignManagementRepositoryStatesNamespaceId',
|
||||
table_name: :design_management_repository_states,
|
||||
column_name: :design_management_repository_id,
|
||||
job_arguments: [:namespace_id, :design_management_repositories, :namespace_id, :design_management_repository_id],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
7cea86680a0475a789c1df120bbea3a5c010fdcb21fb531647c5f1c40fa862e5
|
||||
|
|
@ -0,0 +1 @@
|
|||
cae7e8295a5216ee5b4d4c886ae87e529634b9faac7d6a6b04a0972dbd0858d7
|
||||
|
|
@ -0,0 +1 @@
|
|||
4a5720e055d17c41a01f9cfb68c5da9af258c9cf14900aa821453089b6489937
|
||||
|
|
@ -0,0 +1 @@
|
|||
9cfda522c1e97f313318d6861d540d3ef2d61075e3d0922d73609a69168623b5
|
||||
|
|
@ -275,6 +275,15 @@ instructions only work on the Linux package-provided PostgreSQL:
|
|||
Replace `<PRAEFECT_SQL_PASSWORD_HASH>` with the hash of the password you generated in the
|
||||
preparation step. It is prefixed with `md5` literal.
|
||||
|
||||
1. Create a new user `pgbouncer` to be used by PgBouncer:
|
||||
|
||||
```sql
|
||||
CREATE ROLE pgbouncer WITH LOGIN;
|
||||
ALTER USER pgbouncer WITH password 'md5<PGBOUNCER_SQL_PASSWORD_HASH>';
|
||||
```
|
||||
|
||||
Replace `PGBOUNCER_SQL_PASSWORD_HASH` with the strong password hash you generated in the preparation step.
|
||||
|
||||
1. The PgBouncer that is shipped with the Linux package is configured to use [`auth_query`](https://www.pgbouncer.org/config.html#generic-settings)
|
||||
and uses `pg_shadow_lookup` function. You need to create this function in `praefect_production`
|
||||
database:
|
||||
|
|
|
|||
|
|
@ -307,6 +307,38 @@ There is a limit when embedding metrics in GitLab Flavored Markdown (GLFM) for p
|
|||
|
||||
- **Max limit**: 100 embeds.
|
||||
|
||||
## HTTP response limits
|
||||
|
||||
### Maximum Gzip-compressed size
|
||||
|
||||
This setting is used to restrict the maximum allowed size in MiB for Gzip-compressed
|
||||
HTTP responses after decompression to prevent DoS.
|
||||
|
||||
The default maximum size is 100 MiB. To disable this limit, set the value to 0.
|
||||
If the value is too high, it could expose the instance to DoS attacks.
|
||||
|
||||
You can change this limit by using the GitLab Rails console or use
|
||||
[application setting API](../api/settings.md)
|
||||
|
||||
```ruby
|
||||
ApplicationSetting.update(max_http_decompressed_size: 50)
|
||||
```
|
||||
|
||||
### Maximum HTTP responses size
|
||||
|
||||
This setting is used to restrict the maximum allowed size in MiB for decompressed
|
||||
HTTP responses to prevent DoS. It applies to integrations, importers, and webhooks.
|
||||
|
||||
The default maximum size is 100 MiB. To disable this limit, set the value to 0.
|
||||
If the value is too high, it could expose the instance to DoS attacks.
|
||||
|
||||
You can change this limit by using the GitLab Rails console or use
|
||||
[application setting API](../api/settings.md)
|
||||
|
||||
```ruby
|
||||
ApplicationSetting.update(max_http_response_size_limit: 60)
|
||||
```
|
||||
|
||||
## Webhook limits
|
||||
|
||||
Also see [Webhook rate limits](#webhook-rate-limit).
|
||||
|
|
|
|||
|
|
@ -6697,6 +6697,7 @@ Input type: `HttpIntegrationCreateInput`
|
|||
| <a id="mutationhttpintegrationcreatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | Custom mapping of GitLab alert attributes to fields from the payload example. |
|
||||
| <a id="mutationhttpintegrationcreatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
|
||||
| <a id="mutationhttpintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
|
||||
| <a id="mutationhttpintegrationcreatetype"></a>`type` | [`AlertManagementIntegrationType`](#alertmanagementintegrationtype) | Type of integration to create. Cannot be changed after creation. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
@ -6704,7 +6705,7 @@ Input type: `HttpIntegrationCreateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
|
||||
| <a id="mutationhttpintegrationcreateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
| <a id="mutationhttpintegrationcreateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
|
||||
|
||||
### `Mutation.httpIntegrationDestroy`
|
||||
|
||||
|
|
@ -6723,7 +6724,7 @@ Input type: `HttpIntegrationDestroyInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
|
||||
| <a id="mutationhttpintegrationdestroyintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
| <a id="mutationhttpintegrationdestroyintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
|
||||
|
||||
### `Mutation.httpIntegrationResetToken`
|
||||
|
||||
|
|
@ -6742,7 +6743,7 @@ Input type: `HttpIntegrationResetTokenInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationresettokenerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
|
||||
| <a id="mutationhttpintegrationresettokenintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
| <a id="mutationhttpintegrationresettokenintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
|
||||
|
||||
### `Mutation.httpIntegrationUpdate`
|
||||
|
||||
|
|
@ -6765,7 +6766,7 @@ Input type: `HttpIntegrationUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
|
||||
| <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
| <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | Alerting integration. |
|
||||
|
||||
### `Mutation.importSourceUserCancelReassignment`
|
||||
|
||||
|
|
|
|||
|
|
@ -614,6 +614,8 @@ to configure other related settings. These requirements are
|
|||
| `max_export_size` | integer | no | Maximum export size in MB. 0 for unlimited. Default = 0 (unlimited). |
|
||||
| `max_github_response_size_limit` | integer | no | Maximum allowed GitHub API response size in MB. 0 for unlimited. |
|
||||
| `max_github_response_json_value_count` | integer | no | Maximum allowed value count for GitHub API responses. 0 for unlimited. Count is an estimate based on the number of `:` `,` `{` and `[` occurrences in the response. |
|
||||
| `max_http_decompressed_size` | integer | no | Maximum allowed size in MiB for Gzip-compressed HTTP responses after decompression. 0 for unlimited. |
|
||||
| `max_http_response_size_limit` | integer | no | Maximum allowed size in MiB for HTTP responses. 0 for unlimited. |
|
||||
| `max_import_size` | integer | no | Maximum import size in MB. 0 for unlimited. Default = 0 (unlimited). |
|
||||
| `max_import_remote_file_size` | integer | no | Maximum remote file size for imports from external object storages. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384976) in GitLab 16.3. |
|
||||
| `max_login_attempts` | integer | no | Maximum number of sign-in attempts before locking out the user. |
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ title: Principles of Importer Design
|
|||
- Importers must not add third-party Ruby gems that make HTTP calls.
|
||||
Importers use the same
|
||||
[Ruby gem policy as for integrations](../integrations/_index.md#no-ruby-gems-that-make-http-calls), for more information about Ruby gem use for importers see that page.
|
||||
- All HTTP calls must use `Gitlab::HTTP`.
|
||||
`Gitlab::HTTP` ensures that [network settings](../../security/webhooks.md) of the instance
|
||||
are enforced and has other [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) measures.
|
||||
- All HTTP calls must use `Import::Clients::HTTP`, which:
|
||||
- Ensures that [network settings](../../security/webhooks.md) are enforced for HTTP calls.
|
||||
- Has additional [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) features.
|
||||
- Is our single source of truth for making secure HTTP calls.
|
||||
- Ensure all response sizes are validated.
|
||||
|
||||
## Logging
|
||||
|
||||
|
|
|
|||
|
|
@ -208,13 +208,14 @@ For example, to create metric definitions for the Slack integration, you copy th
|
|||
|
||||
### Security requirements
|
||||
|
||||
#### All HTTP calls must use `Gitlab::HTTP`
|
||||
#### All HTTP calls must use `Integrations::Clients::HTTP`
|
||||
|
||||
Integrations must always make HTTP calls using `Gitlab::HTTP`, which:
|
||||
Integrations must always make HTTP calls using `Integrations::Clients::HTTP`, which:
|
||||
|
||||
- Ensures that [network settings](../../security/webhooks.md) are enforced for HTTP calls.
|
||||
- Has additional [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) features.
|
||||
- Is our single source of truth for making secure HTTP calls.
|
||||
- Ensure all response sizes are validated.
|
||||
|
||||
#### Masking channel values
|
||||
|
||||
|
|
@ -244,7 +245,7 @@ but they offer minimal benefit compared to the costs involved:
|
|||
- They increase the potential surface area of security problems and the effort required to fix them.
|
||||
- Often these gems make HTTP calls on your behalf. As integrations can make HTTP calls to remote
|
||||
servers configured by users, it is critical that we
|
||||
[fully control the network calls](#all-http-calls-must-use-gitlabhttp).
|
||||
[fully control the network calls](#all-http-calls-must-use-integrationsclientshttp).
|
||||
- There is a maintenance cost of managing gem upgrades.
|
||||
- They can block us from using newer features.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ title: GitLab Duo Chat
|
|||
- Tier: Premium, Ultimate
|
||||
- Add-on: GitLab Duo Core, Pro, or Enterprise, GitLab Duo with Amazon Q
|
||||
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
|
||||
- LLMs: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet), Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2), Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet), Anthropic [Claude 3.5 Haiku](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-haiku), and [Vertex AI Search](https://cloud.google.com/enterprise-search). The LLM depends on the question asked.
|
||||
- LLMs: Anthropic [Claude 4.0 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-sonnet-4),
|
||||
Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet), Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2), Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet), Anthropic [Claude 3.5 Haiku](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-haiku), and [Vertex AI Search](https://cloud.google.com/enterprise-search). The LLM depends on the question asked.
|
||||
- LLM for Amazon Q: Amazon Q Developer
|
||||
|
||||
{{< /details >}}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,8 @@ Prerequisites:
|
|||
|
||||
- Review the list of [items that are exported](#project-items-that-are-exported). Not all items are exported.
|
||||
- You must have at least the Maintainer role for the project.
|
||||
- For significantly improved performance for repositories with a large number of Git references, use GitLab 18.0 or later. For more information, see our
|
||||
[blog post about decreasing GitLab repository backup times](https://about.gitlab.com/blog/2025/06/05/how-we-decreased-gitlab-repo-backup-times-from-48-hours-to-41-minutes/).
|
||||
|
||||
To export a project and its data, follow these steps:
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ Naming/ClassAndModuleCamelCase:
|
|||
AllowedNames:
|
||||
- HTTP_V2
|
||||
|
||||
Style/NumericPredicate:
|
||||
EnforcedStyle: comparison
|
||||
|
||||
Style/SpecialGlobalVars:
|
||||
Enabled: false
|
||||
|
||||
|
|
|
|||
|
|
@ -73,21 +73,18 @@ module Gitlab
|
|||
start_time = nil
|
||||
read_total_timeout = options.fetch(:timeout, DEFAULT_READ_TOTAL_TIMEOUT)
|
||||
byte_size = 0
|
||||
already_logged = false
|
||||
max_bytesize = options.fetch(:max_bytes, 0)
|
||||
|
||||
promise = Concurrent::Promise.new do
|
||||
Gitlab::Utils.restrict_within_concurrent_ruby do
|
||||
httparty_perform_request(http_method, path, options_with_timeouts) do |fragment|
|
||||
start_time ||= system_monotonic_time
|
||||
elapsed = system_monotonic_time - start_time
|
||||
byte_size += fragment.bytesize if should_log_response_size?
|
||||
byte_size += fragment.bytesize
|
||||
|
||||
raise ReadTotalTimeout, "Request timed out after #{elapsed} seconds" if elapsed > read_total_timeout
|
||||
|
||||
if should_log_response_size? && byte_size > expected_max_response_size && !already_logged
|
||||
configuration.log_with_level(:debug, message: 'gitlab/http: response size', size: byte_size)
|
||||
already_logged = true
|
||||
end
|
||||
check_max_byte_size_limit!(max_bytesize, byte_size)
|
||||
|
||||
yield fragment if block
|
||||
end
|
||||
|
|
@ -101,20 +98,17 @@ module Gitlab
|
|||
start_time = nil
|
||||
read_total_timeout = options.fetch(:timeout, DEFAULT_READ_TOTAL_TIMEOUT)
|
||||
byte_size = 0
|
||||
already_logged = false
|
||||
max_bytesize = options.fetch(:max_bytes, 0)
|
||||
|
||||
httparty_perform_request(http_method, path, options_with_timeouts) do |fragment|
|
||||
start_time ||= system_monotonic_time
|
||||
elapsed = system_monotonic_time - start_time
|
||||
byte_size += fragment.bytesize if should_log_response_size?
|
||||
|
||||
if should_log_response_size? && byte_size > expected_max_response_size && !already_logged
|
||||
configuration.log_with_level(:debug, message: 'gitlab/http: response size', size: byte_size)
|
||||
already_logged = true
|
||||
end
|
||||
byte_size += fragment.bytesize
|
||||
|
||||
raise ReadTotalTimeout, "Request timed out after #{elapsed} seconds" if elapsed > read_total_timeout
|
||||
|
||||
check_max_byte_size_limit!(max_bytesize, byte_size)
|
||||
|
||||
yield fragment if block
|
||||
end
|
||||
rescue HTTParty::RedirectionTooDeep
|
||||
|
|
@ -141,19 +135,18 @@ module Gitlab
|
|||
raise SilentModeBlockedError, 'only get, head, options, and trace methods are allowed in silent mode'
|
||||
end
|
||||
|
||||
def check_max_byte_size_limit!(max_bytesize, byte_size)
|
||||
return unless max_bytesize > 0 && byte_size > max_bytesize
|
||||
|
||||
configuration.log_with_level(:error,
|
||||
{ message: 'Gitlab::HTTP - Response size too large', size: byte_size })
|
||||
|
||||
raise ResponseSizeTooLarge, "Response size over #{max_bytesize} bytes"
|
||||
end
|
||||
|
||||
def system_monotonic_time
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
|
||||
end
|
||||
|
||||
def should_log_response_size?
|
||||
return @should_log_response_size if instance_variable_defined?(:@should_log_response_size)
|
||||
|
||||
@should_log_response_size = ENV["GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE"].to_i.positive?
|
||||
end
|
||||
|
||||
def expected_max_response_size
|
||||
ENV["GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE"].to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ module Gitlab
|
|||
ReadTotalTimeout = Class.new(Net::ReadTimeout)
|
||||
HeaderReadTimeout = Class.new(Net::ReadTimeout)
|
||||
SilentModeBlockedError = Class.new(StandardError)
|
||||
ResponseSizeTooLarge = Class.new(StandardError)
|
||||
MaxDecompressionSizeError = Class.new(StandardError)
|
||||
|
||||
HTTP_TIMEOUT_ERRORS = [
|
||||
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Gitlab::HTTP_V2::ReadTotalTimeout
|
||||
|
|
@ -18,7 +20,7 @@ module Gitlab
|
|||
EOFError, SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
|
||||
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH,
|
||||
Gitlab::HTTP_V2::BlockedUrlError, Gitlab::HTTP_V2::RedirectionTooDeep,
|
||||
Net::HTTPBadResponse
|
||||
Net::HTTPBadResponse, Gitlab::HTTP_V2::ResponseSizeTooLarge, Gitlab::HTTP_V2::ResponseSizeTooLarge
|
||||
].freeze
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -246,67 +246,72 @@ RSpec.describe Gitlab::HTTP_V2, feature_category: :shared do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'logging response size' do
|
||||
context 'when GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE is not set' do
|
||||
before do
|
||||
stub_full_request('http://example.org', method: :any).to_return(status: 200, body: 'hello world')
|
||||
end
|
||||
|
||||
it 'does not log response size' do
|
||||
expect(described_class.configuration)
|
||||
.not_to receive(:log_with_level)
|
||||
|
||||
described_class.get('http://example.org')
|
||||
end
|
||||
|
||||
context 'when the request is async' do
|
||||
it 'does not log response size' do
|
||||
expect(described_class.configuration)
|
||||
.not_to receive(:log_with_level)
|
||||
|
||||
described_class.get('http://example.org', async: true).execute.value
|
||||
end
|
||||
end
|
||||
describe 'response size limits' do
|
||||
before do
|
||||
stub_full_request('http://example.org', method: :any).to_return(status: 200, body: 'hello world')
|
||||
end
|
||||
|
||||
context 'when GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE is set' do
|
||||
before do
|
||||
described_class::Client.remove_instance_variable(:@should_log_response_size)
|
||||
stub_env('GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE', 5)
|
||||
stub_full_request('http://example.org', method: :any).to_return(status: 200, body: 'hello world')
|
||||
it 'logs and raises an error if response size is greater than max_bytes' do
|
||||
expect(described_class.configuration).to receive(:log_with_level)
|
||||
.with(:error, { message: "Gitlab::HTTP - Response size too large", size: 11 })
|
||||
|
||||
expect do
|
||||
described_class.get('http://example.org', max_bytes: 1.byte)
|
||||
end.to raise_error(Gitlab::HTTP_V2::ResponseSizeTooLarge)
|
||||
end
|
||||
|
||||
it 'returns the response if size is less than max_bytes' do
|
||||
expect(described_class.configuration).not_to receive(:log_with_level)
|
||||
|
||||
result = described_class.put('http://example.org', max_bytes: 16.bytes)
|
||||
|
||||
expect(result.body).to eq('hello world')
|
||||
end
|
||||
|
||||
it 'returns the response if max_bytes is not provided' do
|
||||
result = described_class.put('http://example.org')
|
||||
|
||||
expect(result.body).to eq('hello world')
|
||||
end
|
||||
|
||||
it 'returns the response if max_bytes is 0' do
|
||||
result = described_class.put('http://example.org', max_bytes: 0)
|
||||
|
||||
expect(result.body).to eq('hello world')
|
||||
end
|
||||
|
||||
context 'when the request is async' do
|
||||
it 'logs and raises an error if response size is greater than max_bytes' do
|
||||
expect(described_class.configuration).to receive(:log_with_level)
|
||||
.with(:error, { message: "Gitlab::HTTP - Response size too large", size: 11 })
|
||||
|
||||
expect do
|
||||
described_class.get('http://example.org', max_bytes: 1.byte, async: true).execute.value
|
||||
end.to raise_error(Gitlab::HTTP_V2::ResponseSizeTooLarge)
|
||||
end
|
||||
|
||||
it 'logs the response size' do
|
||||
expect(described_class.configuration)
|
||||
.to receive(:log_with_level)
|
||||
.with(:debug, { message: "gitlab/http: response size", size: 11 })
|
||||
.once
|
||||
it 'returns the response if size is less than max_bytes' do
|
||||
expect(described_class.configuration).not_to receive(:log_with_level)
|
||||
|
||||
described_class.get('http://example.org')
|
||||
result = described_class.put('http://example.org', max_bytes: 16.bytes, async: true)
|
||||
|
||||
expect(result.execute.value.body).to eq('hello world')
|
||||
end
|
||||
|
||||
context 'when the request is async' do
|
||||
it 'logs response size' do
|
||||
expect(described_class.configuration)
|
||||
.to receive(:log_with_level)
|
||||
.with(:debug, { message: "gitlab/http: response size", size: 11 })
|
||||
.once
|
||||
it 'returns the response if max_bytes is not provided' do
|
||||
expect(described_class.configuration).not_to receive(:log_with_level)
|
||||
|
||||
described_class.get('http://example.org', async: true).execute.value
|
||||
end
|
||||
result = described_class.put('http://example.org', async: true)
|
||||
|
||||
expect(result.execute.value.body).to eq('hello world')
|
||||
end
|
||||
|
||||
context 'and the response size is smaller than the limit' do
|
||||
before do
|
||||
stub_env('GITLAB_LOG_DECOMPRESSED_RESPONSE_BYTESIZE', 50)
|
||||
end
|
||||
it 'returns the response if max_bytes is 0' do
|
||||
expect(described_class.configuration).not_to receive(:log_with_level)
|
||||
|
||||
it 'does not log the response size' do
|
||||
expect(described_class.configuration)
|
||||
.not_to receive(:log_with_level)
|
||||
result = described_class.put('http://example.org', max_bytes: 0, async: true)
|
||||
|
||||
described_class.get('http://example.org')
|
||||
end
|
||||
expect(result.execute.value.body).to eq('hello world')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ module API
|
|||
|
||||
urgency :low, [
|
||||
'/projects/:id/merge_requests/:noteable_id/notes',
|
||||
'/projects/:id/merge_requests/:noteable_id/notes/:note_id'
|
||||
'/projects/:id/merge_requests/:noteable_id/notes/:note_id',
|
||||
'/projects/:id/issues/:noteable_id/notes',
|
||||
'/projects/:id/issues/:noteable_id/notes/:note_id'
|
||||
]
|
||||
|
||||
Helpers::NotesHelpers.noteable_types.each do |noteable_type|
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Atlassian
|
||||
module JiraConnect
|
||||
class Client < Gitlab::HTTP
|
||||
class Client
|
||||
def self.generate_update_sequence_id
|
||||
(Time.now.utc.to_f * 1000).round
|
||||
end
|
||||
|
|
@ -46,7 +46,7 @@ module Atlassian
|
|||
uri = URI.join(@base_uri, path)
|
||||
uri.query = URI.encode_www_form(query_params)
|
||||
|
||||
self.class.get(uri, headers: headers(uri, 'GET'))
|
||||
Integrations::Clients::HTTP.get(uri, headers: headers(uri, 'GET'))
|
||||
end
|
||||
|
||||
def store_ff_info(project:, feature_flags:, **opts)
|
||||
|
|
@ -124,13 +124,13 @@ module Atlassian
|
|||
def post(path, payload)
|
||||
uri = URI.join(@base_uri, path)
|
||||
|
||||
self.class.post(uri, headers: headers(uri), body: metadata.merge(payload).to_json)
|
||||
Integrations::Clients::HTTP.post(uri, headers: headers(uri), body: metadata.merge(payload).to_json)
|
||||
end
|
||||
|
||||
def delete(path)
|
||||
uri = URI.join(@base_uri, path)
|
||||
|
||||
self.class.delete(uri, headers: headers(uri, 'DELETE'))
|
||||
Integrations::Clients::HTTP.delete(uri, headers: headers(uri, 'DELETE'))
|
||||
end
|
||||
|
||||
def headers(uri, http_method = 'POST')
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module Bitbucket
|
|||
|
||||
def get(path, extra_query = {})
|
||||
response = retry_with_exponential_backoff do
|
||||
Gitlab::HTTP.get(build_url(path), basic_auth: basic_auth, headers: headers, query: extra_query)
|
||||
Import::Clients::HTTP.get(build_url(path), basic_auth: basic_auth, headers: headers, query: extra_query)
|
||||
end
|
||||
|
||||
response.parsed_response
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ module BitbucketServer
|
|||
|
||||
def get(path, extra_query = {})
|
||||
response = retry_with_delay do
|
||||
Gitlab::HTTP.get(build_url(path), basic_auth: auth, headers: accept_headers, query: extra_query)
|
||||
Import::Clients::HTTP.get(build_url(path), basic_auth: auth, headers: accept_headers, query: extra_query)
|
||||
end
|
||||
|
||||
check_errors!(response)
|
||||
|
|
@ -45,7 +45,7 @@ module BitbucketServer
|
|||
|
||||
def post(path, body)
|
||||
response = retry_with_delay do
|
||||
Gitlab::HTTP.post(build_url(path), basic_auth: auth, headers: post_headers, body: body)
|
||||
Import::Clients::HTTP.post(build_url(path), basic_auth: auth, headers: post_headers, body: body)
|
||||
end
|
||||
|
||||
check_errors!(response)
|
||||
|
|
@ -63,7 +63,7 @@ module BitbucketServer
|
|||
url = delete_url(resource, path)
|
||||
|
||||
response = retry_with_delay do
|
||||
Gitlab::HTTP.delete(url, basic_auth: auth, headers: post_headers, body: body)
|
||||
Import::Clients::HTTP.delete(url, basic_auth: auth, headers: post_headers, body: body)
|
||||
end
|
||||
|
||||
check_errors!(response)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module BulkImports
|
|||
end
|
||||
|
||||
def execute(query:, variables: {})
|
||||
response = ::Gitlab::HTTP.post(
|
||||
response = Import::Clients::HTTP.post(
|
||||
url,
|
||||
headers: headers,
|
||||
follow_redirects: false,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue