Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4d3677a52d
commit
1bbd0179d7
|
|
@ -328,7 +328,6 @@ Gitlab/StrongMemoizeAttr:
|
|||
- 'ee/app/models/vulnerabilities/finding.rb'
|
||||
- 'ee/app/presenters/approval_rule_presenter.rb'
|
||||
- 'ee/app/presenters/ci/minutes/usage_presenter.rb'
|
||||
- 'ee/app/presenters/merge_request_approver_presenter.rb'
|
||||
- 'ee/app/serializers/dashboard_operations_project_entity.rb'
|
||||
- 'ee/app/serializers/ee/member_user_entity.rb'
|
||||
- 'ee/app/services/app_sec/dast/pipelines/find_latest_service.rb'
|
||||
|
|
|
|||
|
|
@ -1222,7 +1222,6 @@ RSpec/BeforeAllRoleAssignment:
|
|||
- 'spec/requests/api/draft_notes_spec.rb'
|
||||
- 'spec/requests/api/environments_spec.rb'
|
||||
- 'spec/requests/api/error_tracking/client_keys_spec.rb'
|
||||
- 'spec/requests/api/error_tracking/project_settings_spec.rb'
|
||||
- 'spec/requests/api/files_spec.rb'
|
||||
- 'spec/requests/api/freeze_periods_spec.rb'
|
||||
- 'spec/requests/api/go_proxy_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1008,7 +1008,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'ee/spec/models/approval_merge_request_rule_spec.rb'
|
||||
- 'ee/spec/models/approval_state_spec.rb'
|
||||
- 'ee/spec/models/approval_wrapped_any_approver_rule_spec.rb'
|
||||
- 'ee/spec/models/approval_wrapped_code_owner_rule_spec.rb'
|
||||
- 'ee/spec/models/approval_wrapped_rule_spec.rb'
|
||||
- 'ee/spec/models/approvals/scan_finding_wrapped_rule_set_spec.rb'
|
||||
- 'ee/spec/models/approvals/wrapped_rule_set_spec.rb'
|
||||
|
|
|
|||
49
CHANGELOG.md
49
CHANGELOG.md
|
|
@ -2,6 +2,23 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 16.1.1 (2023-06-28)
|
||||
|
||||
### Security (12 changes)
|
||||
|
||||
- [Revert 'security-leaked-ci-job-token-permission-16-1' from '16-1'](gitlab-org/security/gitlab@d2599119b120eab983a1446fc9ed3ca801c88368) ([merge request](gitlab-org/security/gitlab!3374))
|
||||
- [Use fully qualified ref when loading code owner file](gitlab-org/security/gitlab@e8ba90bb85de376bb020350c027bb369671c83d6) ([merge request](gitlab-org/security/gitlab!3356))
|
||||
- [Maintainer can leak masked webhook secrets by manipulating URL masking](gitlab-org/security/gitlab@2cf91108544e8c30aae6d9b207385c90c299869c) ([merge request](gitlab-org/security/gitlab!3359))
|
||||
- [Remove approvals when the only commit gets amended](gitlab-org/security/gitlab@3f81f7bc4236bcc2ed887f40b7a14702d756ca9e) ([merge request](gitlab-org/security/gitlab!3366))
|
||||
- [Add authorization validation to GithubController#failures action](gitlab-org/security/gitlab@3c8c305deef9c9bd1194788b40e0d7ae1de45f3b) ([merge request](gitlab-org/security/gitlab!3335))
|
||||
- [Fix for fork permissions check in compare controller](gitlab-org/security/gitlab@5b14436f3874de7be62e0f46a25e93a1d8c99975) ([merge request](gitlab-org/security/gitlab!3342))
|
||||
- [Webhook token leaked in Sidekiq logs if log format is 'default'](gitlab-org/security/gitlab@d2d76399c880c62d7449cdae6014ee3236bffc0b) ([merge request](gitlab-org/security/gitlab!3345))
|
||||
- [Mitigate epic reference filter ReDOS](gitlab-org/security/gitlab@874d5bc2d55e2e1092bf7cc4ebb0e53fc716d850) ([merge request](gitlab-org/security/gitlab!3341))
|
||||
- [Increasing security for CI_JOB_TOKEN on public and internal projects](gitlab-org/security/gitlab@c2aa392b932af04e395d67eb06a20b5c768ec683) ([merge request](gitlab-org/security/gitlab!3337))
|
||||
- [Adjust access to value stream create, edit and destroy actions](gitlab-org/security/gitlab@8a3645e265c71886951bdc03857837aacb57e558) ([merge request](gitlab-org/security/gitlab!3349))
|
||||
- [Sanitize user email addresses in admin confirm user dialog](gitlab-org/security/gitlab@70553e6ca6b3f244df37e306466e2d3b5d54f76b) ([merge request](gitlab-org/security/gitlab!3338))
|
||||
- [Obfuscate email of service desk issue creator in issue REST API](gitlab-org/security/gitlab@d0f27b8241ab53bee11f8ce6efb20811690a2d0d) ([merge request](gitlab-org/security/gitlab!3317))
|
||||
|
||||
## 16.1.0 (2023-06-21)
|
||||
|
||||
### Added (224 changes)
|
||||
|
|
@ -930,6 +947,23 @@ entry.
|
|||
- [Migrate custom CSS to utility classes](gitlab-org/gitlab@a67999317bec111d523c763fc865665d4ded0aaf) ([merge request](gitlab-org/gitlab!120745)) **GitLab Enterprise Edition**
|
||||
- [Remove the vsa_group_and_project_parity FF](gitlab-org/gitlab@d090818bdedb0e220928d8e456cf36c8bce81f42) ([merge request](gitlab-org/gitlab!120727)) **GitLab Enterprise Edition**
|
||||
|
||||
## 16.0.6 (2023-06-28)
|
||||
|
||||
### Security (12 changes)
|
||||
|
||||
- [Revert 'security-leaked-ci-job-token-permission-16-0' from '16-0'"](gitlab-org/security/gitlab@3c4fdbad26a123c581253fb501b5bace953a5e85) ([merge request](gitlab-org/security/gitlab!3373))
|
||||
- [Use fully qualified ref when loading code owner file](gitlab-org/security/gitlab@69c61fcbdc88873b60a217cfd3810364718417e9) ([merge request](gitlab-org/security/gitlab!3355))
|
||||
- [Maintainer can leak masked webhook secrets by manipulating URL masking](gitlab-org/security/gitlab@a3e055010523db5a1c346464e2589cc75f73629d) ([merge request](gitlab-org/security/gitlab!3360))
|
||||
- [Remove approvals when the only commit gets amended](gitlab-org/security/gitlab@01e59413e2570744dc34dd50efd2601dc91c8d2d) ([merge request](gitlab-org/security/gitlab!3367))
|
||||
- [Add authorization validation to GithubController#failures action](gitlab-org/security/gitlab@9eab0689991debab8c8a1afb9e32a3bac9978325) ([merge request](gitlab-org/security/gitlab!3334))
|
||||
- [Fix for fork permissions check in compare controller](gitlab-org/security/gitlab@da9bb4c761dfe7e8efdd910ed3fc89f348e47e90) ([merge request](gitlab-org/security/gitlab!3343))
|
||||
- [Webhook token leaked in Sidekiq logs if log format is 'default'](gitlab-org/security/gitlab@a9835cb72eddfae1748c66314618b3157a6bcb57) ([merge request](gitlab-org/security/gitlab!3346))
|
||||
- [Mitigate epic reference filter ReDOS](gitlab-org/security/gitlab@c8046028a30fe9dca7e141eec2acf3d4b49d93ee) ([merge request](gitlab-org/security/gitlab!3340))
|
||||
- [Increasing security for CI_JOB_TOKEN on public and internal projects](gitlab-org/security/gitlab@b67db0cdd9324633f4abb59bc27bca43e94e3362) ([merge request](gitlab-org/security/gitlab!3318))
|
||||
- [Adjust access to value stream create, edit and destroy actions](gitlab-org/security/gitlab@ee20f3f3a84a75c7e07e1aa6fde95761636a669f) ([merge request](gitlab-org/security/gitlab!3321))
|
||||
- [Sanitize user email addresses in admin confirm user dialog](gitlab-org/security/gitlab@545e0913336e823eb905a8bd86fe2905b321a284) ([merge request](gitlab-org/security/gitlab!3331))
|
||||
- [Obfuscate email of service desk issue creator in issue REST API](gitlab-org/security/gitlab@b921f10b565bafbd6d50d93d84d34b5f103839ea) ([merge request](gitlab-org/security/gitlab!3315))
|
||||
|
||||
## 16.0.5 (2023-06-16)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
|
@ -1765,6 +1799,21 @@ entry.
|
|||
- [Add index to group_group_links table](gitlab-org/gitlab@9a3f2c1a90b54074e61d0abf07101ce664198e81) ([merge request](gitlab-org/gitlab!117386))
|
||||
- [Validate the projects.creator_id foregin key synchronously](gitlab-org/gitlab@ed9351984a16f20506babf6eab6706b917904ed1) ([merge request](gitlab-org/gitlab!117147))
|
||||
|
||||
## 15.11.10 (2023-06-28)
|
||||
|
||||
### Security (10 changes)
|
||||
|
||||
- [Revert 'security-leaked-ci-job-token-permission-15-11' from '15-11'"](gitlab-org/security/gitlab@19f73bf5494d34b43eb8c807f860d545acae0c32) ([merge request](gitlab-org/security/gitlab!3375))
|
||||
- [Use fully qualified ref when loading code owner file](gitlab-org/security/gitlab@d7ffb4cca68373bff38bd05f0b8afc868cda9e04) ([merge request](gitlab-org/security/gitlab!3354))
|
||||
- [Maintainer can leak masked webhook secrets by manipulating URL masking](gitlab-org/security/gitlab@3a7ccdac5e41870fdce362c38d0a1d1437906fbd) ([merge request](gitlab-org/security/gitlab!3361))
|
||||
- [Remove approvals when the only commit gets amended](gitlab-org/security/gitlab@f8a4ad8be7e5fdf752f525ed58b94b1ce625b9a1) ([merge request](gitlab-org/security/gitlab!3368))
|
||||
- [Fix for fork permissions check in compare controller](gitlab-org/security/gitlab@8edf44b13e55ffe0c912f98134d0341a5a6bcd28) ([merge request](gitlab-org/security/gitlab!3344))
|
||||
- [Webhook token leaked in Sidekiq logs if log format is 'default'](gitlab-org/security/gitlab@02b58237085930c62ee277c9ebd89a0560f44a98) ([merge request](gitlab-org/security/gitlab!3347))
|
||||
- [Mitigate epic reference filter ReDOS](gitlab-org/security/gitlab@4c2cd6e5f7c994aca554be37d9ea9e5e114341f1) ([merge request](gitlab-org/security/gitlab!3339))
|
||||
- [Increasing security for CI_JOB_TOKEN on public and internal projects](gitlab-org/security/gitlab@4f8a00b2499e876df5b65eca921812fbb3215800) ([merge request](gitlab-org/security/gitlab!3319))
|
||||
- [Sanitize user email addresses in admin confirm user dialog](gitlab-org/security/gitlab@608c8001c349b0a62aae81850de669d3af02ab60) ([merge request](gitlab-org/security/gitlab!3332))
|
||||
- [Obfuscate email of service desk issue creator in issue REST API](gitlab-org/security/gitlab@a092ebc54cce4492f87f8ed2bf67c31793b0bd0e) ([merge request](gitlab-org/security/gitlab!3316))
|
||||
|
||||
## 15.11.9 (2023-06-15)
|
||||
|
||||
### Changed (1 change)
|
||||
|
|
|
|||
|
|
@ -114,8 +114,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<dom-element-listener :selector="$options.FORM_SELECTOR" @[$options.EVENT_SUCCESS]="onSuccess">
|
||||
<div>
|
||||
<hr />
|
||||
<div class="gl-pt-6">
|
||||
<h5>{{ header }}</h5>
|
||||
|
||||
<p v-if="information" data-testid="information-section">
|
||||
|
|
|
|||
|
|
@ -30,26 +30,19 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<hr />
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<h4 class="gl-mt-0"><slot name="title"></slot></h4>
|
||||
<slot name="description"></slot>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<input-copy-toggle-visibility
|
||||
:label="inputLabel"
|
||||
:label-for="inputId"
|
||||
:form-input-group-props="formInputGroupProps"
|
||||
:value="token"
|
||||
:copy-button-title="copyButtonTitle"
|
||||
>
|
||||
<template #description>
|
||||
<slot name="input-description"></slot>
|
||||
</template>
|
||||
</input-copy-toggle-visibility>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="gl-my-0"><slot name="title"></slot></h4>
|
||||
<slot name="description"></slot>
|
||||
<input-copy-toggle-visibility
|
||||
:label="inputLabel"
|
||||
:label-for="inputId"
|
||||
:form-input-group-props="formInputGroupProps"
|
||||
:value="token"
|
||||
:copy-button-title="copyButtonTitle"
|
||||
>
|
||||
<template #description>
|
||||
<slot name="input-description"></slot>
|
||||
</template>
|
||||
</input-copy-toggle-visibility>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -91,8 +91,10 @@ export default {
|
|||
>
|
||||
<template #title>{{ $options.i18n[tokenType].label }}</template>
|
||||
<template #description>
|
||||
<p>{{ $options.i18n[tokenType].description }}</p>
|
||||
<p>{{ $options.i18n.canNotAccessOtherData }}</p>
|
||||
<p class="gl-text-secondary">
|
||||
{{ $options.i18n[tokenType].description }}
|
||||
{{ $options.i18n.canNotAccessOtherData }}
|
||||
</p>
|
||||
</template>
|
||||
<template #input-description>
|
||||
<gl-sprintf :message="$options.i18n[tokenType].inputDescription">
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ async function loadEmojiWithNames() {
|
|||
}
|
||||
|
||||
export async function loadCustomEmojiWithNames() {
|
||||
if (document.body?.dataset?.group && window.gon?.features?.customEmoji) {
|
||||
if (document.body?.dataset?.groupFullPath && window.gon?.features?.customEmoji) {
|
||||
const client = createApolloClient();
|
||||
const { data } = await client.query({
|
||||
query: customEmojiQuery,
|
||||
variables: {
|
||||
groupPath: document.body.dataset.group,
|
||||
groupPath: document.body.dataset.groupFullPath,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ export default {
|
|||
class="gl-display-flex gl-align-items-center gl-mt-2 gl-font-sm"
|
||||
>
|
||||
<gl-emoji :data-name="user.status.emoji" class="gl-mr-1" />
|
||||
<span v-safe-html="user.status.message" class="gl-text-truncate"></span>
|
||||
<span v-safe-html="user.status.message_html" class="gl-text-truncate"></span>
|
||||
<gl-tooltip
|
||||
:target="() => $refs.statusTooltipTarget"
|
||||
boundary="viewport"
|
||||
placement="bottom"
|
||||
>
|
||||
<span v-safe-html="user.status.message"></span>
|
||||
<span v-safe-html="user.status.message_html"></span>
|
||||
</gl-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -330,14 +330,6 @@ li.note {
|
|||
height: 220px;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
margin-bottom: 20px;
|
||||
|
||||
a {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.card.card-body {
|
||||
margin-bottom: $gl-padding;
|
||||
|
||||
|
|
|
|||
|
|
@ -62,11 +62,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.navless-container {
|
||||
margin-top: $header-height;
|
||||
padding-top: $gl-padding * 2;
|
||||
}
|
||||
|
||||
.container-limited {
|
||||
max-width: $fixed-layout-width;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,16 +36,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// System Footer
|
||||
.with-system-footer {
|
||||
// navless pages' footer eg: login page
|
||||
// navless pages' footer border eg: login page
|
||||
&.devise-layout-html body .footer-container,
|
||||
&.devise-layout-html body hr.footer-fixed {
|
||||
bottom: $system-footer-height;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen-layout {
|
||||
.header-message,
|
||||
.footer-message {
|
||||
|
|
|
|||
|
|
@ -228,58 +228,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.devise-layout-html {
|
||||
.html-devise-layout {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
|
||||
&.with-system-header {
|
||||
.login-page-broadcast {
|
||||
margin-top: calc(#{$system-header-height} + #{$header-height});
|
||||
}
|
||||
}
|
||||
|
||||
// Fixes footer container to bottom of viewport
|
||||
body {
|
||||
// offset height of fixed header + 1 to avoid scroll
|
||||
height: calc(100% - 51px);
|
||||
padding-top: 48px; // Remove this line when the restyle_login_page feature flag is deleted. Instead, add self-align `center` to container, and maybe a top margin.
|
||||
|
||||
// offset without the header
|
||||
&.navless {
|
||||
height: calc(100% - 11px);
|
||||
&.with-system-header {
|
||||
padding-top: $system-header-height;
|
||||
padding-top: calc(#{$system-header-height} + 48px); // Remove this line when the restyle_login_page feature flag is deleted
|
||||
}
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.page-wrap {
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer-container,
|
||||
hr.footer-fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
background: var(--white, $white);
|
||||
}
|
||||
|
||||
.login-page-broadcast {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.navless-container {
|
||||
padding: 0 15px 65px; // height of footer + bottom padding of email confirmation link
|
||||
}
|
||||
|
||||
.flash-container {
|
||||
padding-bottom: 65px;
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
padding-bottom: 0;
|
||||
&.with-system-footer {
|
||||
.footer-container {
|
||||
padding-bottom: $system-footer-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@
|
|||
}
|
||||
|
||||
.wiki-list {
|
||||
min-height: $gl-spacing-scale-8;
|
||||
|
||||
&:hover {
|
||||
background: $gray-10;
|
||||
|
||||
|
|
|
|||
|
|
@ -622,10 +622,6 @@ body.navless {
|
|||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.navless-container {
|
||||
margin-top: var(--header-height, 48px);
|
||||
padding-top: 32px;
|
||||
}
|
||||
.btn {
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
|
|
@ -685,12 +681,6 @@ hr {
|
|||
margin: 1.5rem 0;
|
||||
border-top: 1px solid #ececef;
|
||||
}
|
||||
.footer-links {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.footer-links a {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.flash-container {
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
|
|
@ -777,9 +767,15 @@ svg {
|
|||
.gl-align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.gl-flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.gl-justify-content-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.gl-align-self-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
.gl-w-10 {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
|
@ -794,6 +790,9 @@ svg {
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
.gl-h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.gl-p-5 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
|
@ -805,6 +804,9 @@ svg {
|
|||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.gl-m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
.gl-mt-3 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
|
@ -823,6 +825,9 @@ svg {
|
|||
.gl-ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
.gl-gap-5 {
|
||||
gap: 1rem;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.gl-sm-mt-0 {
|
||||
margin-top: 0;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ module Analytics
|
|||
|
||||
included do
|
||||
before_action :authorize
|
||||
# Defining the before action here, because in the EE module we cannot define a before_action.
|
||||
# Reason: this is a module which is being included into a controller. This module is extended in EE.
|
||||
before_action :authorize_modification, only: %i[create destroy update] # rubocop:disable Rails/LexicallyScopedActionFilter
|
||||
end
|
||||
|
||||
def index
|
||||
|
|
@ -25,6 +28,10 @@ module Analytics
|
|||
def authorize
|
||||
authorize_read_cycle_analytics!
|
||||
end
|
||||
|
||||
def authorize_modification
|
||||
# no-op, overridden in EE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ class Import::GithubController < Import::BaseController
|
|||
include ActionView::Helpers::SanitizeHelper
|
||||
include Import::GithubOauth
|
||||
|
||||
before_action :authorize_owner_access!, except: [:new, :callback, :personal_access_token, :status, :details, :create,
|
||||
:realtime_changes, :cancel_all, :counts]
|
||||
before_action :verify_import_enabled
|
||||
before_action :provider_auth, only: [:status, :realtime_changes, :create]
|
||||
before_action :expire_etag_cache, only: [:status, :create]
|
||||
|
|
@ -92,8 +94,6 @@ class Import::GithubController < Import::BaseController
|
|||
end
|
||||
|
||||
def failures
|
||||
project = Project.imported_from(provider_name).find(params[:project_id])
|
||||
|
||||
unless project.import_finished?
|
||||
return render status: :bad_request, json: {
|
||||
message: _('The import is not complete.')
|
||||
|
|
@ -107,7 +107,6 @@ class Import::GithubController < Import::BaseController
|
|||
end
|
||||
|
||||
def cancel
|
||||
project = Project.imported_from(provider_name).find(params[:project_id])
|
||||
result = Import::Github::CancelProjectImportService.new(project, current_user).execute
|
||||
|
||||
if result[:status] == :success
|
||||
|
|
@ -168,6 +167,14 @@ class Import::GithubController < Import::BaseController
|
|||
|
||||
private
|
||||
|
||||
def project
|
||||
@project ||= Project.imported_from(provider_name).find(params[:project_id])
|
||||
end
|
||||
|
||||
def authorize_owner_access!
|
||||
return render_404 unless current_user.can?(:owner_access, project)
|
||||
end
|
||||
|
||||
def import_params
|
||||
params.permit(permitted_import_params)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -89,10 +89,14 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
# target == start_ref == from
|
||||
def target_project
|
||||
strong_memoize(:target_project) do
|
||||
next source_project.default_merge_request_target unless compare_params.key?(:from_project_id)
|
||||
next source_project if compare_params[:from_project_id].to_i == source_project.id
|
||||
|
||||
target_project = target_projects(source_project).find_by_id(compare_params[:from_project_id])
|
||||
target_project =
|
||||
if !compare_params.key?(:from_project_id)
|
||||
source_project.default_merge_request_target
|
||||
elsif compare_params[:from_project_id].to_i == source_project.id
|
||||
source_project
|
||||
else
|
||||
target_projects(source_project).find_by_id(compare_params[:from_project_id])
|
||||
end
|
||||
|
||||
# Just ignore the field if it points at a non-existent or hidden project
|
||||
next source_project unless target_project && can?(current_user, :read_code, target_project)
|
||||
|
|
|
|||
|
|
@ -727,6 +727,8 @@ module Types
|
|||
if minimum_access_level.nil?
|
||||
object.forks.public_or_visible_to_user(current_user)
|
||||
else
|
||||
return [] if current_user.nil?
|
||||
|
||||
object.forks.visible_to_user_and_access_level(current_user, minimum_access_level)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ module ApplicationHelper
|
|||
page: body_data_page,
|
||||
page_type_id: controller.params[:id],
|
||||
find_file: find_file_path,
|
||||
group: @group&.path
|
||||
group: @group&.path,
|
||||
group_full_path: @group&.full_path
|
||||
}.merge(project_data)
|
||||
end
|
||||
|
||||
|
|
@ -135,6 +136,7 @@ module ApplicationHelper
|
|||
project_id: @project.id,
|
||||
project: @project.path,
|
||||
group: @project.group&.path,
|
||||
group_full_path: @project.group&.full_path,
|
||||
namespace_id: @project.namespace&.id
|
||||
}
|
||||
end
|
||||
|
|
@ -328,7 +330,7 @@ module ApplicationHelper
|
|||
class_names << 'with-system-header' if appearance.show_header?
|
||||
class_names << 'with-system-footer' if appearance.show_footer?
|
||||
|
||||
class_names
|
||||
class_names.join(' ')
|
||||
end
|
||||
|
||||
# Returns active css class when condition returns true
|
||||
|
|
|
|||
|
|
@ -152,7 +152,8 @@ module SidebarsHelper
|
|||
customized: user.status&.customized?,
|
||||
availability: user.status&.availability.to_s,
|
||||
emoji: user.status&.emoji,
|
||||
message: user.status&.message_html&.html_safe,
|
||||
message_html: user.status&.message_html&.html_safe,
|
||||
message: user.status&.message,
|
||||
clear_after: user_clear_status_at(user)
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ module UsersHelper
|
|||
|
||||
def confirm_user_data(user)
|
||||
message = if user.unconfirmed_email.present?
|
||||
_('This user has an unconfirmed email address (%{email}). You may force a confirmation.') % { email: user.unconfirmed_email }
|
||||
safe_format(_('This user has an unconfirmed email address (%{email}). You may force a confirmation.'), email: user.unconfirmed_email)
|
||||
else
|
||||
_('This user has an unconfirmed email address. You may force a confirmation.')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class WebHook < ApplicationRecord
|
|||
|
||||
return if url_variables_were.blank? || interpolated_url_was == interpolated_url
|
||||
|
||||
self.url_variables = {} if url_variables_were.keys.intersection(url_variables.keys).any?
|
||||
self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any?
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SystemAccess
|
||||
def self.table_name_prefix
|
||||
'system_access_'
|
||||
end
|
||||
end
|
||||
|
|
@ -8,7 +8,7 @@ module ImportCsv
|
|||
@user = user
|
||||
@project = project
|
||||
@csv_io = csv_io
|
||||
@results = { success: 0, error_lines: [], parse_error: false }
|
||||
@results = { success: 0, error_lines: [], parse_error: false, preprocess_errors: {} }
|
||||
end
|
||||
|
||||
PreprocessError = Class.new(StandardError)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ImportCsv
|
||||
class PreprocessMilestonesService < BaseService
|
||||
def initialize(user, project, provided_titles)
|
||||
@user = user
|
||||
@project = project
|
||||
@provided_titles = provided_titles
|
||||
|
||||
@results = { success: 0, errors: nil }
|
||||
@milestone_errors = { missing: { header: {}, titles: [] } }
|
||||
end
|
||||
|
||||
attr_reader :user, :project, :provided_titles, :results, :milestone_errors
|
||||
|
||||
def execute
|
||||
available_milestones = find_milestones_by_titles
|
||||
return ServiceResponse.success if provided_titles.sort == available_milestones.sort
|
||||
|
||||
milestone_errors[:missing][:header] = 'Milestone'
|
||||
milestone_errors[:missing][:titles] = provided_titles.difference(available_milestones) || []
|
||||
ServiceResponse.error(message: "", payload: milestone_errors)
|
||||
end
|
||||
|
||||
def find_milestones_by_titles
|
||||
# Find if these milestones exist in the project or its group and group ancestors
|
||||
finder_params = {
|
||||
project_ids: [project.id],
|
||||
title: provided_titles
|
||||
}
|
||||
finder_params[:group_ids] = project.group.self_and_ancestors.select(:id) if project.group
|
||||
MilestonesFinder.new(finder_params).execute.map(&:title).uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -23,6 +23,24 @@ module Issuable
|
|||
|
||||
raise CSV::MalformedCSVError.new('Invalid CSV format - missing required headers.', 1)
|
||||
end
|
||||
|
||||
def preprocess!
|
||||
preprocess_milestones!
|
||||
|
||||
raise PreprocessError if results[:preprocess_errors].any?
|
||||
end
|
||||
|
||||
def preprocess_milestones!
|
||||
# Pre-Process Milestone if header is present
|
||||
return unless csv_data.lines.first.downcase.include?('milestone')
|
||||
|
||||
provided_titles = with_csv_lines.filter_map { |row| row[:milestone]&.strip&.downcase }.uniq
|
||||
result = ::ImportCsv::PreprocessMilestonesService.new(user, project, provided_titles).execute
|
||||
return if result.success?
|
||||
|
||||
# collate errors here and throw errors
|
||||
results[:preprocess_errors][:milestone_errors] = result.payload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
%hr.footer-fixed
|
||||
.container.footer-container.gl-display-flex.gl-justify-content-space-between
|
||||
.footer-links
|
||||
- unless public_visibility_restricted?
|
||||
= link_to _("Explore"), explore_root_path
|
||||
= link_to _("Help"), help_path
|
||||
= link_to _("About GitLab"), "https://#{ApplicationHelper.promo_host}"
|
||||
= link_to _("Community forum"), ApplicationHelper.community_forum, target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
|
||||
= render 'devise/shared/language_switcher'
|
||||
.footer-container.gl-w-full.gl-align-self-end
|
||||
%hr.gl-m-0
|
||||
.container.gl-py-5.gl-display-flex.gl-justify-content-space-between
|
||||
.gl-display-flex.gl-gap-5.gl-flex-wrap
|
||||
- unless public_visibility_restricted?
|
||||
= link_to _("Explore"), explore_root_path
|
||||
= link_to _("Help"), help_path
|
||||
= link_to _("About GitLab"), "https://#{ApplicationHelper.promo_host}"
|
||||
= link_to _("Community forum"), ApplicationHelper.community_forum, target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
|
||||
= render 'devise/shared/language_switcher'
|
||||
= footer_message
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
- add_page_specific_style 'page_bundles/login'
|
||||
- custom_text = custom_sign_in_description
|
||||
!!! 5
|
||||
%html.devise-layout-html{ class: system_message_class }
|
||||
%html.html-devise-layout{ lang: "en" }
|
||||
= render "layouts/head", { startup_filename: 'signin' }
|
||||
%body.login-page.application.navless{ class: "#{user_application_theme} #{client_class_list}", data: { page: body_data_page, qa_selector: 'login_page' } }
|
||||
%body.gl-h-full.login-page.navless{ class: "#{system_message_class} #{user_application_theme} #{client_class_list}", data: { page: body_data_page, qa_selector: 'login_page' } }
|
||||
= header_message
|
||||
= render "layouts/init_client_detection_flags"
|
||||
- if Feature.enabled?(:restyle_login_page, @project)
|
||||
.page-wrap.borderless
|
||||
.container.navless-container
|
||||
.gl-h-full.borderless.gl-display-flex.gl-flex-wrap
|
||||
.container
|
||||
.content
|
||||
= render "layouts/flash"
|
||||
- if custom_text.present?
|
||||
|
|
@ -33,11 +33,11 @@
|
|||
.gl-w-half.gl-xs-w-full.gl-ml-auto.gl-mr-auto.bar
|
||||
= yield
|
||||
|
||||
= render 'devise/shared/footer', footer_message: footer_message
|
||||
= render 'devise/shared/footer'
|
||||
- else
|
||||
.page-wrap
|
||||
= render "layouts/header/empty"
|
||||
.container.navless-container
|
||||
= render "layouts/header/empty"
|
||||
.gl-h-full.gl-display-flex.gl-flex-wrap
|
||||
.container
|
||||
.content
|
||||
= render "layouts/flash"
|
||||
.row.mt-3
|
||||
|
|
@ -63,4 +63,4 @@
|
|||
.col-md-6.order-1.new-session-forms-container{ class: recently_confirmed_com? ? 'order-sm-first' : 'order-sm-12' }
|
||||
= yield
|
||||
|
||||
= render 'devise/shared/footer', footer_message: footer_message
|
||||
= render 'devise/shared/footer'
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
- add_page_specific_style 'page_bundles/login'
|
||||
!!! 5
|
||||
%html.devise-layout-html{ lang: "en", class: system_message_class }
|
||||
%html.html-devise-layout{ lang: "en" }
|
||||
= render "layouts/head"
|
||||
%body.login-page.application.navless{ class: "#{user_application_theme} #{client_class_list}" }
|
||||
%body.gl-h-full.login-page.navless{ class: "#{system_message_class} #{user_application_theme} #{client_class_list}" }
|
||||
= header_message
|
||||
= render "layouts/init_client_detection_flags"
|
||||
= render "layouts/header/empty"
|
||||
= render "layouts/broadcast"
|
||||
.container.navless-container
|
||||
.content
|
||||
= render "layouts/flash"
|
||||
= yield
|
||||
.gl-h-full.gl-display-flex.gl-flex-wrap
|
||||
.container
|
||||
.content
|
||||
= render "layouts/flash"
|
||||
= yield
|
||||
|
||||
= render 'devise/shared/footer', footer_message: footer_message
|
||||
= render 'devise/shared/footer'
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
- add_page_specific_style 'page_bundles/signup'
|
||||
- add_page_specific_style 'page_bundles/login'
|
||||
!!! 5
|
||||
%html.devise-layout-html.navless{ class: system_message_class }
|
||||
- add_page_specific_style 'page_bundles/signup'
|
||||
- add_page_specific_style 'page_bundles/login'
|
||||
%html.html-devise-layout{ lang: 'en' }
|
||||
= render "layouts/head"
|
||||
%body.signup-page{ class: "#{user_application_theme} #{client_class_list}", data: { page: body_data_page, qa_selector: 'signup_page' } }
|
||||
= render "layouts/header/logo_with_title"
|
||||
%body.signup-page.navless{ class: "#{system_message_class} #{user_application_theme} #{client_class_list}", data: { page: body_data_page, qa_selector: 'signup_page' } }
|
||||
= header_message
|
||||
= render "layouts/init_client_detection_flags"
|
||||
.page-wrap
|
||||
.container.signup-box-container.navless-container
|
||||
= render "layouts/broadcast"
|
||||
.content
|
||||
= yield
|
||||
= render "layouts/header/logo_with_title"
|
||||
.container
|
||||
.content
|
||||
= yield
|
||||
|
||||
= footer_message
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
- text_style = 'font-size:16px; text-align:center; line-height:30px;'
|
||||
- error_style = 'font-size:13px; text-align:center; line-height:16px; color:#dd2b0e;'
|
||||
|
||||
%p{ style: text_style }
|
||||
- project_link = link_to(@project.full_name, project_url(@project), style: "color:#3777b0; text-decoration:none;")
|
||||
|
|
@ -16,3 +17,18 @@
|
|||
- if @results[:parse_error]
|
||||
%p{ style: text_style }
|
||||
= s_('Notify|Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.')
|
||||
|
||||
- preprocess_errors = @results[:preprocess_errors]
|
||||
- if preprocess_errors.present?
|
||||
|
||||
- missing_milestone_errors = preprocess_errors.dig(:milestone_errors, :missing) || []
|
||||
|
||||
- if missing_milestone_errors.present?
|
||||
%p{ style: error_style }
|
||||
= s_('Notify|Could not find the following %{column} values in %{project}%{parent_groups_clause}: %{error_lines}') % { error_lines: missing_milestone_errors[:titles].join(', '),
|
||||
column: missing_milestone_errors[:header].downcase, project: @project.full_name,
|
||||
parent_groups_clause: @project.group.present? ? ' or its parent groups' : ''}
|
||||
|
||||
- if @results[:error_lines].present? || preprocess_errors.present?
|
||||
%p{ style: text_style }
|
||||
= s_('Notify|Please fix the errors above and try the CSV import again.')
|
||||
|
|
|
|||
|
|
@ -9,3 +9,20 @@ Errors found on line <%= 'number'.pluralize(@results[:error_lines].size) %>: <%=
|
|||
<% if @results[:parse_error] %>
|
||||
Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.
|
||||
<% end %>
|
||||
|
||||
<% preprocess_errors = @results[:preprocess_errors] %>
|
||||
<%
|
||||
if preprocess_errors.present?
|
||||
missing_milestone_errors = preprocess_errors.dig(:milestone_errors, :missing) || []
|
||||
%>
|
||||
|
||||
<% if missing_milestone_errors.present? %>
|
||||
<%= s_('Notify|Could not find the following %{column} values in %{project}%{parent_groups_clause}: %{error_lines}') % { error_lines: missing_milestone_errors[:titles].join(', '),
|
||||
column: missing_milestone_errors[:header].downcase, project: @project.full_name,
|
||||
parent_groups_clause: @project.group.present? ? ' or its parent groups' : ''} %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @results[:error_lines].present? || preprocess_errors.present? %>
|
||||
<%= s_('Notify|Please fix the errors above and try the CSV import again.') %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -4,27 +4,24 @@
|
|||
- type_plural = _('personal access tokens')
|
||||
- @force_desktop_expanded_sidebar = true
|
||||
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
%p
|
||||
= s_('AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API.')
|
||||
%p
|
||||
= s_('AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP.')
|
||||
= s_('AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.')
|
||||
.gl-pb-6.js-search-settings-section
|
||||
%h4.gl-my-0
|
||||
= page_title
|
||||
%p.gl-text-secondary
|
||||
= s_('AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API.')
|
||||
= s_('AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP.')
|
||||
= s_('AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.')
|
||||
|
||||
.col-lg-8
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
#js-new-access-token-app{ data: { access_token_type: type } }
|
||||
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: profile_personal_access_tokens_path,
|
||||
token: @personal_access_token,
|
||||
scopes: @scopes,
|
||||
help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
|
||||
= render 'shared/access_tokens/form',
|
||||
ajax: true,
|
||||
type: type,
|
||||
path: profile_personal_access_tokens_path,
|
||||
token: @personal_access_token,
|
||||
scopes: @scopes,
|
||||
help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
|
||||
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json } }
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json } }
|
||||
|
||||
#js-tokens-app{ data: { tokens_data: tokens_app_data } }
|
||||
|
|
|
|||
|
|
@ -9,34 +9,25 @@
|
|||
|
||||
%h5.gl-mt-0
|
||||
= title
|
||||
%p.profile-settings-content
|
||||
= s_("AccessTokens|Enter the name of your application, and we'll return a unique %{type}.") % { type: type }
|
||||
|
||||
= gitlab_ui_form_for token, as: prefix, url: path, method: :post, html: { id: 'js-new-access-token-form', class: 'js-requires-input' }, remote: ajax do |f|
|
||||
|
||||
= form_errors(token)
|
||||
|
||||
.row
|
||||
.form-group.col
|
||||
.row
|
||||
= f.label :name, s_('AccessTokens|Token name'), class: 'label-bold col-md-12'
|
||||
.col-md-6
|
||||
- resource_type = resource.is_a?(Group) ? "group" : "project"
|
||||
= f.text_field :name, class: 'form-control gl-form-input', required: true, data: { qa_selector: 'access_token_name_field' }, :'aria-describedby' => 'access_token_help_text'
|
||||
%span.form-text.text-muted.col-md-12#access_token_help_text= s_("AccessTokens|For example, the application using the token or the purpose of the token. Do not give sensitive information for the name of the token, as it will be visible to all %{resource_type} members.") % { resource_type: resource_type }
|
||||
.form-group
|
||||
= f.label :name, s_('AccessTokens|Token name'), class: 'label-bold'
|
||||
- resource_type = resource.is_a?(Group) ? "group" : "project"
|
||||
= f.text_field :name, class: 'form-control gl-form-input gl-max-w-80', required: true, data: { qa_selector: 'access_token_name_field' }, :'aria-describedby' => 'access_token_help_text'
|
||||
%span.form-text.text-muted#access_token_help_text= s_("AccessTokens|For example, the application using the token or the purpose of the token. Do not give sensitive information for the name of the token, as it will be visible to all %{resource_type} members.") % { resource_type: resource_type }
|
||||
|
||||
.row
|
||||
.col
|
||||
.js-access-tokens-expires-at{ data: expires_at_field_data }
|
||||
= f.text_field :expires_at, class: 'gl-datepicker-input form-control gl-form-input', placeholder: 'YYYY-MM-DD', autocomplete: 'off', data: { js_name: 'expiresAt' }
|
||||
.js-access-tokens-expires-at{ data: expires_at_field_data }
|
||||
= f.text_field :expires_at, class: 'gl-datepicker-input form-control gl-form-input', placeholder: 'YYYY-MM-DD', autocomplete: 'off', data: { js_name: 'expiresAt' }
|
||||
|
||||
- if resource
|
||||
.row
|
||||
.form-group.col-md-6
|
||||
= label_tag :access_level, s_("AccessTokens|Select a role"), class: "label-bold"
|
||||
.select-wrapper
|
||||
= select_tag :"#{prefix}[access_level]", options_for_select(access_levels, default_access_level), class: "form-control select-control", data: { qa_selector: 'access_token_access_level' }
|
||||
= sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
|
||||
.form-group
|
||||
= label_tag :access_level, s_("AccessTokens|Select a role"), class: "label-bold"
|
||||
.select-wrapper
|
||||
= select_tag :"#{prefix}[access_level]", options_for_select(access_levels, default_access_level), class: "form-control select-control", data: { qa_selector: 'access_token_access_level' }
|
||||
= sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
|
||||
|
||||
.form-group
|
||||
%b{ :'aria-describedby' => 'select_scope_help_text' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.ci_templates.p_ci_templates_security_dast_on_demand_api_scan_monthly
|
||||
name: "dast_on_demand_api_scan"
|
||||
description: Count of pipelines using the latest DAST API template
|
||||
product_section: sec
|
||||
product_stage: secure
|
||||
product_group: "dynamic_analysis"
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73564
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- p_ci_templates_security_dast_on_demand_api_scan
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.ci_templates.p_ci_templates_security_dast_on_demand_api_scan_weekly
|
||||
name: "dast_on_demand_api_scan"
|
||||
description: Count of pipelines using the latest DAST API template
|
||||
product_section: sec
|
||||
product_stage: secure
|
||||
product_group: "dynamic_analysis"
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73564
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- p_ci_templates_security_dast_on_demand_api_scan
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
table_name: system_access_microsoft_applications
|
||||
classes:
|
||||
- SystemAccess::MicrosoftApplication
|
||||
feature_categories:
|
||||
- system_access
|
||||
description: Integration with Microsoft Azure application
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124101
|
||||
milestone: '16.2'
|
||||
gitlab_schema: gitlab_main
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
table_name: system_access_microsoft_graph_access_tokens
|
||||
classes:
|
||||
- SystemAccess::MicrosoftGraphAccessToken
|
||||
feature_categories:
|
||||
- system_access
|
||||
description: Access tokens for the Microsoft Graph API
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124101
|
||||
milestone: '16.2'
|
||||
gitlab_schema: gitlab_main
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateSystemAccessMicrosoftApplication < Gitlab::Database::Migration[2.1]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
create_table :system_access_microsoft_applications do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.references :namespace, index: { unique: true }, foreign_key: { on_delete: :cascade }
|
||||
t.boolean :enabled, null: false, default: false
|
||||
t.text :tenant_xid, null: false, limit: 255
|
||||
t.text :client_xid, null: false, limit: 255
|
||||
t.text :login_endpoint, null: false, limit: 255, default: 'https://login.microsoftonline.com'
|
||||
t.text :graph_endpoint, null: false, limit: 255, default: 'https://graph.microsoft.com'
|
||||
t.binary :encrypted_client_secret, null: false
|
||||
t.binary :encrypted_client_secret_iv, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateSystemAccessMicrosoftGraphAccessTokens < Gitlab::Database::Migration[2.1]
|
||||
def change
|
||||
create_table :system_access_microsoft_graph_access_tokens do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.references :system_access_microsoft_application,
|
||||
index: { name: 'unique_index_sysaccess_ms_access_tokens_on_sysaccess_ms_app_id', unique: true },
|
||||
foreign_key: { on_delete: :cascade }
|
||||
t.integer :expires_in, null: false
|
||||
t.binary :encrypted_token, null: false
|
||||
t.binary :encrypted_token_iv, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
de2c254df58e13ffba7fef9bbf4fff2e244aa46ce58f8245646ed7ce4ab51770
|
||||
|
|
@ -0,0 +1 @@
|
|||
29cf1dfb1429cb177f5b6cb2fae2a0bc388c0c6cbda5c4405456afcee8374a54
|
||||
|
|
@ -23116,6 +23116,52 @@ CREATE SEQUENCE suggestions_id_seq
|
|||
|
||||
ALTER SEQUENCE suggestions_id_seq OWNED BY suggestions.id;
|
||||
|
||||
CREATE TABLE system_access_microsoft_applications (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
namespace_id bigint,
|
||||
enabled boolean DEFAULT false NOT NULL,
|
||||
tenant_xid text NOT NULL,
|
||||
client_xid text NOT NULL,
|
||||
login_endpoint text DEFAULT 'https://login.microsoftonline.com'::text NOT NULL,
|
||||
graph_endpoint text DEFAULT 'https://graph.microsoft.com'::text NOT NULL,
|
||||
encrypted_client_secret bytea NOT NULL,
|
||||
encrypted_client_secret_iv bytea NOT NULL,
|
||||
CONSTRAINT check_042f6b21aa CHECK ((char_length(login_endpoint) <= 255)),
|
||||
CONSTRAINT check_1e8b2d405f CHECK ((char_length(tenant_xid) <= 255)),
|
||||
CONSTRAINT check_339c3ffca8 CHECK ((char_length(graph_endpoint) <= 255)),
|
||||
CONSTRAINT check_ee72fb5459 CHECK ((char_length(client_xid) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE system_access_microsoft_applications_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE system_access_microsoft_applications_id_seq OWNED BY system_access_microsoft_applications.id;
|
||||
|
||||
CREATE TABLE system_access_microsoft_graph_access_tokens (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
system_access_microsoft_application_id bigint,
|
||||
expires_in integer NOT NULL,
|
||||
encrypted_token bytea NOT NULL,
|
||||
encrypted_token_iv bytea NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE system_access_microsoft_graph_access_tokens_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE system_access_microsoft_graph_access_tokens_id_seq OWNED BY system_access_microsoft_graph_access_tokens.id;
|
||||
|
||||
CREATE TABLE system_note_metadata (
|
||||
id integer NOT NULL,
|
||||
commit_count integer,
|
||||
|
|
@ -25885,6 +25931,10 @@ ALTER TABLE ONLY subscriptions ALTER COLUMN id SET DEFAULT nextval('subscription
|
|||
|
||||
ALTER TABLE ONLY suggestions ALTER COLUMN id SET DEFAULT nextval('suggestions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY system_access_microsoft_applications ALTER COLUMN id SET DEFAULT nextval('system_access_microsoft_applications_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY system_access_microsoft_graph_access_tokens ALTER COLUMN id SET DEFAULT nextval('system_access_microsoft_graph_access_tokens_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY system_note_metadata ALTER COLUMN id SET DEFAULT nextval('system_note_metadata_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY taggings ALTER COLUMN id SET DEFAULT nextval('taggings_id_seq'::regclass);
|
||||
|
|
@ -28379,6 +28429,12 @@ ALTER TABLE ONLY subscriptions
|
|||
ALTER TABLE ONLY suggestions
|
||||
ADD CONSTRAINT suggestions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY system_access_microsoft_applications
|
||||
ADD CONSTRAINT system_access_microsoft_applications_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY system_access_microsoft_graph_access_tokens
|
||||
ADD CONSTRAINT system_access_microsoft_graph_access_tokens_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY system_note_metadata
|
||||
ADD CONSTRAINT system_note_metadata_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -32950,6 +33006,8 @@ CREATE INDEX index_successful_deployments_on_cluster_id_and_environment_id ON de
|
|||
|
||||
CREATE UNIQUE INDEX index_suggestions_on_note_id_and_relative_order ON suggestions USING btree (note_id, relative_order);
|
||||
|
||||
CREATE UNIQUE INDEX index_system_access_microsoft_applications_on_namespace_id ON system_access_microsoft_applications USING btree (namespace_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_system_note_metadata_on_description_version_id ON system_note_metadata USING btree (description_version_id) WHERE (description_version_id IS NOT NULL);
|
||||
|
||||
CREATE UNIQUE INDEX index_system_note_metadata_on_note_id ON system_note_metadata USING btree (note_id);
|
||||
|
|
@ -33620,6 +33678,8 @@ CREATE UNIQUE INDEX unique_index_for_project_pages_unique_domain ON project_sett
|
|||
|
||||
CREATE UNIQUE INDEX unique_index_on_system_note_metadata_id ON resource_link_events USING btree (system_note_metadata_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_index_sysaccess_ms_access_tokens_on_sysaccess_ms_app_id ON system_access_microsoft_graph_access_tokens USING btree (system_access_microsoft_application_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_instance_audit_event_destination_name ON audit_events_instance_external_audit_event_destinations USING btree (name);
|
||||
|
||||
CREATE UNIQUE INDEX unique_merge_request_diff_llm_summaries_on_mr_diff_id ON merge_request_diff_llm_summaries USING btree (merge_request_diff_id);
|
||||
|
|
@ -36917,6 +36977,9 @@ ALTER TABLE ONLY incident_management_oncall_participants
|
|||
ALTER TABLE ONLY work_item_parent_links
|
||||
ADD CONSTRAINT fk_rails_601d5bec3a FOREIGN KEY (work_item_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY system_access_microsoft_graph_access_tokens
|
||||
ADD CONSTRAINT fk_rails_604908851f FOREIGN KEY (system_access_microsoft_application_id) REFERENCES system_access_microsoft_applications(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY vulnerability_state_transitions
|
||||
ADD CONSTRAINT fk_rails_60e4899648 FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -37607,6 +37670,9 @@ ALTER TABLE ONLY ci_job_artifacts
|
|||
ALTER TABLE ONLY organization_settings
|
||||
ADD CONSTRAINT fk_rails_c56e4690c0 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY system_access_microsoft_applications
|
||||
ADD CONSTRAINT fk_rails_c5b7765d04 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY project_settings
|
||||
ADD CONSTRAINT fk_rails_c6df6e6328 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ Streaming destination is deleted if:
|
|||
- The returned `errors` object is empty.
|
||||
- The API responds with `200 OK`.
|
||||
|
||||
To remove an HTTP header, use the GraphQL `auditEventsStreamingInstanceHeadersDestroy` mutation.
|
||||
To remove an HTTP header, use the GraphQL `auditEventsStreamingInstanceHeadersDestroy` mutation.
|
||||
To retrieve the header ID,
|
||||
[list all the custom HTTP headers](#list-streaming-destinations) for the instance.
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ POST groups/:id/access_tokens
|
|||
| `name` | String | yes | Name of the group access token |
|
||||
| `scopes` | `Array[String]` | yes | [List of scopes](../user/group/settings/group_access_tokens.md#scopes-for-a-group-access-token) |
|
||||
| `access_level` | Integer | no | Access level. Valid values are `10` (Guest), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). |
|
||||
| `expires_at` | Date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). If no date is set, the expiration is set to the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#when-personal-access-tokens-expire). |
|
||||
| `expires_at` | Date | yes | Expiration date of the access token in ISO format (`YYYY-MM-DD`). The date cannot be set later than the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#when-personal-access-tokens-expire). |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ POST projects/:id/access_tokens
|
|||
| `name` | String | yes | Name of the project access token |
|
||||
| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#scopes-for-a-project-access-token) |
|
||||
| `access_level` | Integer | no | Access level. Valid values are `10` (Guest), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). Defaults to `40`. |
|
||||
| `expires_at` | Date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). If no date is set, the expiration is set to the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#when-personal-access-tokens-expire). |
|
||||
| `expires_at` | Date | yes | Expiration date of the access token in ISO format (`YYYY-MM-DD`). The date cannot be set later than the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#when-personal-access-tokens-expire). |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
|
|
|
|||
|
|
@ -578,6 +578,248 @@ GET /users/:user_id/projects
|
|||
]
|
||||
```
|
||||
|
||||
## List projects a user has contributed to
|
||||
|
||||
Get a list of visible projects a given user has contributed to.
|
||||
|
||||
```plaintext
|
||||
GET /users/:user_id/contributed_projects
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------------------------|---------|------------------------|-------------|
|
||||
| `user_id` | string | **{check-circle}** Yes | The ID or username of the user. |
|
||||
| `order_by` | string | **{dotted-circle}** No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. |
|
||||
| `simple` | boolean | **{dotted-circle}** No | Return only limited fields for each project. Without authentication, this operation is a no-op; only simple fields are returned. |
|
||||
| `sort` | string | **{dotted-circle}** No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/users/5/contributed_projects"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 4,
|
||||
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
"description_html": "<p data-sourcepos=\"1:1-1:56\" dir=\"auto\">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>",
|
||||
"default_branch": "master",
|
||||
"visibility": "private",
|
||||
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
|
||||
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
|
||||
"web_url": "http://example.com/diaspora/diaspora-client",
|
||||
"readme_url": "http://example.com/diaspora/diaspora-client/blob/master/README.md",
|
||||
"tag_list": [ //deprecated, use `topics` instead
|
||||
"example",
|
||||
"disapora client"
|
||||
],
|
||||
"topics": [
|
||||
"example",
|
||||
"disapora client"
|
||||
],
|
||||
"owner": {
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"created_at": "2013-09-30T13:46:02Z"
|
||||
},
|
||||
"name": "Diaspora Client",
|
||||
"name_with_namespace": "Diaspora / Diaspora Client",
|
||||
"path": "diaspora-client",
|
||||
"path_with_namespace": "diaspora/diaspora-client",
|
||||
"issues_enabled": true,
|
||||
"open_issues_count": 1,
|
||||
"merge_requests_enabled": true,
|
||||
"jobs_enabled": true,
|
||||
"wiki_enabled": true,
|
||||
"snippets_enabled": false,
|
||||
"can_create_merge_request_in": true,
|
||||
"resolve_outdated_diff_discussions": false,
|
||||
"container_registry_enabled": false, // deprecated, use container_registry_access_level instead
|
||||
"container_registry_access_level": "disabled",
|
||||
"security_and_compliance_access_level": "disabled",
|
||||
"created_at": "2013-09-30T13:46:02Z",
|
||||
"updated_at": "2013-09-30T13:46:02Z",
|
||||
"last_activity_at": "2013-09-30T13:46:02Z",
|
||||
"creator_id": 3,
|
||||
"namespace": {
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"path": "diaspora",
|
||||
"kind": "group",
|
||||
"full_path": "diaspora"
|
||||
},
|
||||
"import_status": "none",
|
||||
"archived": false,
|
||||
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
|
||||
"shared_runners_enabled": true,
|
||||
"group_runners_enabled": true,
|
||||
"forks_count": 0,
|
||||
"star_count": 0,
|
||||
"runners_token": "b8547b1dc37721d05889db52fa2f02",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
"allow_merge_on_skipped_pipeline": false,
|
||||
"restrict_user_defined_variables": false,
|
||||
"only_allow_merge_if_all_discussions_are_resolved": false,
|
||||
"remove_source_branch_after_merge": false,
|
||||
"request_access_enabled": false,
|
||||
"merge_method": "merge",
|
||||
"squash_option": "default_on",
|
||||
"autoclose_referenced_issues": true,
|
||||
"enforce_auth_checks_on_uploads": true,
|
||||
"suggestion_commit_message": null,
|
||||
"merge_commit_template": null,
|
||||
"squash_commit_template": null,
|
||||
"issue_branch_template": "gitlab/%{id}-%{title}",
|
||||
"statistics": {
|
||||
"commit_count": 37,
|
||||
"storage_size": 1038090,
|
||||
"repository_size": 1038090,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"pipeline_artifacts_size": 0,
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0,
|
||||
"uploads_size": 0
|
||||
},
|
||||
"container_registry_image_prefix": "registry.example.com/diaspora/diaspora-client",
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
"issues": "http://example.com/api/v4/projects/1/issues",
|
||||
"merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
|
||||
"repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
|
||||
"labels": "http://example.com/api/v4/projects/1/labels",
|
||||
"events": "http://example.com/api/v4/projects/1/events",
|
||||
"members": "http://example.com/api/v4/projects/1/members",
|
||||
"cluster_agents": "http://example.com/api/v4/projects/1/cluster_agents"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
"description_html": "<p data-sourcepos=\"1:1-1:56\" dir=\"auto\">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>",
|
||||
"default_branch": "master",
|
||||
"visibility": "private",
|
||||
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
|
||||
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
|
||||
"web_url": "http://example.com/brightbox/puppet",
|
||||
"readme_url": "http://example.com/brightbox/puppet/blob/master/README.md",
|
||||
"tag_list": [ //deprecated, use `topics` instead
|
||||
"example",
|
||||
"puppet"
|
||||
],
|
||||
"topics": [
|
||||
"example",
|
||||
"puppet"
|
||||
],
|
||||
"owner": {
|
||||
"id": 4,
|
||||
"name": "Brightbox",
|
||||
"created_at": "2013-09-30T13:46:02Z"
|
||||
},
|
||||
"name": "Puppet",
|
||||
"name_with_namespace": "Brightbox / Puppet",
|
||||
"path": "puppet",
|
||||
"path_with_namespace": "brightbox/puppet",
|
||||
"issues_enabled": true,
|
||||
"open_issues_count": 1,
|
||||
"merge_requests_enabled": true,
|
||||
"jobs_enabled": true,
|
||||
"wiki_enabled": true,
|
||||
"snippets_enabled": false,
|
||||
"can_create_merge_request_in": true,
|
||||
"resolve_outdated_diff_discussions": false,
|
||||
"container_registry_enabled": false, // deprecated, use container_registry_access_level instead
|
||||
"container_registry_access_level": "disabled",
|
||||
"security_and_compliance_access_level": "disabled",
|
||||
"created_at": "2013-09-30T13:46:02Z",
|
||||
"updated_at": "2013-09-30T13:46:02Z",
|
||||
"last_activity_at": "2013-09-30T13:46:02Z",
|
||||
"creator_id": 3,
|
||||
"namespace": {
|
||||
"id": 4,
|
||||
"name": "Brightbox",
|
||||
"path": "brightbox",
|
||||
"kind": "group",
|
||||
"full_path": "brightbox"
|
||||
},
|
||||
"import_status": "none",
|
||||
"import_error": null,
|
||||
"permissions": {
|
||||
"project_access": {
|
||||
"access_level": 10,
|
||||
"notification_level": 3
|
||||
},
|
||||
"group_access": {
|
||||
"access_level": 50,
|
||||
"notification_level": 3
|
||||
}
|
||||
},
|
||||
"archived": false,
|
||||
"avatar_url": null,
|
||||
"shared_runners_enabled": true,
|
||||
"group_runners_enabled": true,
|
||||
"forks_count": 0,
|
||||
"star_count": 0,
|
||||
"runners_token": "b8547b1dc37721d05889db52fa2f02",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
"allow_merge_on_skipped_pipeline": false,
|
||||
"restrict_user_defined_variables": false,
|
||||
"only_allow_merge_if_all_discussions_are_resolved": false,
|
||||
"remove_source_branch_after_merge": false,
|
||||
"request_access_enabled": false,
|
||||
"merge_method": "merge",
|
||||
"squash_option": "default_on",
|
||||
"auto_devops_enabled": true,
|
||||
"auto_devops_deploy_strategy": "continuous",
|
||||
"repository_storage": "default",
|
||||
"approvals_before_merge": 0, // Deprecated. Use merge request approvals API instead.
|
||||
"mirror": false,
|
||||
"mirror_user_id": 45,
|
||||
"mirror_trigger_builds": false,
|
||||
"only_mirror_protected_branches": false,
|
||||
"mirror_overwrites_diverged_branches": false,
|
||||
"external_authorization_classification_label": null,
|
||||
"packages_enabled": true,
|
||||
"service_desk_enabled": false,
|
||||
"service_desk_address": null,
|
||||
"autoclose_referenced_issues": true,
|
||||
"enforce_auth_checks_on_uploads": true,
|
||||
"suggestion_commit_message": null,
|
||||
"merge_commit_template": null,
|
||||
"squash_commit_template": null,
|
||||
"issue_branch_template": "gitlab/%{id}-%{title}",
|
||||
"statistics": {
|
||||
"commit_count": 12,
|
||||
"storage_size": 2066080,
|
||||
"repository_size": 2066080,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"pipeline_artifacts_size": 0,
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0,
|
||||
"uploads_size": 0
|
||||
},
|
||||
"container_registry_image_prefix": "registry.example.com/brightbox/puppet",
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
"issues": "http://example.com/api/v4/projects/1/issues",
|
||||
"merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
|
||||
"repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
|
||||
"labels": "http://example.com/api/v4/projects/1/labels",
|
||||
"events": "http://example.com/api/v4/projects/1/events",
|
||||
"members": "http://example.com/api/v4/projects/1/members",
|
||||
"cluster_agents": "http://example.com/api/v4/projects/1/cluster_agents"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## List projects starred by a user
|
||||
|
||||
> The `_links.cluster_agents` attribute in the response [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/347047) in GitLab 14.10.
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ of the container registry for both GitLab.com and for self-managed users.
|
|||
## Proposal
|
||||
|
||||
There are two main components that must be further developed in order for
|
||||
self-managed admins to move to the registry database: the deployment environment and
|
||||
self-managed admins to move to the registry database: the deployment environment and
|
||||
the registry migration tooling.
|
||||
|
||||
For the deployment environments need to document what the user needs to do to set up their
|
||||
|
|
@ -108,7 +108,7 @@ methods.
|
|||
Given that we're not mutating data via object storage as part of the import
|
||||
process, we should not need to double-check these drivers or try to predict
|
||||
potential errors. Relying on user feedback during the beta to direct any efforts
|
||||
we should be making here could prevent us from scheduling unnecessary work.
|
||||
we should be making here could prevent us from scheduling unnecessary work.
|
||||
|
||||
#### Arguments in Favor of Structuring Support by Driver
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ the surrounding process will enable non-expert users to import their registries
|
|||
with both minimal risk and with minimal support from GitLab team members.
|
||||
Therefore, the most important work remaining is crafting the UX of this tooling
|
||||
such that those goals are met. This
|
||||
[epic](https://gitlab.com/groups/gitlab-org/-/epics/8602) captures many of the
|
||||
[epic](https://gitlab.com/groups/gitlab-org/-/epics/8602) captures many of the
|
||||
proposed improvements.
|
||||
|
||||
#### Design
|
||||
|
|
@ -186,7 +186,7 @@ migration, importing tags requires that the registry be offline or in
|
|||
read-only mode. This step does the minimum possible work to achieve fast and
|
||||
efficient tag imports and will always be the fastest of the three steps, reducing
|
||||
the downtime component to a fraction of the total import time. The user can then
|
||||
bring up the registry configured to use the metadata database. After that, the
|
||||
bring up the registry configured to use the metadata database. After that, the
|
||||
user is free to run the third step during normal registry operations. This step
|
||||
makes any dangling blobs in common storage visible to the database and therefore
|
||||
the online garbage collection process.
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ The new UI will be built using the Pajamas Design System in accordance with GitL
|
|||
- provision API
|
||||
- remove existing iframe provisioning
|
||||
- UI for trace detail
|
||||
- UI for filtering/searching traces
|
||||
- UI for filtering/searching traces
|
||||
- basic e2e test for provision, send data, query in UI
|
||||
- metrics, dashboards, alerts
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ job1:
|
|||
The order of caches extraction is:
|
||||
|
||||
1. Retrieval attempt for `cache:key`
|
||||
1. Retrieval attemps for each entry in order in `fallback_keys`
|
||||
1. Retrieval attempts for each entry in order in `fallback_keys`
|
||||
1. Retrieval attempt for the global fallback key in `CACHE_FALLBACK_KEY`
|
||||
|
||||
The cache extraction process stops after the first successful cache is retrieved.
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ Configure a dashboard to use it for a given environment.
|
|||
You can configure dashboard for an environment that already exists, or
|
||||
add one when you create an environment.
|
||||
|
||||
Prerequisite:
|
||||
Prerequisites:
|
||||
|
||||
- The agent for Kubernetes must be shared with the environment's project, or its parent group, using the [`user_access`](../../user/clusters/agent/user_access.md) keyword.
|
||||
- Self-managed only. KAS is running on the GitLab subdomain. For example, `kas.example.com` and `example.com`.
|
||||
|
||||
### The environment already exists
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ Reviewer roulette is an internal tool for use on GitLab.com, and not available f
|
|||
The [Danger bot](dangerbot.md) randomly picks a reviewer and a maintainer for
|
||||
each area of the codebase that your merge request seems to touch. It makes
|
||||
**recommendations** for developer reviewers and you should override it if you think someone else is a better
|
||||
fit.
|
||||
fit.
|
||||
|
||||
We only do UX reviews for MRs from teams that include a Product Designer. User-facing changes from these teams are required to have a UX review, even if it's behind a feature flag. Default to the recommended UX reviewer suggested.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
## Database Reviews
|
||||
|
||||
- During the design phase of the feature you're working on, be mindful if you are adding any database-related changes. If you're adding or modifying a query, start looking at the `explain` plan early to avoid surprises late in the review phase.
|
||||
- During the design phase of the feature you're working on, be mindful if you are adding any database-related changes. If you're adding or modifying a query, start looking at the `explain` plan early to avoid surprises late in the review phase.
|
||||
- If, at any time, you need help optimizing a query or understanding an `explain` plan, ask for assistance in `#database`.
|
||||
- If you're creating a database MR for review, check out our [Database review guidelines](../database_review.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ is implemented in GitLab Rails and Sidekiq.
|
|||
|
||||
## Components
|
||||
|
||||
F few Ruby classes are involved in the load balancing process. All of them are
|
||||
A few Ruby classes are involved in the load balancing process. All of them are
|
||||
in the namespace `Gitlab::Database::LoadBalancing`:
|
||||
|
||||
1. `Host`
|
||||
|
|
|
|||
|
|
@ -45,6 +45,20 @@ Using Gems can provide several benefits for code maintenance:
|
|||
Since the gem is packaged, not changed too often, it also allows us to run those tests less frequently improving
|
||||
CI testing time.
|
||||
|
||||
## Gem naming
|
||||
|
||||
Gems can fall under three different case:
|
||||
|
||||
- `unique_gem`: Don't include `gitlab` in the gem name if the gem doesn't include anything specific to GitLab
|
||||
- `existing_gem-gitlab`: When you fork and modify/extend a publicly available gem, add the `-gitlab` suffix, according to [Rubygems' convention](https://guides.rubygems.org/name-your-gem/)
|
||||
- `gitlab-unique_gem`: Include a `gitlab-` prefix to gems that are only useful in the context of GitLab projects.
|
||||
|
||||
Examples of existing gems:
|
||||
|
||||
- `y-rb`: Ruby bindings for yrs. Yrs "wires" is a Rust port of the Yjs framework.
|
||||
- `activerecord-gitlab`: Adds GitLab-specific patches to the `activerecord` public gem.
|
||||
- `gitlab-rspec` and `gitlab-utils`: GitLab-specific set of classes to help in a particular context, or re-use code.
|
||||
|
||||
## In the same repo
|
||||
|
||||
**Our GitLab Gems should be always put in `gems/` of GitLab monorepo.**
|
||||
|
|
@ -57,23 +71,24 @@ They should not be published to RubyGems.
|
|||
|
||||
### Create and use a new Gem
|
||||
|
||||
You can see example adding new Gem: [!121676](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121676).
|
||||
You can see example adding a new gem: [!121676](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121676).
|
||||
|
||||
1. Create a new Ruby Gem in `gems/gitlab-<name-of-gem>` with `bundle gem gems/gitlab-<name-of-gem> --no-exe --no-coc --no-ext --no-mit`.
|
||||
1. Remove the `.git` folder in `gems/gitlab-<name-of-gem>` with `rm -rf gems/gitlab-<name-of-gem>/.git`.
|
||||
1. Edit `gitlab-<name-of-gem>/README.md` to provide a simple description of the Gem.
|
||||
1. Edit `gitlab-<name-of-gem>/gitlab-<name-of-gem>.gemspec` and fill the details about the Gem as in the following example:
|
||||
1. Pick a good name for the gem, by following the [Gem naming](#gem-naming) convention.
|
||||
1. Create the new gem in `gems/<name-of-gem>` with `bundle gem gems/<name-of-gem> --no-exe --no-coc --no-ext --no-mit`.
|
||||
1. Remove the `.git` folder in `gems/<name-of-gem>` with `rm -rf gems/<name-of-gem>/.git`.
|
||||
1. Edit `gems/<name-of-gem>/README.md` to provide a simple description of the Gem.
|
||||
1. Edit `gems/<name-of-gem>/<name-of-gem>.gemspec` and fill the details about the Gem as in the following example:
|
||||
|
||||
```ruby
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "gitlab-<name-of-gem>"
|
||||
spec.name = "<name-of-gem>"
|
||||
spec.version = Gitlab::NameOfGem::VERSION
|
||||
spec.authors = ["group::tenant-scale"]
|
||||
spec.email = ["engineering@gitlab.com"]
|
||||
|
||||
spec.summary = "GitLab's RSpec extensions"
|
||||
spec.description = "A set of useful helpers to configure RSpec with various stubs and CI configs."
|
||||
spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-<name-of-gem>"
|
||||
spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/<name-of-gem>"
|
||||
spec.required_ruby_version = ">= 2.7"
|
||||
end
|
||||
```
|
||||
|
|
@ -174,7 +189,7 @@ usage, adding consistency checks and various helpers to track owners of feature
|
|||
not really part of GitLab business logic and could be used to better track our implementation
|
||||
of Flipper and possibly much easier change it to dogfood [GitLab Feature Flags](../operations/feature_flags.md).
|
||||
|
||||
The `gitlab-active_record` is a gem adding GitLab specific Active Record patches.
|
||||
The `activerecord-gitlab` is a gem adding GitLab specific Active Record patches.
|
||||
It is very well desired for such to be managed separately to isolate complexity.
|
||||
|
||||
### Other potential use cases
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Feature flags help reduce risk, allowing you to do controlled testing, and separ
|
|||
delivery from customer launch.
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an example of feature flags in action, see [GitLab for deploys, feature flags, and error tracking](https://www.youtube.com/watch?v=5tw2p6lwXxo).
|
||||
For an example of feature flags in action, see [Feature Flags configuration, instrumentation and use](https://www.youtube.com/watch?v=ViA6suScxkE).
|
||||
|
||||
NOTE:
|
||||
To contribute to the development of the GitLab product, view
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ This table shows available scopes per token. Scopes can be limited further on to
|
|||
1. You can also store token using a [Git credential storage](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
1. Do not:
|
||||
- Store tokens in plain text in your projects.
|
||||
- Include tokens when pasting code, console commands, or log outputs into an issue, MR description, or comment.
|
||||
- Include tokens when pasting code, console commands, or log outputs into an issue, MR description, or comment.
|
||||
Consider an approach such as [using external secrets in CI](../ci/secrets/index.md).
|
||||
1. Do not log credentials in the console logs or artifacts. Consider [protecting](../ci/variables/index.md#protect-a-cicd-variable) and
|
||||
[masking](../ci/variables/index.md#mask-a-cicd-variable) your credentials.
|
||||
|
|
@ -194,5 +194,5 @@ This table shows available scopes per token. Scopes can be limited further on to
|
|||
- Personal, project, and group access tokens.
|
||||
- Feed tokens.
|
||||
- Trigger tokens.
|
||||
- Runner registration tokens.
|
||||
- Runner registration tokens.
|
||||
- Any other sensitive secrets etc.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Write code more efficiently by using generative AI to suggest code while you're
|
|||
Code Suggestions are available:
|
||||
|
||||
- To users of GitLab SaaS (by default) and self-managed GitLab (when requested).
|
||||
- In VS Code and Microsoft Visual Studio when you have the corresponding GitLab extension installed.
|
||||
- In VS Code and Microsoft Visual Studio when you have the corresponding GitLab extension installed.
|
||||
- In the GitLab WebIDE
|
||||
|
||||
<div class="video-fallback">
|
||||
|
|
@ -54,7 +54,7 @@ The best results from Code Suggestions are expected [for languages the Google Ve
|
|||
- Python
|
||||
- TypeScript
|
||||
|
||||
Supported [code infrastructure interfaces](https://cloud.google.com/vertex-ai/docs/generative-ai/code/code-models-overview#supported_code_infrastructure_interfaces) include:
|
||||
Supported [code infrastructure interfaces](https://cloud.google.com/vertex-ai/docs/generative-ai/code/code-models-overview#supported_code_infrastructure_interfaces) include:
|
||||
|
||||
- Google Cloud CLI
|
||||
- Kubernetes Resource Model (KRM)
|
||||
|
|
@ -175,7 +175,7 @@ Suggestions are best when writing new code. Editing existing functions or 'fill
|
|||
GitLab is making improvements to the Code Suggestions to improve the quality. AI is non-deterministic, so you may not get the same suggestion every time with the same input.
|
||||
|
||||
This feature is currently in [Beta](../../../policy/experiment-beta-support.md#beta).
|
||||
Code Suggestions depends on both Google Vertex AI Codey APIs and the GitLab Code Suggestions service. We expect a
|
||||
Code Suggestions depends on both Google Vertex AI Codey APIs and the GitLab Code Suggestions service. We expect a
|
||||
high demand for this Beta feature, which may cause degraded performance or unexpected downtime
|
||||
of the feature. We have built this feature to gracefully degrade and have controls in place to allow us to
|
||||
mitigate abuse or misuse. GitLab may disable this feature for any or all customers at any time at our discretion.
|
||||
|
|
@ -244,9 +244,9 @@ We strongly encourage all beta users to leverage GitLab native
|
|||
[Code Quality Scanning](../../../ci/testing/code_quality.md) and
|
||||
[Security Scanning](../../application_security/index.md) capabilities.
|
||||
|
||||
GitLab currently does not retrain Google Vertex AI Codey APIs. GitLab makes no claims
|
||||
to the accuracy or quality of code suggestions generated by Google Vertex AI Codey API.
|
||||
Read more about [Google Vertex AI foundation model capabilities](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models).
|
||||
GitLab currently does not retrain Google Vertex AI Codey APIs. GitLab makes no claims
|
||||
to the accuracy or quality of code suggestions generated by Google Vertex AI Codey API.
|
||||
Read more about [Google Vertex AI foundation model capabilities](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models).
|
||||
|
||||
## Known limitations
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ module API
|
|||
expose :full_name, :full_path
|
||||
expose :created_at
|
||||
expose :parent_id
|
||||
expose :shared_runners_setting
|
||||
|
||||
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,14 @@ module API
|
|||
end
|
||||
|
||||
expose :moved_to_id
|
||||
expose :service_desk_reply_to
|
||||
expose :service_desk_reply_to do |issue|
|
||||
issue.present(
|
||||
current_user: options[:current_user],
|
||||
# We need to pass it explicitly to account for the case where `issue`
|
||||
# is a `WorkItem` which doesn't have a presenter yet.
|
||||
presenter_class: IssuePresenter
|
||||
).service_desk_reply_to
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -251,6 +251,28 @@ module API
|
|||
present_projects load_projects
|
||||
end
|
||||
|
||||
desc 'Get projects that a user has contributed to' do
|
||||
success code: 200, model: Entities::BasicProjectDetails
|
||||
failure [{ code: 404, message: '404 User Not Found' }]
|
||||
tags %w[projects]
|
||||
is_array true
|
||||
end
|
||||
params do
|
||||
requires :user_id, type: String, desc: 'The ID or username of the user'
|
||||
use :sort_params
|
||||
use :pagination
|
||||
|
||||
optional :simple, type: Boolean, default: false,
|
||||
desc: 'Return only the ID, URL, name, and path of each project'
|
||||
end
|
||||
get ":user_id/contributed_projects", feature_category: :groups_and_projects, urgency: :low do
|
||||
user = find_user(params[:user_id])
|
||||
not_found!('User') unless user
|
||||
|
||||
contributed_projects = ContributedProjectsFinder.new(user).execute(current_user).joined(user)
|
||||
present_projects contributed_projects
|
||||
end
|
||||
|
||||
desc 'Get projects starred by a user' do
|
||||
success code: 200, model: Entities::BasicProjectDetails
|
||||
failure [{ code: 404, message: '404 User Not Found' }]
|
||||
|
|
|
|||
|
|
@ -29,15 +29,19 @@ module Banzai
|
|||
@references_per_parent[parent_type] ||= begin
|
||||
refs = Hash.new { |hash, key| hash[key] = Set.new }
|
||||
|
||||
prepare_doc_for_scan.to_enum(:scan, regex).each do
|
||||
parent_path = if parent_type == :project
|
||||
full_project_path($~[:namespace], $~[:project])
|
||||
else
|
||||
full_group_path($~[:group])
|
||||
end
|
||||
[filter.object_class.link_reference_pattern, filter.object_class.reference_pattern].each do |pattern|
|
||||
next unless pattern
|
||||
|
||||
ident = filter.identifier($~)
|
||||
refs[parent_path] << ident if ident
|
||||
prepare_doc_for_scan.to_enum(:scan, pattern).each do
|
||||
parent_path = if parent_type == :project
|
||||
full_project_path($~[:namespace], $~[:project])
|
||||
else
|
||||
full_group_path($~[:group])
|
||||
end
|
||||
|
||||
ident = filter.identifier($~)
|
||||
refs[parent_path] << ident if ident
|
||||
end
|
||||
end
|
||||
|
||||
refs
|
||||
|
|
@ -171,15 +175,6 @@ module Banzai
|
|||
|
||||
delegate :project, :group, :parent, :parent_type, to: :filter
|
||||
|
||||
def regex
|
||||
strong_memoize(:regex) do
|
||||
[
|
||||
filter.object_class.link_reference_pattern,
|
||||
filter.object_class.reference_pattern
|
||||
].compact.reduce { |a, b| Regexp.union(a, b) }
|
||||
end
|
||||
end
|
||||
|
||||
def refs_cache
|
||||
Gitlab::SafeRequestStore["banzai_#{parent_type}_refs".to_sym] ||= {}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ module Gitlab
|
|||
include Sidekiq::ServerMiddleware
|
||||
|
||||
def call(worker, job, queue)
|
||||
logger.info "arguments: #{Gitlab::Json.dump(job['args'])}"
|
||||
loggable_args = Gitlab::ErrorTracking::Processor::SidekiqProcessor.loggable_arguments(job['args'], job['class'])
|
||||
logger.info "arguments: #{Gitlab::Json.dump(loggable_args)}"
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1962,6 +1962,9 @@ msgstr ""
|
|||
msgid "AI|May provide inappropriate responses not representative of GitLab's views. Do not input personal data."
|
||||
msgstr ""
|
||||
|
||||
msgid "AI|New chat"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI|Populate issue description"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2442,9 +2445,6 @@ msgstr ""
|
|||
msgid "AccessTokens|Created"
|
||||
msgstr ""
|
||||
|
||||
msgid "AccessTokens|Enter the name of your application, and we'll return a unique %{type}."
|
||||
msgstr ""
|
||||
|
||||
msgid "AccessTokens|Feed token"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31273,6 +31273,9 @@ msgstr ""
|
|||
msgid "Notify|Committed by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notify|Could not find the following %{column} values in %{project}%{parent_groups_clause}: %{error_lines}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notify|Don't want to receive updates from GitLab administrators?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31390,6 +31393,9 @@ msgstr ""
|
|||
msgid "Notify|Please check that your service provider supports email subaddressing and that you have set up email forwarding correctly."
|
||||
msgstr ""
|
||||
|
||||
msgid "Notify|Please fix the errors above and try the CSV import again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Notify|Please fix the lines with errors and try the CSV import again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
diff --git a/node_modules/@rails/ujs/lib/assets/compiled/rails-ujs.js b/node_modules/@rails/ujs/lib/assets/compiled/rails-ujs.js
|
||||
index d428163..010eaa5 100644
|
||||
--- a/node_modules/@rails/ujs/lib/assets/compiled/rails-ujs.js
|
||||
+++ b/node_modules/@rails/ujs/lib/assets/compiled/rails-ujs.js
|
||||
@@ -281,11 +281,6 @@ Released under the MIT license
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {}
|
||||
- } else if (type.match(/\b(?:java|ecma)script\b/)) {
|
||||
- script = document.createElement('script');
|
||||
- script.setAttribute('nonce', cspNonce());
|
||||
- script.text = response;
|
||||
- document.head.appendChild(script).parentNode.removeChild(script);
|
||||
} else if (type.match(/\b(xml|html|svg)\b/)) {
|
||||
parser = new DOMParser();
|
||||
type = type.replace(/;.+/, '');
|
||||
|
|
@ -55,12 +55,13 @@ RSpec.describe Admin::HooksController do
|
|||
hook.update!(url_variables: { 'foo' => 'bar', 'baz' => 'woo' })
|
||||
|
||||
hook_params = {
|
||||
url: 'http://example.com/{baz}?token={token}',
|
||||
url: 'http://example.com/{bar}?token={token}',
|
||||
enable_ssl_verification: false,
|
||||
url_variables: [
|
||||
{ key: 'token', value: 'some secret value' },
|
||||
{ key: 'baz', value: 'qux' },
|
||||
{ key: 'foo', value: nil }
|
||||
{ key: 'baz', value: nil },
|
||||
{ key: 'foo', value: nil },
|
||||
{ key: 'bar', value: 'qux' }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ RSpec.describe Admin::HooksController do
|
|||
expect(flash[:notice]).to include('was updated')
|
||||
expect(hook).to have_attributes(hook_params.except(:url_variables))
|
||||
expect(hook).to have_attributes(
|
||||
url_variables: { 'token' => 'some secret value', 'baz' => 'qux' }
|
||||
url_variables: { 'token' => 'some secret value', 'bar' => 'qux' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@ RSpec.describe Import::GithubController, feature_category: :importers do
|
|||
)
|
||||
end
|
||||
|
||||
let(:user) { project.owner }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when import is not finished' do
|
||||
it 'return bad_request' do
|
||||
get :failures, params: { project_id: project.id }
|
||||
|
|
@ -434,6 +440,16 @@ RSpec.describe Import::GithubController, feature_category: :importers do
|
|||
expect(json_response.first['title']).to eq(issue_title)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed user is not the owner' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'renders 404' do
|
||||
get :failures, params: { project_id: project.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST cancel" do
|
||||
|
|
@ -444,6 +460,12 @@ RSpec.describe Import::GithubController, feature_category: :importers do
|
|||
)
|
||||
end
|
||||
|
||||
let(:user) { project.owner }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when project import was canceled' do
|
||||
before do
|
||||
allow(Import::Github::CancelProjectImportService)
|
||||
|
|
@ -476,6 +498,16 @@ RSpec.describe Import::GithubController, feature_category: :importers do
|
|||
expect(json_response['errors']).to eq('The import cannot be canceled because it is finished')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed user is not the owner' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'renders 404' do
|
||||
post :cancel, params: { project_id: project.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST cancel_all' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::CompareController do
|
||||
RSpec.describe Projects::CompareController, feature_category: :source_code_management do
|
||||
include ProjectForksHelper
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
|
@ -211,6 +211,36 @@ RSpec.describe Projects::CompareController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the target project is the default source but hidden to the user' do
|
||||
let(:project) { create(:project, :repository, :private) }
|
||||
let(:from_ref) { 'improve%2Fmore-awesome' }
|
||||
let(:to_ref) { 'feature' }
|
||||
let(:whitespace) { nil }
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
from: from_ref,
|
||||
to: to_ref,
|
||||
w: whitespace,
|
||||
page: page,
|
||||
straight: straight
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not show the diff' do
|
||||
allow(controller).to receive(:source_project).and_return(project)
|
||||
expect(project).to receive(:default_merge_request_target).and_return(private_fork)
|
||||
|
||||
show_request
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:diffs)).to be_empty
|
||||
expect(assigns(:commits)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the source ref does not exist' do
|
||||
let(:from_project_id) { nil }
|
||||
let(:from_ref) { 'non-existent-source-ref' }
|
||||
|
|
|
|||
|
|
@ -504,7 +504,9 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
|
|||
end
|
||||
|
||||
context 'when user has an unconfirmed email', :js do
|
||||
let(:unconfirmed_user) { create(:user, :unconfirmed) }
|
||||
# Email address contains HTML to ensure email address is displayed in an HTML safe way.
|
||||
let_it_be(:unconfirmed_email) { "#{generate(:email)}<h2>testing<img/src=http://localhost:8000/test.png>" }
|
||||
let_it_be(:unconfirmed_user) { create(:user, :unconfirmed, unconfirmed_email: unconfirmed_email) }
|
||||
|
||||
where(:path_helper) do
|
||||
[
|
||||
|
|
@ -524,7 +526,9 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
|
|||
|
||||
within_modal do
|
||||
expect(page).to have_content("Confirm user #{unconfirmed_user.name}?")
|
||||
expect(page).to have_content('This user has an unconfirmed email address. You may force a confirmation.')
|
||||
expect(page).to have_content(
|
||||
"This user has an unconfirmed email address (#{unconfirmed_email}). You may force a confirmation."
|
||||
)
|
||||
|
||||
click_button 'Confirm user'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
title,description,milestone
|
||||
"Issue with missing milestone","",15.10,
|
||||
"Issue without milestone","",,
|
||||
"Issue with milestone","",10.1,
|
||||
"Issue with duplicate milestone","",15.10,
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
|
@ -44,7 +44,7 @@ describe('TokensApp', () => {
|
|||
const container = extendedWrapper(wrapper.findByTestId(testId));
|
||||
|
||||
expect(container.findByText(expectedLabel, { selector: 'h4' }).exists()).toBe(true);
|
||||
expect(container.findByText(expectedDescription).exists()).toBe(true);
|
||||
expect(container.findByText(expectedDescription, { exact: false }).exists()).toBe(true);
|
||||
expect(container.findByText(expectedInputDescription, { exact: false }).exists()).toBe(true);
|
||||
expect(container.findByText('reset this token').attributes()).toMatchObject({
|
||||
'data-confirm': expectedResetConfirmMessage,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ describe('RemoveAvatar', () => {
|
|||
let formSubmitSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
formSubmitSpy = jest.spyOn(wrapper.vm.$refs.deleteForm, 'submit');
|
||||
formSubmitSpy = jest.spyOn(findForm().element, 'submit');
|
||||
findModal().vm.$emit('primary');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -162,14 +162,14 @@ describe('gl_emoji', () => {
|
|||
]);
|
||||
|
||||
window.gon = { features: { customEmoji: true } };
|
||||
document.body.dataset.group = 'test-group';
|
||||
document.body.dataset.groupFullPath = 'test-group';
|
||||
|
||||
await initEmojiMock(emojiData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.gon = {};
|
||||
delete document.body.dataset.group;
|
||||
delete document.body.dataset.groupFullPath;
|
||||
});
|
||||
|
||||
it('renders custom emoji', async () => {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ function createMockEmojiClient() {
|
|||
]);
|
||||
|
||||
window.gon = { features: { customEmoji: true } };
|
||||
document.body.dataset.group = 'test-group';
|
||||
document.body.dataset.groupFullPath = 'test-group';
|
||||
}
|
||||
|
||||
describe('emoji', () => {
|
||||
|
|
@ -82,7 +82,7 @@ describe('emoji', () => {
|
|||
|
||||
afterEach(() => {
|
||||
window.gon = {};
|
||||
delete document.body.dataset.group;
|
||||
delete document.body.dataset.groupFullPath;
|
||||
clearEmojiMock();
|
||||
});
|
||||
|
||||
|
|
@ -758,7 +758,7 @@ describe('emoji', () => {
|
|||
|
||||
describe('when not in a group', () => {
|
||||
beforeEach(() => {
|
||||
delete document.body.dataset.group;
|
||||
delete document.body.dataset.groupFullPath;
|
||||
});
|
||||
|
||||
it('returns empty object', async () => {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ describe('UserNameGroup component', () => {
|
|||
});
|
||||
|
||||
it('should render status message', () => {
|
||||
expect(findUserStatus().text()).toContain(userMenuMockData.status.message);
|
||||
expect(findUserStatus().html()).toContain(userMenuMockData.status.message_html);
|
||||
});
|
||||
|
||||
it("sets the tooltip's target to the status container", () => {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ export const userMenuMockStatus = {
|
|||
customized: false,
|
||||
emoji: 'art',
|
||||
message: 'Working on user menu in super sidebar',
|
||||
message_html: '<gl-emoji></gl-emoji> Working on user menu in super sidebar',
|
||||
availability: 'busy',
|
||||
clear_after: '2023-02-09 20:06:35 UTC',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
||||
import { nextTick } from 'vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import DiffStatsDropdown, { i18n } from '~/vue_shared/components/diff_stats_dropdown.vue';
|
||||
|
||||
jest.mock('fuzzaldrin-plus', () => ({
|
||||
|
|
@ -38,6 +39,7 @@ const mockFiles = [
|
|||
|
||||
describe('Diff Stats Dropdown', () => {
|
||||
let wrapper;
|
||||
const focusInputMock = jest.fn();
|
||||
|
||||
const createComponent = ({ changed = 0, added = 0, deleted = 0, files = [] } = {}) => {
|
||||
wrapper = shallowMountExtended(DiffStatsDropdown, {
|
||||
|
|
@ -50,6 +52,9 @@ describe('Diff Stats Dropdown', () => {
|
|||
stubs: {
|
||||
GlSprintf,
|
||||
GlDropdown,
|
||||
GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
|
||||
methods: { focusInput: focusInputMock },
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -151,10 +156,8 @@ describe('Diff Stats Dropdown', () => {
|
|||
});
|
||||
|
||||
it('should set the search input focus', () => {
|
||||
wrapper.vm.$refs.search.focusInput = jest.fn();
|
||||
findChanged().vm.$emit('shown');
|
||||
|
||||
expect(wrapper.vm.$refs.search.focusInput).toHaveBeenCalled();
|
||||
expect(focusInputMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -909,6 +909,14 @@ RSpec.describe GitlabSchema.types['Project'] do
|
|||
expect(forks).to contain_exactly(a_hash_including('fullPath' => fork_developer.full_path),
|
||||
a_hash_including('fullPath' => fork_group_developer.full_path))
|
||||
end
|
||||
|
||||
context 'when current user is not set' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not return any forks' do
|
||||
expect(forks.count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -433,7 +433,8 @@ RSpec.describe ApplicationHelper do
|
|||
page: 'application',
|
||||
page_type_id: nil,
|
||||
find_file: nil,
|
||||
group: nil
|
||||
group: nil,
|
||||
group_full_path: nil
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
@ -449,7 +450,8 @@ RSpec.describe ApplicationHelper do
|
|||
page: 'application',
|
||||
page_type_id: nil,
|
||||
find_file: nil,
|
||||
group: group.path
|
||||
group: group.path,
|
||||
group_full_path: group.full_path
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
@ -473,6 +475,7 @@ RSpec.describe ApplicationHelper do
|
|||
page_type_id: nil,
|
||||
find_file: nil,
|
||||
group: nil,
|
||||
group_full_path: nil,
|
||||
project_id: project.id,
|
||||
project: project.path,
|
||||
namespace_id: project.namespace.id
|
||||
|
|
@ -491,6 +494,7 @@ RSpec.describe ApplicationHelper do
|
|||
page_type_id: nil,
|
||||
find_file: nil,
|
||||
group: project.group.name,
|
||||
group_full_path: project.group.full_path,
|
||||
project_id: project.id,
|
||||
project: project.path,
|
||||
namespace_id: project.namespace.id
|
||||
|
|
@ -517,6 +521,7 @@ RSpec.describe ApplicationHelper do
|
|||
page_type_id: issue.id,
|
||||
find_file: nil,
|
||||
group: nil,
|
||||
group_full_path: nil,
|
||||
project_id: issue.project.id,
|
||||
project: issue.project.path,
|
||||
namespace_id: issue.project.namespace.id
|
||||
|
|
|
|||
|
|
@ -106,7 +106,8 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
|
|||
customized: user.status&.customized?,
|
||||
availability: user.status&.availability.to_s,
|
||||
emoji: user.status&.emoji,
|
||||
message: user.status&.message_html&.html_safe,
|
||||
message_html: user.status&.message_html&.html_safe,
|
||||
message: user.status&.message&.html_safe,
|
||||
clear_after: nil
|
||||
},
|
||||
settings: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::API::Entities::Issue, feature_category: :team_planning do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:issue) { build_stubbed(:issue, project: project) }
|
||||
let(:current_user) { build_stubbed(:user) }
|
||||
let(:options) { { current_user: current_user }.merge(option_addons) }
|
||||
let(:option_addons) { {} }
|
||||
let(:entity) { described_class.new(issue, options) }
|
||||
|
||||
subject(:json) { entity.as_json }
|
||||
|
||||
describe '#service_desk_reply_to', feature_category: :service_desk do
|
||||
# Setting to true (default) doesn't play nice with stubs
|
||||
let(:option_addons) { { include_subscribed: false } }
|
||||
let(:issue) { build_stubbed(:issue, project: project, service_desk_reply_to: email) }
|
||||
let(:email) { 'creator@example.com' }
|
||||
let(:role) { :developer }
|
||||
|
||||
subject { json[:service_desk_reply_to] }
|
||||
|
||||
context 'as developer' do
|
||||
before do
|
||||
stub_member_access_level(issue.project, developer: current_user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(email) }
|
||||
end
|
||||
|
||||
context 'as guest' do
|
||||
before do
|
||||
stub_member_access_level(issue.project, guest: current_user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq('cr*****@e*****.c**') }
|
||||
end
|
||||
|
||||
context 'without email' do
|
||||
let(:email) { nil }
|
||||
|
||||
specify { expect(json).to have_key(:service_desk_reply_to) }
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -36,6 +36,15 @@ RSpec.describe Emails::Issues, feature_category: :team_planning do
|
|||
expect(subject).to have_body_text "23, 34, 58"
|
||||
end
|
||||
|
||||
it "shows issuable errors with column" do
|
||||
@results = { success: 0, error_lines: [], parse_error: false,
|
||||
preprocess_errors:
|
||||
{ milestone_errors: { missing: { header: 'Milestone', titles: %w[15.10 15.11] } } } }
|
||||
|
||||
expect(subject).to have_body_text "Could not find the following milestone values in \
|
||||
#{project.full_name}: 15.10, 15.11"
|
||||
end
|
||||
|
||||
context 'with header and footer' do
|
||||
let(:results) { { success: 165, error_lines: [], parse_error: false } }
|
||||
|
||||
|
|
|
|||
|
|
@ -258,6 +258,13 @@ RSpec.describe WebHook, feature_category: :webhooks do
|
|||
expect(hook.url_variables).to eq({})
|
||||
end
|
||||
|
||||
it 'resets url variables if url variables are overwritten' do
|
||||
hook.url_variables = hook.url_variables.merge('abc' => 'baz')
|
||||
|
||||
expect(hook).not_to be_valid
|
||||
expect(hook.url_variables).to eq({})
|
||||
end
|
||||
|
||||
it 'does not reset url variables if both url and url variables are changed' do
|
||||
hook.url = 'http://example.com/{one}/{two}'
|
||||
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
|
||||
|
|
|
|||
|
|
@ -3,10 +3,20 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tracking do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:setting) { create(:project_error_tracking_setting, project: project) }
|
||||
let_it_be(:project_without_setting) { create(:project) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:non_member) { create(:user) }
|
||||
let(:user) { maintainer }
|
||||
|
||||
before_all do
|
||||
project.add_developer(developer)
|
||||
project.add_maintainer(maintainer)
|
||||
project_without_setting.add_developer(developer)
|
||||
project_without_setting.add_maintainer(maintainer)
|
||||
end
|
||||
|
||||
shared_examples 'returns project settings' do
|
||||
it 'returns correct project settings' do
|
||||
|
|
@ -108,10 +118,6 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
end
|
||||
|
||||
context 'when authenticated as maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'with integrated_error_tracking feature enabled' do
|
||||
it_behaves_like 'returns project settings'
|
||||
end
|
||||
|
|
@ -179,10 +185,6 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
context 'without a project setting' do
|
||||
let(:project) { project_without_setting }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns no project settings'
|
||||
end
|
||||
|
||||
|
|
@ -208,14 +210,14 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
end
|
||||
|
||||
context 'when authenticated as developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
let(:user) { developer }
|
||||
|
||||
it_behaves_like 'returns 403'
|
||||
end
|
||||
|
||||
context 'when authenticated as non-member' do
|
||||
let(:user) { non_member }
|
||||
|
||||
it_behaves_like 'returns 404'
|
||||
end
|
||||
|
||||
|
|
@ -232,10 +234,6 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
end
|
||||
|
||||
context 'when authenticated as maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns project settings'
|
||||
|
||||
context 'when integrated_error_tracking feature disabled' do
|
||||
|
|
@ -250,22 +248,18 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
context 'without a project setting' do
|
||||
let(:project) { project_without_setting }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns no project settings'
|
||||
end
|
||||
|
||||
context 'when authenticated as developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
let(:user) { developer }
|
||||
|
||||
it_behaves_like 'returns 403'
|
||||
end
|
||||
|
||||
context 'when authenticated as non-member' do
|
||||
let(:user) { non_member }
|
||||
|
||||
it_behaves_like 'returns 404'
|
||||
end
|
||||
|
||||
|
|
@ -287,14 +281,8 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
|
||||
context 'when authenticated' do
|
||||
context 'as maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context "when integrated" do
|
||||
context "with existing setting" do
|
||||
let(:project) { setting.project }
|
||||
let(:setting) { create(:project_error_tracking_setting, :integrated) }
|
||||
let(:active) { false }
|
||||
|
||||
it "updates a setting" do
|
||||
|
|
@ -302,13 +290,7 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
expect(json_response).to eq(
|
||||
"active" => false,
|
||||
"api_url" => nil,
|
||||
"integrated" => integrated,
|
||||
"project_name" => nil,
|
||||
"sentry_external_url" => nil
|
||||
)
|
||||
expect(json_response).to include("integrated" => true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -366,14 +348,14 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra
|
|||
end
|
||||
|
||||
context "as developer" do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
let(:user) { developer }
|
||||
|
||||
it_behaves_like 'returns 403'
|
||||
end
|
||||
|
||||
context 'as non-member' do
|
||||
let(:user) { non_member }
|
||||
|
||||
it_behaves_like 'returns 404'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -844,6 +844,39 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
expect(shared_with_groups).to contain_exactly(group_link_1.shared_with_group_id, group_link_2.shared_with_group_id)
|
||||
end
|
||||
end
|
||||
|
||||
context "expose shared_runners_setting attribute" do
|
||||
let(:group) { create(:group, shared_runners_enabled: true) }
|
||||
|
||||
before do
|
||||
group.add_owner(user1)
|
||||
end
|
||||
|
||||
it "returns the group with shared_runners_setting as 'enabled'", :aggregate_failures do
|
||||
get api("/groups/#{group.id}", user1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['shared_runners_setting']).to eq("enabled")
|
||||
end
|
||||
|
||||
it "returns the group with shared_runners_setting as 'disabled_and_unoverridable'", :aggregate_failures do
|
||||
group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false)
|
||||
|
||||
get api("/groups/#{group.id}", user1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['shared_runners_setting']).to eq("disabled_and_unoverridable")
|
||||
end
|
||||
|
||||
it "returns the group with shared_runners_setting as 'disabled_and_overridable'", :aggregate_failures do
|
||||
group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true)
|
||||
|
||||
get api("/groups/#{group.id}", user1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['shared_runners_setting']).to eq("disabled_and_overridable")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1835,6 +1835,72 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /users/:user_id/contributed_projects/' do
|
||||
let(:path) { "/users/#{user3.id}/contributed_projects/" }
|
||||
|
||||
let_it_be(:project1) { create(:project, :public, path: 'my-project') }
|
||||
let_it_be(:project2) { create(:project, :public) }
|
||||
let_it_be(:project3) { create(:project, :public) }
|
||||
let_it_be(:private_project) { create(:project, :private) }
|
||||
|
||||
before do
|
||||
private_project.add_maintainer(user3)
|
||||
|
||||
create(:push_event, project: project1, author: user3)
|
||||
create(:push_event, project: project2, author: user3)
|
||||
create(:push_event, project: private_project, author: user3)
|
||||
end
|
||||
|
||||
it 'returns error when user not found' do
|
||||
get api("/users/#{non_existing_record_id}/contributed_projects/", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(json_response['message']).to eq('404 User Not Found')
|
||||
end
|
||||
|
||||
context 'with a public profile' do
|
||||
it 'returns projects filtered by user' do
|
||||
get api(path, user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] })
|
||||
.to contain_exactly(project1.id, project2.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a private profile' do
|
||||
before do
|
||||
user3.update!(private_profile: true)
|
||||
user3.reload
|
||||
end
|
||||
|
||||
context 'user does not have access to view the private profile' do
|
||||
it 'returns no projects' do
|
||||
get api(path, user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has access to view the private profile as an admin' do
|
||||
it 'returns projects filtered by user' do
|
||||
get api(path, admin, admin_mode: true)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] })
|
||||
.to contain_exactly(project1.id, project2.id, private_project.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/user/:id' do
|
||||
let(:path) { "/projects/user/#{user.id}" }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ImportCsv::PreprocessMilestonesService, feature_category: :importers do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:provided_titles) { %w[15.10 10.1] }
|
||||
|
||||
let(:service) { described_class.new(user, project, provided_titles) }
|
||||
|
||||
subject { service.execute }
|
||||
|
||||
describe '#execute' do
|
||||
let(:project_milestones) { ::MilestonesFinder.new({ project_ids: [project.id] }).execute }
|
||||
|
||||
shared_examples 'csv import' do |is_success:, milestone_errors:|
|
||||
it 'does not create milestones' do
|
||||
expect { subject }.not_to change { project_milestones.count }
|
||||
end
|
||||
|
||||
it 'reports any missing milestones' do
|
||||
result = subject
|
||||
|
||||
if is_success
|
||||
expect(result).to be_success
|
||||
else
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result.payload).to match(milestone_errors)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with csv that has missing or unavailable milestones' do
|
||||
it_behaves_like 'csv import',
|
||||
{ is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: %w[15.10 10.1] } } }
|
||||
end
|
||||
|
||||
context 'with csv that includes project milestones' do
|
||||
let!(:project_milestone) { create(:milestone, project: project, title: '15.10') }
|
||||
|
||||
it_behaves_like 'csv import',
|
||||
{ is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: ["10.1"] } } }
|
||||
end
|
||||
|
||||
context 'with csv that includes milestones column' do
|
||||
let!(:project_milestone) { create(:milestone, project: project, title: '15.10') }
|
||||
|
||||
context 'when milestones exist in the importing projects group' do
|
||||
let!(:group_milestone) { create(:milestone, group: group, title: '10.1') }
|
||||
|
||||
it_behaves_like 'csv import', { is_success: true, milestone_errors: nil }
|
||||
end
|
||||
|
||||
context 'when milestones exist in a subgroup of the importing projects group' do
|
||||
let_it_be(:subgroup) { create(:group, parent: group) }
|
||||
let!(:group_milestone) { create(:milestone, group: subgroup, title: '10.1') }
|
||||
|
||||
it_behaves_like 'csv import',
|
||||
{ is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: ["10.1"] } } }
|
||||
end
|
||||
|
||||
context 'when milestones exist in a different project from the importing project' do
|
||||
let_it_be(:second_project) { create(:project, group: group) }
|
||||
let!(:second_project_milestone) { create(:milestone, project: second_project, title: '10.1') }
|
||||
|
||||
it_behaves_like 'csv import',
|
||||
{ is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: ["10.1"] } } }
|
||||
end
|
||||
|
||||
context 'when duplicate milestones exist in the projects group and parent group' do
|
||||
let_it_be(:sub_group) { create(:group, parent: group) }
|
||||
let_it_be(:project) { create(:project, group: sub_group) }
|
||||
let!(:ancestor_group_milestone) { create(:milestone, group: group, title: '15.10') }
|
||||
let!(:ancestor_group_milestone_two) { create(:milestone, group: group, title: '10.1') }
|
||||
let!(:group_milestone) { create(:milestone, group: sub_group, title: '10.1') }
|
||||
|
||||
it_behaves_like 'csv import', { is_success: true, milestone_errors: nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Issuable::ImportCsv::BaseService, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:csv_io) { double }
|
||||
|
||||
let(:importer_klass) do
|
||||
Class.new(described_class) do
|
||||
def email_results_to_user
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
uploader = FileUploader.new(project)
|
||||
uploader.store!(file)
|
||||
|
||||
importer_klass.new(user, project, uploader)
|
||||
end
|
||||
|
||||
subject { service.execute }
|
||||
|
||||
describe '#preprocess_milestones' do
|
||||
let(:utility_class) { ImportCsv::PreprocessMilestonesService }
|
||||
let(:file) { fixture_file_upload('spec/fixtures/csv_missing_milestones.csv') }
|
||||
let(:mocked_object) { double }
|
||||
|
||||
before do
|
||||
allow(service).to receive(:create_object).and_return(mocked_object)
|
||||
allow(mocked_object).to receive(:persisted?).and_return(true)
|
||||
end
|
||||
|
||||
context 'with csv that has milestone heading' do
|
||||
before do
|
||||
allow(utility_class).to receive(:new).and_return(utility_class)
|
||||
allow(utility_class).to receive(:execute).and_return(ServiceResponse.success)
|
||||
end
|
||||
|
||||
it 'calls PreprocessMilestonesService' do
|
||||
subject
|
||||
expect(utility_class).to have_received(:new)
|
||||
end
|
||||
|
||||
it 'calls PreprocessMilestonesService with unique milestone titles' do
|
||||
subject
|
||||
expect(utility_class).to have_received(:new).with(user, project, %w[15.10 10.1])
|
||||
expect(utility_class).to have_received(:execute)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with csv that does not have milestone heading' do
|
||||
let(:file) { fixture_file_upload('spec/fixtures/work_items_valid_types.csv') }
|
||||
|
||||
before do
|
||||
allow(utility_class).to receive(:new).and_return(utility_class)
|
||||
end
|
||||
|
||||
it 'does not call PreprocessMilestonesService' do
|
||||
subject
|
||||
expect(utility_class).not_to have_received(:new)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when one or more milestones do not exist' do
|
||||
it 'returns the expected error in results payload' do
|
||||
results = subject
|
||||
|
||||
expect(results[:success]).to eq(0)
|
||||
expect(results[:preprocess_errors]).to match({
|
||||
milestone_errors: { missing: { header: 'Milestone', titles: %w[15.10 10.1] } }
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all milestones exist' do
|
||||
let!(:group_milestone) { create(:milestone, group: group, title: '10.1') }
|
||||
let!(:project_milestone) { create(:milestone, project: project, title: '15.10') }
|
||||
|
||||
it 'returns a successful response' do
|
||||
results = subject
|
||||
|
||||
expect(results[:preprocess_errors]).to be_empty
|
||||
expect(results[:success]).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -30,6 +30,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
|
|||
|
||||
context 'with a file generated by Gitlab CSV export' do
|
||||
let(:file) { fixture_file_upload('spec/fixtures/csv_gitlab_export.csv') }
|
||||
let!(:test_milestone) { create(:milestone, project: project, title: 'v1.0') }
|
||||
|
||||
it 'imports the CSV without errors' do
|
||||
expect(subject[:success]).to eq(4)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ RSpec.describe 'notify/import_issues_csv_email.html.haml' do
|
|||
let(:correct_results) { { success: 3, parse_error: false } }
|
||||
let(:errored_results) { { success: 3, error_lines: [5, 6, 7], parse_error: false } }
|
||||
let(:parse_error_results) { { success: 0, parse_error: true } }
|
||||
let(:milestone_error_results) do
|
||||
{ success: 0,
|
||||
preprocess_errors: { milestone_errors: { missing: { header: 'Milestone', titles: %w[15.10 15.11] } } } }
|
||||
end
|
||||
|
||||
before do
|
||||
assign(:user, user)
|
||||
|
|
@ -58,4 +62,29 @@ a delimited text file that uses a comma to separate values.")
|
|||
Please make sure it has the correct format: a delimited text file that uses a comma to separate values.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when preprocess errors reported while importing' do
|
||||
before do
|
||||
assign(:results, milestone_error_results)
|
||||
end
|
||||
|
||||
it 'renders with project name error' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_content("Could not find the following milestone values in \
|
||||
#{project.full_name}: 15.10, 15.11")
|
||||
end
|
||||
|
||||
context 'with a project in a group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
||||
it 'renders with group clause error' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_content("Could not find the following milestone values in #{project.full_name} \
|
||||
or its parent groups: 15.10, 15.11")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue