Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6c50700233
commit
800c3eceda
|
|
@ -2339,12 +2339,14 @@ Gitlab/BoundedContexts:
|
|||
- 'ee/app/graphql/resolvers/security_orchestration/policy_violations_resolver.rb'
|
||||
- 'ee/app/graphql/resolvers/security_orchestration/scan_execution_policy_resolver.rb'
|
||||
- 'ee/app/graphql/resolvers/security_orchestration/scan_result_policy_resolver.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/scan_execution_policy_attributes_type.rb'
|
||||
- 'ee/app/graphql/resolvers/security_orchestration/security_policy_resolver.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/approval_policy_attributes_type.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/scan_execution_policy_attributes_type.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/policy_attributes_union.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/pipeline_execution_scheduled_policy_attributes_type.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/pipeline_execution_policy_attributes_type.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/pipeline_execution_policy_scheduled_fields_type.rb'
|
||||
- 'ee/app/graphql/types/security/vulnerability_management_policy_attributes_type.rb'
|
||||
- 'ee/app/graphql/types/security_orchestration/security_policy_type.rb'
|
||||
- 'ee/app/graphql/resolvers/security_orchestration/security_policy_project_suggestions_resolver.rb'
|
||||
- 'ee/app/graphql/resolvers/security_report/finding_reports_comparer_resolver.rb'
|
||||
- 'ee/app/graphql/resolvers/security_report/finding_resolver.rb'
|
||||
|
|
|
|||
|
|
@ -1003,7 +1003,6 @@ RSpec/BeEq:
|
|||
- 'spec/models/anti_abuse/reports/note_spec.rb'
|
||||
- 'spec/models/anti_abuse/user_trust_score_spec.rb'
|
||||
- 'spec/models/appearance_spec.rb'
|
||||
- 'spec/models/attr_encrypted_patches_spec.rb'
|
||||
- 'spec/models/authentication_event_spec.rb'
|
||||
- 'spec/models/batched_git_ref_updates/deletion_spec.rb'
|
||||
- 'spec/models/blob_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
c2df26faa831df5935081aad4833580a791d0002
|
||||
da07ea6b72e37e8d07020e7be6cd4a378ddc442a
|
||||
|
|
|
|||
6
Gemfile
6
Gemfile
|
|
@ -19,10 +19,6 @@ extend ignore_feature_category
|
|||
|
||||
gem 'bundler-checksum', '~> 0.1.0', path: 'vendor/gems/bundler-checksum', require: false, feature_category: :shared
|
||||
|
||||
# NOTE: When incrementing the major or minor version here, also increment activerecord_version
|
||||
# in vendor/gems/attr_encrypted/attr_encrypted.gemspec until we resolve
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/375713
|
||||
#
|
||||
# See https://docs.gitlab.com/ee/development/gemfile.html#upgrade-rails for guidelines when upgrading Rails
|
||||
|
||||
gem 'rails', '~> 7.1.5.1', feature_category: :shared
|
||||
|
|
@ -128,7 +124,7 @@ gem 'invisible_captcha', '~> 2.1.0', feature_category: :insider_threat
|
|||
gem 'devise-two-factor', '~> 4.1.1', feature_category: :system_access
|
||||
gem 'rqrcode', '~> 2.2', feature_category: :system_access
|
||||
|
||||
gem 'attr_encrypted', '~> 3.2.4', path: 'vendor/gems/attr_encrypted', feature_category: :shared
|
||||
gem 'attr_encrypted', '~> 4.2', feature_category: :shared
|
||||
|
||||
# GitLab Pages
|
||||
gem 'validates_hostname', '~> 1.0.13', feature_category: :pages
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
{"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"},
|
||||
{"name":"async","version":"2.23.1","platform":"ruby","checksum":"612c97346948a5dbfb6b4aef12976416b01aef48ec2d41677efb25c8c32a5006"},
|
||||
{"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"},
|
||||
{"name":"attr_encrypted","version":"4.2.0","platform":"ruby","checksum":"7e5c80159e6e38ed40dc4e2e65c4f57234fe1f376bddc40c8b773bfb9b81ad51"},
|
||||
{"name":"attr_required","version":"1.0.2","platform":"ruby","checksum":"f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99"},
|
||||
{"name":"awesome_print","version":"1.9.2","platform":"ruby","checksum":"e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e"},
|
||||
{"name":"awrence","version":"1.2.1","platform":"ruby","checksum":"dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e"},
|
||||
|
|
|
|||
10
Gemfile.lock
10
Gemfile.lock
|
|
@ -126,12 +126,6 @@ PATH
|
|||
diffy (~> 3.4)
|
||||
oj (~> 3.16, >= 3.16.10)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/attr_encrypted
|
||||
specs:
|
||||
attr_encrypted (3.2.4)
|
||||
encryptor (~> 3.0.0)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/bundler-checksum
|
||||
specs:
|
||||
|
|
@ -352,6 +346,8 @@ GEM
|
|||
traces (~> 0.15)
|
||||
atlassian-jwt (0.2.1)
|
||||
jwt (~> 2.1)
|
||||
attr_encrypted (4.2.0)
|
||||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.2)
|
||||
awesome_print (1.9.2)
|
||||
awrence (1.2.1)
|
||||
|
|
@ -2079,7 +2075,7 @@ DEPENDENCIES
|
|||
asciidoctor-plantuml (~> 0.0.16)
|
||||
async (~> 2.23.0)
|
||||
atlassian-jwt (~> 0.2.1)
|
||||
attr_encrypted (~> 3.2.4)!
|
||||
attr_encrypted (~> 4.2)
|
||||
awesome_print
|
||||
aws-sdk-cloudformation (~> 1)
|
||||
aws-sdk-core (~> 3.223.0)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
{"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"},
|
||||
{"name":"async","version":"2.23.1","platform":"ruby","checksum":"612c97346948a5dbfb6b4aef12976416b01aef48ec2d41677efb25c8c32a5006"},
|
||||
{"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"},
|
||||
{"name":"attr_encrypted","version":"4.2.0","platform":"ruby","checksum":"7e5c80159e6e38ed40dc4e2e65c4f57234fe1f376bddc40c8b773bfb9b81ad51"},
|
||||
{"name":"attr_required","version":"1.0.2","platform":"ruby","checksum":"f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99"},
|
||||
{"name":"awesome_print","version":"1.9.2","platform":"ruby","checksum":"e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e"},
|
||||
{"name":"awrence","version":"1.2.1","platform":"ruby","checksum":"dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e"},
|
||||
|
|
|
|||
|
|
@ -126,12 +126,6 @@ PATH
|
|||
diffy (~> 3.4)
|
||||
oj (~> 3.16, >= 3.16.10)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/attr_encrypted
|
||||
specs:
|
||||
attr_encrypted (3.2.4)
|
||||
encryptor (~> 3.0.0)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/bundler-checksum
|
||||
specs:
|
||||
|
|
@ -352,6 +346,8 @@ GEM
|
|||
traces (~> 0.15)
|
||||
atlassian-jwt (0.2.1)
|
||||
jwt (~> 2.1)
|
||||
attr_encrypted (4.2.0)
|
||||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.2)
|
||||
awesome_print (1.9.2)
|
||||
awrence (1.2.1)
|
||||
|
|
@ -2079,7 +2075,7 @@ DEPENDENCIES
|
|||
asciidoctor-plantuml (~> 0.0.16)
|
||||
async (~> 2.23.0)
|
||||
atlassian-jwt (~> 0.2.1)
|
||||
attr_encrypted (~> 3.2.4)!
|
||||
attr_encrypted (~> 4.2)
|
||||
awesome_print
|
||||
aws-sdk-cloudformation (~> 1)
|
||||
aws-sdk-core (~> 3.223.0)
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@
|
|||
"PipelineExecutionSchedulePolicy",
|
||||
"ScanExecutionPolicy",
|
||||
"ScanResultPolicy",
|
||||
"SecurityPolicyType",
|
||||
"VulnerabilityManagementPolicy"
|
||||
],
|
||||
"PackageFileMetadata": [
|
||||
|
|
@ -194,6 +195,13 @@
|
|||
"Pipeline",
|
||||
"PipelineMinimalAccess"
|
||||
],
|
||||
"PolicyAttributesUnion": [
|
||||
"ApprovalPolicyAttributesType",
|
||||
"PipelineExecutionPolicyAttributesType",
|
||||
"PipelineExecutionScheduledPolicyAttributesType",
|
||||
"ScanExecutionPolicyAttributesType",
|
||||
"VulnerabilityManagementPolicyAttributesType"
|
||||
],
|
||||
"ProjectInterface": [
|
||||
"Project",
|
||||
"ProjectMinimalAccess"
|
||||
|
|
|
|||
|
|
@ -23,13 +23,12 @@ import { serializeParams, update2WeekFromNow, updateUrlWithQueryParams } from '.
|
|||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.url
|
||||
* @param {string|number} options.id
|
||||
* @param {Object<string, string|number>} options.params
|
||||
* @param {string} options.sort
|
||||
*/
|
||||
const fetchTokens = async ({ url, id, params, sort }) => {
|
||||
const fetchTokens = async ({ url, params, sort }) => {
|
||||
const { data, headers } = await axios.get(url, {
|
||||
params: { user_id: id, sort, ...params },
|
||||
params: { sort, ...params },
|
||||
});
|
||||
const { perPage, total } = parseIntPagination(normalizeHeaders(headers));
|
||||
|
||||
|
|
@ -105,7 +104,6 @@ export const useAccessTokens = defineStore('accessTokens', {
|
|||
const url = Api.buildUrl(this.urlShow.replace(':id', this.id));
|
||||
const { total } = await fetchTokens({
|
||||
url,
|
||||
id: this.id,
|
||||
params,
|
||||
sort: this.sort,
|
||||
});
|
||||
|
|
@ -136,7 +134,6 @@ export const useAccessTokens = defineStore('accessTokens', {
|
|||
updateUrlWithQueryParams({ params: this.params, sort: this.sort });
|
||||
const { data, perPage, total } = await fetchTokens({
|
||||
url,
|
||||
id: this.id,
|
||||
params: this.params,
|
||||
sort: this.sort,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default {
|
|||
},
|
||||
moreChildrenLinkText() {
|
||||
return n__(
|
||||
'View all (one more item)',
|
||||
'View all (%d more item)',
|
||||
'View all (%d more items)',
|
||||
this.item.childrenCount - this.item.children.length,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
discussion: {
|
||||
type: Array,
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
sortOrder: {
|
||||
type: String,
|
||||
|
|
@ -110,23 +111,26 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
note() {
|
||||
return this.discussion[0];
|
||||
notes() {
|
||||
return this.discussion.notes.nodes;
|
||||
},
|
||||
noteId() {
|
||||
return getIdFromGraphQLId(this.note.id);
|
||||
firstNote() {
|
||||
return this.notes[0];
|
||||
},
|
||||
firstNoteId() {
|
||||
return getIdFromGraphQLId(this.firstNote.id);
|
||||
},
|
||||
hasReplies() {
|
||||
return Boolean(this.replies?.length);
|
||||
},
|
||||
replies() {
|
||||
if (this.discussion?.length > 1) {
|
||||
return this.discussion.slice(1);
|
||||
if (this.notes?.length > 1) {
|
||||
return this.notes.slice(1);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
discussionId() {
|
||||
return this.firstComment?.id || '';
|
||||
return this.discussion.id || '';
|
||||
},
|
||||
shouldShowReplyForm() {
|
||||
return this.showForm || this.hasReplies;
|
||||
|
|
@ -134,20 +138,17 @@ export default {
|
|||
isOnlyCommentOfAThread() {
|
||||
return !this.hasReplies && !this.showForm;
|
||||
},
|
||||
firstComment() {
|
||||
return this.discussion[0]?.discussion;
|
||||
},
|
||||
isDiscussionResolved() {
|
||||
return this.firstComment?.resolved;
|
||||
return this.discussion.resolved;
|
||||
},
|
||||
isDiscussionResolvable() {
|
||||
return this.firstComment?.resolvable && this.note?.userPermissions?.resolveNote;
|
||||
return this.discussion.resolvable && this.firstNote?.userPermissions?.resolveNote;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
discussion: {
|
||||
handler(newDiscussion) {
|
||||
if (newDiscussion[0].discussion.resolved === false) {
|
||||
if (newDiscussion.resolved === false) {
|
||||
this.isExpanded = true;
|
||||
}
|
||||
},
|
||||
|
|
@ -163,7 +164,7 @@ export default {
|
|||
methods: {
|
||||
handleQuoteReply({ event, discussionId, text }) {
|
||||
// Ensure we're using correct discussion block for reply form.
|
||||
if (this.note.discussion.id === discussionId) {
|
||||
if (this.discussion.id === discussionId) {
|
||||
// Prevent 'r' being written.
|
||||
if (event && typeof event.preventDefault === 'function') {
|
||||
event.preventDefault();
|
||||
|
|
@ -214,21 +215,11 @@ export default {
|
|||
__typename: 'UserCore',
|
||||
};
|
||||
}
|
||||
const toggledDiscussionNotes = [...this.discussion].map((note) => {
|
||||
return {
|
||||
...note,
|
||||
discussion: {
|
||||
...note.discussion,
|
||||
resolved,
|
||||
resolvedBy,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: this.discussionId,
|
||||
notes: {
|
||||
nodes: [...toggledDiscussionNotes],
|
||||
},
|
||||
...this.discussion,
|
||||
resolved,
|
||||
resolvedBy,
|
||||
};
|
||||
},
|
||||
async resolveDiscussion() {
|
||||
|
|
@ -259,7 +250,7 @@ export default {
|
|||
<work-item-note
|
||||
v-if="isOnlyCommentOfAThread"
|
||||
:is-first-note="true"
|
||||
:note="note"
|
||||
:note="firstNote"
|
||||
:discussion-id="discussionId"
|
||||
:full-path="fullPath"
|
||||
:has-replies="hasReplies"
|
||||
|
|
@ -276,19 +267,20 @@ export default {
|
|||
:work-item-id="workItemId"
|
||||
:work-item-iid="workItemIid"
|
||||
:is-resolving="isResolving"
|
||||
:resolved-by="discussion.resolvedBy"
|
||||
:hide-fullscreen-markdown-button="hideFullscreenMarkdownButton"
|
||||
:uploads-path="uploadsPath"
|
||||
@startEditing="$emit('startEditing')"
|
||||
@resolve="resolveDiscussion"
|
||||
@startReplying="showReplyForm"
|
||||
@deleteNote="$emit('deleteNote', note)"
|
||||
@reportAbuse="$emit('reportAbuse', note)"
|
||||
@deleteNote="$emit('deleteNote', firstNote)"
|
||||
@reportAbuse="$emit('reportAbuse', firstNote)"
|
||||
@cancelEditing="$emit('cancelEditing')"
|
||||
@error="$emit('error', $event)"
|
||||
/>
|
||||
<timeline-entry-item
|
||||
v-else
|
||||
:data-note-id="noteId"
|
||||
:data-note-id="firstNoteId"
|
||||
:data-discussion-id="discussionId"
|
||||
class="note note-discussion gl-px-0"
|
||||
>
|
||||
|
|
@ -300,7 +292,7 @@ export default {
|
|||
<ul class="notes" data-testid="note-container">
|
||||
<work-item-note
|
||||
is-first-note
|
||||
:note="note"
|
||||
:note="firstNote"
|
||||
:discussion-id="discussionId"
|
||||
:full-path="fullPath"
|
||||
:has-replies="hasReplies"
|
||||
|
|
@ -317,12 +309,13 @@ export default {
|
|||
:is-discussion-resolved="isDiscussionResolved"
|
||||
:is-discussion-resolvable="isDiscussionResolvable"
|
||||
:is-resolving="isResolving"
|
||||
:resolved-by="discussion.resolvedBy"
|
||||
:hide-fullscreen-markdown-button="hideFullscreenMarkdownButton"
|
||||
:uploads-path="uploadsPath"
|
||||
@startReplying="showReplyForm"
|
||||
@startEditing="$emit('startEditing')"
|
||||
@deleteNote="$emit('deleteNote', note)"
|
||||
@reportAbuse="$emit('reportAbuse', note)"
|
||||
@deleteNote="$emit('deleteNote', firstNote)"
|
||||
@reportAbuse="$emit('reportAbuse', firstNote)"
|
||||
@cancelEditing="$emit('cancelEditing')"
|
||||
@resolve="resolveDiscussion"
|
||||
@error="$emit('error', $event)"
|
||||
|
|
@ -335,37 +328,37 @@ export default {
|
|||
@toggle="toggleDiscussion({ discussionId })"
|
||||
/>
|
||||
<template v-if="isExpanded">
|
||||
<template v-for="reply in replies">
|
||||
<work-item-note
|
||||
:key="threadKey(reply)"
|
||||
:discussion-id="discussionId"
|
||||
:full-path="fullPath"
|
||||
:note="reply"
|
||||
:work-item-type="workItemType"
|
||||
:is-modal="isModal"
|
||||
:autocomplete-data-sources="autocompleteDataSources"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:new-comment-template-paths="newCommentTemplatePaths"
|
||||
:assignees="assignees"
|
||||
:work-item-id="workItemId"
|
||||
:work-item-iid="workItemIid"
|
||||
:can-set-work-item-metadata="canSetWorkItemMetadata"
|
||||
:is-discussion-resolved="isDiscussionResolved"
|
||||
:is-discussion-resolvable="isDiscussionResolvable"
|
||||
:is-resolving="isResolving"
|
||||
:hide-fullscreen-markdown-button="hideFullscreenMarkdownButton"
|
||||
:uploads-path="uploadsPath"
|
||||
@startReplying="showReplyForm"
|
||||
@deleteNote="$emit('deleteNote', reply)"
|
||||
@reportAbuse="$emit('reportAbuse', reply)"
|
||||
@startEditing="$emit('startEditing')"
|
||||
@cancelEditing="$emit('cancelEditing')"
|
||||
@error="$emit('error', $event)"
|
||||
/>
|
||||
</template>
|
||||
<work-item-note
|
||||
v-for="reply in replies"
|
||||
:key="threadKey(reply)"
|
||||
:discussion-id="discussionId"
|
||||
:full-path="fullPath"
|
||||
:note="reply"
|
||||
:work-item-type="workItemType"
|
||||
:is-modal="isModal"
|
||||
:autocomplete-data-sources="autocompleteDataSources"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:new-comment-template-paths="newCommentTemplatePaths"
|
||||
:assignees="assignees"
|
||||
:work-item-id="workItemId"
|
||||
:work-item-iid="workItemIid"
|
||||
:can-set-work-item-metadata="canSetWorkItemMetadata"
|
||||
:is-discussion-resolved="isDiscussionResolved"
|
||||
:is-discussion-resolvable="isDiscussionResolvable"
|
||||
:is-resolving="isResolving"
|
||||
:resolved-by="discussion.resolvedBy"
|
||||
:hide-fullscreen-markdown-button="hideFullscreenMarkdownButton"
|
||||
:uploads-path="uploadsPath"
|
||||
@startReplying="showReplyForm"
|
||||
@deleteNote="$emit('deleteNote', reply)"
|
||||
@reportAbuse="$emit('reportAbuse', reply)"
|
||||
@startEditing="$emit('startEditing')"
|
||||
@cancelEditing="$emit('cancelEditing')"
|
||||
@error="$emit('error', $event)"
|
||||
/>
|
||||
<work-item-note-replying
|
||||
v-if="isReplying"
|
||||
:is-internal-note="note.internal"
|
||||
:is-internal-note="firstNote.internal"
|
||||
:body="replyingText"
|
||||
/>
|
||||
<work-item-add-note
|
||||
|
|
@ -384,7 +377,7 @@ export default {
|
|||
:markdown-preview-path="markdownPreviewPath"
|
||||
:new-comment-template-paths="newCommentTemplatePaths"
|
||||
:is-discussion-locked="isDiscussionLocked"
|
||||
:is-internal-thread="note.internal"
|
||||
:is-internal-thread="firstNote.internal"
|
||||
:is-work-item-confidential="isWorkItemConfidential"
|
||||
:is-discussion-resolved="isDiscussionResolved"
|
||||
:is-discussion-resolvable="isDiscussionResolvable"
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
resolvedBy: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
hideFullscreenMarkdownButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -213,9 +218,6 @@ export default {
|
|||
isWorkItemConfidential() {
|
||||
return this.workItem.confidential;
|
||||
},
|
||||
discussionResolvedBy() {
|
||||
return this.note.discussion.resolvedBy;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
gfmEventHub.$on('edit-note', this.handleEditNote);
|
||||
|
|
@ -413,7 +415,7 @@ export default {
|
|||
:can-resolve="canResolve"
|
||||
:is-resolved="isDiscussionResolved"
|
||||
:is-resolving="isResolving"
|
||||
:resolved-by="discussionResolvedBy"
|
||||
:resolved-by="resolvedBy"
|
||||
@startReplying="showReplyForm"
|
||||
@startEditing="startEditing"
|
||||
@resolve="$emit('resolve')"
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ export default {
|
|||
},
|
||||
getDiscussionKey(discussion) {
|
||||
// discussion key is important like this since after first comment changes
|
||||
const discussionId = discussion.notes.nodes[0].id;
|
||||
const discussionId = discussion.id;
|
||||
return discussionId.split('/')[discussionId.split('/').length - 1];
|
||||
},
|
||||
isSystemNote(note) {
|
||||
|
|
@ -488,10 +488,7 @@ export default {
|
|||
);
|
||||
},
|
||||
isDiscussionExpandedOnLoad(discussion) {
|
||||
return !this.isDiscussionResolved(discussion) || this.isHashTargeted(discussion);
|
||||
},
|
||||
isDiscussionResolved(discussion) {
|
||||
return discussion.notes.nodes[0]?.discussion?.resolved;
|
||||
return !discussion.resolved || this.isHashTargeted(discussion);
|
||||
},
|
||||
async fetchMoreNotes() {
|
||||
this.isLoadingMore = true;
|
||||
|
|
@ -508,14 +505,14 @@ export default {
|
|||
showDeleteNoteModal(note, discussion) {
|
||||
const isLastNote = discussion.notes.nodes.length === 1;
|
||||
this.$refs.deleteNoteModal.show();
|
||||
this.noteToDelete = { ...note, isLastNote };
|
||||
this.noteToDelete = { ...note, isLastNote, discussionId: discussion.id };
|
||||
},
|
||||
cancelDeletingNote() {
|
||||
this.noteToDelete = null;
|
||||
},
|
||||
async deleteNote() {
|
||||
try {
|
||||
const { id, isLastNote, discussion } = this.noteToDelete;
|
||||
const { id, isLastNote, discussionId } = this.noteToDelete;
|
||||
await this.$apollo.mutate({
|
||||
mutation: deleteNoteMutation,
|
||||
variables: {
|
||||
|
|
@ -525,7 +522,7 @@ export default {
|
|||
},
|
||||
update(cache) {
|
||||
const deletedObject = isLastNote
|
||||
? { __typename: TYPENAME_DISCUSSION, id: discussion.id }
|
||||
? { __typename: TYPENAME_DISCUSSION, id: discussionId }
|
||||
: { __typename: TYPENAME_NOTE, id };
|
||||
cache.modify({
|
||||
id: cache.identify(deletedObject),
|
||||
|
|
@ -590,7 +587,7 @@ export default {
|
|||
<work-item-discussion
|
||||
:key="getDiscussionKey(discussion)"
|
||||
ref="workItemDiscussion"
|
||||
:discussion="discussion.notes.nodes"
|
||||
:discussion="discussion"
|
||||
:full-path="fullPath"
|
||||
:work-item-id="workItemId"
|
||||
:work-item-iid="workItemIid"
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ export const WORK_ITEM_TO_ISSUABLE_MAP = {
|
|||
[WIDGET_TYPE_AWARD_EMOJI]: 'awardEmoji',
|
||||
[WIDGET_TYPE_TIME_TRACKING]: 'timeEstimate',
|
||||
[WIDGET_TYPE_COLOR]: 'color',
|
||||
[WIDGET_TYPE_STATUS]: 'status',
|
||||
};
|
||||
|
||||
export const LINKED_CATEGORIES_MAP = {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ mutation createWorkItemNote($input: CreateNoteInput!) {
|
|||
id
|
||||
discussion {
|
||||
id
|
||||
resolved
|
||||
resolvable
|
||||
resolvedBy {
|
||||
id
|
||||
name
|
||||
}
|
||||
notes {
|
||||
nodes {
|
||||
...WorkItemNote
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ mutation toggleWorkItemNoteResolveDiscussion($id: DiscussionID!, $resolve: Boole
|
|||
discussionToggleResolve(input: { id: $id, resolve: $resolve }) {
|
||||
discussion {
|
||||
id
|
||||
resolved
|
||||
resolvable
|
||||
resolvedBy {
|
||||
id
|
||||
name
|
||||
}
|
||||
notes {
|
||||
nodes {
|
||||
...WorkItemNote
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ fragment WorkItemDiscussionNote on Note {
|
|||
}
|
||||
discussion {
|
||||
id
|
||||
resolved
|
||||
resolvable
|
||||
resolvedBy {
|
||||
id
|
||||
name
|
||||
}
|
||||
notes {
|
||||
nodes {
|
||||
...WorkItemNote
|
||||
|
|
|
|||
|
|
@ -19,15 +19,6 @@ fragment WorkItemNote on Note {
|
|||
...User
|
||||
webPath
|
||||
}
|
||||
discussion {
|
||||
id
|
||||
resolved
|
||||
resolvable
|
||||
resolvedBy {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
author {
|
||||
...User
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ query workItemNotesByIid($fullPath: ID!, $iid: String!, $after: String, $pageSiz
|
|||
}
|
||||
nodes {
|
||||
id
|
||||
resolved
|
||||
resolvable
|
||||
resolvedBy {
|
||||
id
|
||||
name
|
||||
}
|
||||
notes {
|
||||
nodes {
|
||||
...WorkItemNote
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ module AccessTokensHelper
|
|||
create: user_settings_personal_access_tokens_path,
|
||||
revoke: api_v4_personal_access_tokens_path,
|
||||
rotate: api_v4_personal_access_tokens_path,
|
||||
show: api_v4_personal_access_tokens_path
|
||||
show: "#{api_v4_personal_access_tokens_path}?user_id=:id"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -332,10 +332,10 @@ class ApplicationSetting < ApplicationRecord
|
|||
allow_blank: true
|
||||
|
||||
jsonb_accessor :token_prefixes,
|
||||
instance_token_prefix: [:string, { default: 'gl' }]
|
||||
instance_token_prefix: [:string, { default: '' }]
|
||||
|
||||
validates :instance_token_prefix,
|
||||
format: { with: %r{\A[a-zA-Z0-9-]+\z} },
|
||||
format: { with: %r{\A[a-zA-Z0-9]+\z} },
|
||||
length: { maximum: 20, message: N_('is too long (maximum is %{count} characters)') },
|
||||
allow_blank: true
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ module ApplicationSettingImplementation
|
|||
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
|
||||
performance_bar_allowed_group_id: nil,
|
||||
personal_access_token_prefix: 'glpat-',
|
||||
instance_token_prefix: 'gl',
|
||||
instance_token_prefix: '',
|
||||
plantuml_enabled: false,
|
||||
plantuml_url: nil,
|
||||
diagramsnet_enabled: true,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ module Gitlab
|
|||
def sensitive_attributes
|
||||
attributes = []
|
||||
|
||||
if respond_to?(:attr_encrypted_attributes)
|
||||
# Per https://github.com/attr-encrypted/attr_encrypted/blob/a96693e9a2a25f4f910bf915e29b0f364f277032/lib/attr_encrypted.rb#L413
|
||||
attributes.concat attr_encrypted_attributes.keys
|
||||
attributes.concat attr_encrypted_attributes.values.map { |v| v[:attribute] } # rubocop:disable Rails/Pluck -- Not a ActiveRecord object
|
||||
attributes.concat attr_encrypted_attributes.values.map { |v| :"#{v[:attribute]}_iv" }
|
||||
if respond_to?(:attr_encrypted_encrypted_attributes)
|
||||
# Per https://github.com/attr-encrypted/attr_encrypted/blob/c2aa160c2327f2613fbca913e9fd20bce6e98880/lib/attr_encrypted.rb#L413
|
||||
attributes.concat attr_encrypted_encrypted_attributes.keys
|
||||
attributes.concat attr_encrypted_encrypted_attributes.values.map { |v| v[:attribute] } # rubocop:disable Rails/Pluck -- Not a ActiveRecord object
|
||||
attributes.concat attr_encrypted_encrypted_attributes.values.map { |v| :"#{v[:attribute]}_iv" }
|
||||
end
|
||||
|
||||
if respond_to?(:token_authenticatable_sensitive_fields)
|
||||
|
|
|
|||
|
|
@ -672,10 +672,10 @@ module Integrations
|
|||
|
||||
def reencrypt_properties
|
||||
unless properties.nil? || properties.empty?
|
||||
attr_encrypted_attributes = self.class.attr_encrypted_attributes[:properties]
|
||||
attr_encrypted_attributes = self.class.attr_encrypted_encrypted_attributes[:properties]
|
||||
key = dynamic_encryption_key_for_operation(attr_encrypted_attributes[:key])
|
||||
iv = generate_iv(attr_encrypted_attributes[:algorithm])
|
||||
ep = self.class.attr_encrypt(:properties, properties, { key: key, iv: iv })
|
||||
ep = self.class.attr_encrypted_encrypt(:properties, properties, { key: key, iv: iv })
|
||||
end
|
||||
|
||||
{ 'encrypted_properties' => ep, 'encrypted_properties_iv' => iv }
|
||||
|
|
|
|||
|
|
@ -170,20 +170,20 @@ module WebHooks
|
|||
|
||||
def decrypt_url_was
|
||||
options = {
|
||||
key: dynamic_encryption_key_for_operation(attr_encrypted_attributes[:url][:key]),
|
||||
key: dynamic_encryption_key_for_operation(attr_encrypted_encrypted_attributes[:url][:key]),
|
||||
iv: Base64.decode64(encrypted_url_iv_was)
|
||||
}
|
||||
|
||||
self.class.attr_decrypt(:url, encrypted_url_was, options)
|
||||
self.class.attr_encrypted_decrypt(:url, encrypted_url_was, options)
|
||||
end
|
||||
|
||||
def url_variables_were
|
||||
options = {
|
||||
key: dynamic_encryption_key_for_operation(attr_encrypted_attributes[:url_variables][:key]),
|
||||
key: dynamic_encryption_key_for_operation(attr_encrypted_encrypted_attributes[:url_variables][:key]),
|
||||
iv: encrypted_url_variables_iv_was
|
||||
}
|
||||
|
||||
self.class.attr_decrypt(:url_variables, encrypted_url_variables_was, options)
|
||||
self.class.attr_encrypted_decrypt(:url_variables, encrypted_url_variables_was, options)
|
||||
end
|
||||
|
||||
def initialize_url_variables
|
||||
|
|
|
|||
|
|
@ -58,9 +58,7 @@ class DeployToken < ApplicationRecord
|
|||
def self.prefix_for_deploy_token
|
||||
return DEPLOY_TOKEN_PREFIX unless Feature.enabled?(:custom_prefix_for_all_token_types, :instance)
|
||||
|
||||
# Manually remove gl - we'll add this from the configuration.
|
||||
# Once the feature flag has been removed, we can change DEPLOY_TOKEN_PREFIX to `ft-`
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(DEPLOY_TOKEN_PREFIX.delete_prefix('gl'))
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(DEPLOY_TOKEN_PREFIX)
|
||||
end
|
||||
|
||||
def valid_for_dependency_proxy?
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class GrafanaIntegration < ApplicationRecord
|
|||
end
|
||||
|
||||
def token
|
||||
attr_decrypt(:token, encrypted_token)
|
||||
attr_encrypted_decrypt(:token, encrypted_token)
|
||||
end
|
||||
|
||||
def check_token_changes
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ module Operations
|
|||
|
||||
DEFAULT_UNLEASH_API_VERSION = 1
|
||||
FEATURE_FLAGS_CLIENT_TOKEN_PREFIX = 'glffct-'
|
||||
FEATURE_FLAGS_CLIENT_TOKEN_PREFIX_PREFIX_WITHOUT_INSTANCE_PREFIX = 'ffct-'
|
||||
|
||||
self.table_name = 'operations_feature_flags_clients'
|
||||
|
||||
|
|
@ -35,13 +34,9 @@ module Operations
|
|||
end
|
||||
|
||||
def self.prefix_for_feature_flags_client_token
|
||||
if Feature.enabled?(:custom_prefix_for_all_token_types, :instance)
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(
|
||||
FEATURE_FLAGS_CLIENT_TOKEN_PREFIX_PREFIX_WITHOUT_INSTANCE_PREFIX
|
||||
)
|
||||
else
|
||||
FEATURE_FLAGS_CLIENT_TOKEN_PREFIX
|
||||
end
|
||||
return FEATURE_FLAGS_CLIENT_TOKEN_PREFIX unless Feature.enabled?(:custom_prefix_for_all_token_types, :instance)
|
||||
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(FEATURE_FLAGS_CLIENT_TOKEN_PREFIX)
|
||||
end
|
||||
|
||||
def unleash_api_version
|
||||
|
|
|
|||
|
|
@ -3,18 +3,22 @@
|
|||
class SentNotification < ApplicationRecord
|
||||
include EachBatch
|
||||
|
||||
INVALID_NOTEABLE = Class.new(StandardError)
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
belongs_to :recipient, class_name: "User"
|
||||
belongs_to :issue_email_participant
|
||||
belongs_to :namespace
|
||||
|
||||
validates :recipient, presence: true
|
||||
validates :recipient, :namespace_id, presence: true
|
||||
validates :reply_key, presence: true, uniqueness: true
|
||||
validates :noteable_id, presence: true, unless: :for_commit?
|
||||
validates :commit_id, presence: true, if: :for_commit?
|
||||
validates :commit_id, :project, presence: true, if: :for_commit?
|
||||
validates :in_reply_to_discussion_id, format: { with: /\A\h{40}\z/, allow_nil: true }
|
||||
validate :note_valid
|
||||
|
||||
before_validation :ensure_sharding_key
|
||||
before_create :ensure_created_at
|
||||
|
||||
class << self
|
||||
|
|
@ -109,6 +113,30 @@ class SentNotification < ApplicationRecord
|
|||
self.created_at = Time.current
|
||||
end
|
||||
|
||||
def ensure_sharding_key
|
||||
self.namespace_id = namespace_id_from_noteable
|
||||
end
|
||||
|
||||
def namespace_id_from_noteable
|
||||
case noteable
|
||||
when DesignManagement::Design, Issue
|
||||
noteable.namespace_id
|
||||
when MergeRequest, ProjectSnippet
|
||||
noteable.project.project_namespace_id
|
||||
when Commit
|
||||
project&.project_namespace_id
|
||||
when WikiPage::Meta
|
||||
noteable.namespace_id || noteable.project.project_namespace_id
|
||||
else
|
||||
# Raising an error here to make sure that the correct sharding key is set if support
|
||||
# for a new `noteable_type` is added.
|
||||
raise(
|
||||
INVALID_NOTEABLE,
|
||||
_("%{noteable_type} is not supported") % { noteable_type: noteable_type }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def reply_params
|
||||
{
|
||||
noteable_type: self.noteable_type,
|
||||
|
|
@ -129,3 +157,5 @@ class SentNotification < ApplicationRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
SentNotification.prepend_mod
|
||||
|
|
|
|||
|
|
@ -2954,9 +2954,7 @@ class User < ApplicationRecord
|
|||
def self.prefix_for_feed_token
|
||||
return FEED_TOKEN_PREFIX unless Feature.enabled?(:custom_prefix_for_all_token_types, :instance)
|
||||
|
||||
# Manually remove gl - we'll add this from the configuration.
|
||||
# Once the feature flag has been removed, we can change FEED_TOKEN_PREFIX to `ft-`
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(FEED_TOKEN_PREFIX.delete_prefix('gl'))
|
||||
::Authn::TokenField::PrefixHelper.prepend_instance_prefix(FEED_TOKEN_PREFIX)
|
||||
end
|
||||
|
||||
# method overridden in EE
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserPreference < ApplicationRecord
|
||||
include SafelyChangeColumnDefault
|
||||
|
||||
# We could use enums, but Rails 4 doesn't support multiple
|
||||
# enum options with same name for multiple fields, also it creates
|
||||
# extra methods that aren't really needed here.
|
||||
|
|
@ -10,6 +12,8 @@ class UserPreference < ApplicationRecord
|
|||
belongs_to :user
|
||||
belongs_to :home_organization, class_name: "Organizations::Organization", optional: true
|
||||
|
||||
columns_changing_default :organization_groups_projects_display
|
||||
|
||||
scope :with_user, -> { joins(:user) }
|
||||
scope :gitpod_enabled, -> { where(gitpod_enabled: true) }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ name: bbm_retry_sidekiq_shutdown_exception
|
|||
description:
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366720
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182060
|
||||
rollout_issue_url:
|
||||
milestone: '18.0'
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/542900
|
||||
milestone: '18.1'
|
||||
group: group::database frameworks
|
||||
type: experiment
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -8,23 +8,10 @@ class MigrateAnonymousSearchesFlagToApplicationSettings < Gitlab::Database::Migr
|
|||
self.table_name = 'application_settings'
|
||||
end
|
||||
|
||||
def up
|
||||
ApplicationSetting.reset_column_information
|
||||
# Marking this as a noop because the backfill migration incorrectly ignores the
|
||||
# default_enabled configuration value, causing wrong feature flag value to be added in setting.
|
||||
# Revert MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/190832
|
||||
def up; end
|
||||
|
||||
application_setting = ApplicationSetting.last
|
||||
return unless application_setting
|
||||
|
||||
search_settings = application_setting.search
|
||||
search_settings[:anonymous_searches_allowed] = feature_flag_enabled?('allow_anonymous_searches')
|
||||
application_setting.update_columns(search: search_settings, updated_at: Time.current)
|
||||
end
|
||||
|
||||
def down
|
||||
application_setting = ApplicationSetting.last
|
||||
return unless application_setting
|
||||
|
||||
search_settings_hash = application_setting.search
|
||||
search_settings_hash.delete('anonymous_searches_allowed')
|
||||
application_setting.update_column(:search, search_settings_hash)
|
||||
end
|
||||
def down; end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeUserPreferenceOrganizationGroupsProjectsDisplayDefault < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.0'
|
||||
|
||||
def up
|
||||
change_column_default :user_preferences, :organization_groups_projects_display, from: 0, to: 1
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_default :user_preferences, :organization_groups_projects_display, from: 1, to: 0
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveDefaultFromSentNotificationsNamespaceId < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.1'
|
||||
|
||||
def change
|
||||
change_column_default :sent_notifications, :namespace_id, from: 0, to: nil
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleIndexForPCiPipelineVariablesOnProjectId < Gitlab::Database::Migration[2.3]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers
|
||||
|
||||
milestone '18.1'
|
||||
|
||||
TABLE_NAME = :p_ci_pipeline_variables
|
||||
INDEX_NAME = :index_p_ci_pipeline_variables_on_project_id
|
||||
COLUMN_NAME = :project_id
|
||||
|
||||
def up
|
||||
prepare_partitioned_async_index(TABLE_NAME, COLUMN_NAME, name: INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_partitioned_async_index(TABLE_NAME, COLUMN_NAME, name: INDEX_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
bfe0c221edcbb48e5c1511ed9dcad20b9753c7db6db389f8cc8b15ec4f2b006a
|
||||
|
|
@ -0,0 +1 @@
|
|||
756b664964be9ee347f2fc909051dc5fcc6f3097fc6600dbd128cb6b90099818
|
||||
|
|
@ -0,0 +1 @@
|
|||
c98f8f3735452917b50c379ce0b4b011538536927f903242944f7876fc0f1454
|
||||
|
|
@ -22771,7 +22771,7 @@ CREATE TABLE sent_notifications (
|
|||
id bigint NOT NULL,
|
||||
issue_email_participant_id bigint,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
namespace_id bigint DEFAULT 0 NOT NULL
|
||||
namespace_id bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE sent_notifications_id_seq
|
||||
|
|
@ -24277,7 +24277,7 @@ CREATE TABLE user_preferences (
|
|||
early_access_program_tracking boolean DEFAULT false NOT NULL,
|
||||
extensions_marketplace_opt_in_status smallint DEFAULT 0 NOT NULL,
|
||||
organization_groups_projects_sort text,
|
||||
organization_groups_projects_display smallint DEFAULT 0 NOT NULL,
|
||||
organization_groups_projects_display smallint DEFAULT 1 NOT NULL,
|
||||
dpop_enabled boolean DEFAULT false NOT NULL,
|
||||
use_work_items_view boolean DEFAULT false NOT NULL,
|
||||
text_editor_type smallint DEFAULT 0 NOT NULL,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Cells 1.0 is in development. For more information about the state of cell develo
|
|||
|
||||
{{< /alert >}}
|
||||
|
||||
This page explains how to configure the GitLab Rails console for cell functionality. For more information on the proposed design and terminology, see the design document for [Cells](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/).
|
||||
To test cell functionality, configure the GitLab Rails console.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ these definitions yet.
|
|||
|
||||
These are the defined terms to describe all aspects of Geo. Using a set of clearly
|
||||
defined terms helps us to communicate efficiently and avoids confusion. The language
|
||||
on this page aims to be [ubiquitous](https://handbook.gitlab.com/handbook/communication/#ubiquitous-language)
|
||||
and [as simple as possible](https://handbook.gitlab.com/handbook/communication/#simple-language).
|
||||
on this page aims to be ubiquitous
|
||||
and as simple as possible.
|
||||
|
||||
## Main terms
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ from [owasp.org](https://owasp.org/).
|
|||
private projects. Geo replicates them all indiscriminately. "Selective sync"
|
||||
exists for files and repositories (but not database content), which would permit
|
||||
only less-sensitive projects to be replicated to a **secondary** site if desired.
|
||||
- See also: [GitLab data classification policy](https://handbook.gitlab.com/handbook/security/data-classification-standard/).
|
||||
|
||||
### What data backup and retention requirements have been defined for the application?
|
||||
|
||||
|
|
|
|||
|
|
@ -371,8 +371,7 @@ This feature is available for testing, but not ready for production use.
|
|||
|
||||
{{< /alert >}}
|
||||
|
||||
You can set a custom prefix for all tokens generated on your instance.
|
||||
By default, GitLab uses `gl` as the instance prefix.
|
||||
You can set a custom prefix that is prepended to all tokens generated on your instance.
|
||||
|
||||
Custom token prefixes apply only to the following tokens:
|
||||
|
||||
|
|
|
|||
|
|
@ -19162,6 +19162,29 @@ The edge type for [`SecretPermission`](#secretpermission).
|
|||
| <a id="secretpermissionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="secretpermissionedgenode"></a>`node` | [`SecretPermission`](#secretpermission) | The item at the end of the edge. |
|
||||
|
||||
#### `SecurityPolicyTypeConnection`
|
||||
|
||||
The connection type for [`SecurityPolicyType`](#securitypolicytype).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="securitypolicytypeconnectionedges"></a>`edges` | [`[SecurityPolicyTypeEdge]`](#securitypolicytypeedge) | A list of edges. |
|
||||
| <a id="securitypolicytypeconnectionnodes"></a>`nodes` | [`[SecurityPolicyType]`](#securitypolicytype) | A list of nodes. |
|
||||
| <a id="securitypolicytypeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `SecurityPolicyTypeEdge`
|
||||
|
||||
The edge type for [`SecurityPolicyType`](#securitypolicytype).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="securitypolicytypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="securitypolicytypeedgenode"></a>`node` | [`SecurityPolicyType`](#securitypolicytype) | The item at the end of the edge. |
|
||||
|
||||
#### `SentryErrorConnection`
|
||||
|
||||
The connection type for [`SentryError`](#sentryerror).
|
||||
|
|
@ -21487,6 +21510,22 @@ Represents the approval policy.
|
|||
| <a id="approvalpolicyuserapprovers"></a>`userApprovers` | [`[UserCore!]`](#usercore) | Approvers of the user type. |
|
||||
| <a id="approvalpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `ApprovalPolicyAttributesType`
|
||||
|
||||
Represents policy fields related to the approval policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="approvalpolicyattributestypeactionapprovers"></a>`actionApprovers` | [`[PolicyApproversType!]`](#policyapproverstype) | Multiple approvers action. |
|
||||
| <a id="approvalpolicyattributestypeallgroupapprovers"></a>`allGroupApprovers` | [`[PolicyApprovalGroup!]`](#policyapprovalgroup) | All potential approvers of the group type, including groups inaccessible to the user. |
|
||||
| <a id="approvalpolicyattributestypecustomroles"></a>`customRoles` | [`[MemberRole!]`](#memberrole) | Approvers of the custom role type. Users belonging to these role(s) alone will be approvers. |
|
||||
| <a id="approvalpolicyattributestypedeprecatedproperties"></a>`deprecatedProperties` {{< icon name="warning-solid" >}} | [`[String!]`](#string) | **Introduced** in GitLab 16.10. **Status**: Experiment. All deprecated properties in the policy. |
|
||||
| <a id="approvalpolicyattributestyperoleapprovers"></a>`roleApprovers` | [`[MemberAccessLevelName!]`](#memberaccesslevelname) | Approvers of the role type. Users belonging to these role(s) alone will be approvers. |
|
||||
| <a id="approvalpolicyattributestypesource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
|
||||
| <a id="approvalpolicyattributestypeuserapprovers"></a>`userApprovers` | [`[UserCore!]`](#usercore) | Approvers of the user type. |
|
||||
|
||||
### `ApprovalProjectRule`
|
||||
|
||||
Describes a project approval rule regarding who can approve merge requests.
|
||||
|
|
@ -29085,6 +29124,28 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="groupscanresultpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="groupscanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Group.securityPolicies`
|
||||
|
||||
List of security policies configured for the namespace.
|
||||
|
||||
{{< details >}}
|
||||
**Introduced** in GitLab 18.1.
|
||||
**Status**: Experiment.
|
||||
{{< /details >}}
|
||||
|
||||
Returns [`SecurityPolicyTypeConnection`](#securitypolicytypeconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, and `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupsecuritypoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="groupsecuritypoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
|
||||
##### `Group.securityPolicyProjectSuggestions`
|
||||
|
||||
Security policy project suggestions.
|
||||
|
|
@ -33529,6 +33590,28 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="namespacescanresultpoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespacescanresultpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. Default is DIRECT. |
|
||||
|
||||
##### `Namespace.securityPolicies`
|
||||
|
||||
List of security policies configured for the namespace.
|
||||
|
||||
{{< details >}}
|
||||
**Introduced** in GitLab 18.1.
|
||||
**Status**: Experiment.
|
||||
{{< /details >}}
|
||||
|
||||
Returns [`SecurityPolicyTypeConnection`](#securitypolicytypeconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, and `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="namespacesecuritypoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="namespacesecuritypoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
|
||||
##### `Namespace.vulnerabilityManagementPolicies`
|
||||
|
||||
Vulnerability Management Policies of the project.
|
||||
|
|
@ -34766,6 +34849,18 @@ Represents the pipeline execution policy.
|
|||
| <a id="pipelineexecutionpolicywarnings"></a>`warnings` | [`[String!]!`](#string) | Warnings associated with the policy. |
|
||||
| <a id="pipelineexecutionpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `PipelineExecutionPolicyAttributesType`
|
||||
|
||||
Represents policy fields related to the pipeline execution policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelineexecutionpolicyattributestypepolicyblobfilepath"></a>`policyBlobFilePath` | [`String!`](#string) | Path to the policy file in the project. |
|
||||
| <a id="pipelineexecutionpolicyattributestypesource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
|
||||
| <a id="pipelineexecutionpolicyattributestypewarnings"></a>`warnings` | [`[String!]!`](#string) | Warnings associated with the policy. |
|
||||
|
||||
### `PipelineExecutionSchedulePolicy`
|
||||
|
||||
Represents the pipeline execution schedule policy.
|
||||
|
|
@ -34785,6 +34880,18 @@ Represents the pipeline execution schedule policy.
|
|||
| <a id="pipelineexecutionschedulepolicywarnings"></a>`warnings` | [`[String!]!`](#string) | Warnings associated with the policy. |
|
||||
| <a id="pipelineexecutionschedulepolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `PipelineExecutionScheduledPolicyAttributesType`
|
||||
|
||||
Represents policy fields related to the pipeline execution scheduled policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelineexecutionscheduledpolicyattributestypepolicyblobfilepath"></a>`policyBlobFilePath` | [`String!`](#string) | Path to the policy file in the project. |
|
||||
| <a id="pipelineexecutionscheduledpolicyattributestypesource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
|
||||
| <a id="pipelineexecutionscheduledpolicyattributestypewarnings"></a>`warnings` | [`[String!]!`](#string) | Warnings associated with the policy. |
|
||||
|
||||
### `PipelineManualVariable`
|
||||
|
||||
CI/CD variables added to a manual pipeline.
|
||||
|
|
@ -37056,6 +37163,28 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="projectsecurityexclusionsscanner"></a>`scanner` | [`ExclusionScannerEnum`](#exclusionscannerenum) | Filter entries by scanner. |
|
||||
| <a id="projectsecurityexclusionstype"></a>`type` | [`ExclusionTypeEnum`](#exclusiontypeenum) | Filter entries by exclusion type. |
|
||||
|
||||
##### `Project.securityPolicies`
|
||||
|
||||
All security policies of the project.
|
||||
|
||||
{{< details >}}
|
||||
**Introduced** in GitLab 18.1.
|
||||
**Status**: Experiment.
|
||||
{{< /details >}}
|
||||
|
||||
Returns [`SecurityPolicyTypeConnection`](#securitypolicytypeconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, and `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectsecuritypoliciesincludeunscoped"></a>`includeUnscoped` | [`Boolean`](#boolean) | Filter policies that are scoped to the project. |
|
||||
| <a id="projectsecuritypoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
|
||||
|
||||
##### `Project.securityPolicyProjectLinkedGroups`
|
||||
|
||||
Groups linked to the project, when used as Security Policy Project.
|
||||
|
|
@ -38721,6 +38850,17 @@ Represents the scan execution policy.
|
|||
| <a id="scanexecutionpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
|
||||
| <a id="scanexecutionpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `ScanExecutionPolicyAttributesType`
|
||||
|
||||
Represents policy fields related to the scan execution policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="scanexecutionpolicyattributestypedeprecatedproperties"></a>`deprecatedProperties` {{< icon name="warning-solid" >}} | [`[String!]`](#string) | **Introduced** in GitLab 17.3. **Status**: Experiment. All deprecated properties in the policy. |
|
||||
| <a id="scanexecutionpolicyattributestypesource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
|
||||
|
||||
### `ScanResultPolicy`
|
||||
|
||||
Represents the scan result policy.
|
||||
|
|
@ -38809,6 +38949,24 @@ Representation of a secrets permission.
|
|||
| <a id="secretpermissionprincipal"></a>`principal` | [`Principal!`](#principal) | Who is provided access to. For eg: User/Role/MemberRole/Group. |
|
||||
| <a id="secretpermissionproject"></a>`project` | [`Project!`](#project) | Project the secret permission belong to. |
|
||||
|
||||
### `SecurityPolicyType`
|
||||
|
||||
Represents the security policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="securitypolicytypedescription"></a>`description` | [`String!`](#string) | Description of the policy. |
|
||||
| <a id="securitypolicytypeeditpath"></a>`editPath` | [`String!`](#string) | URL of policy edit page. |
|
||||
| <a id="securitypolicytypeenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether the policy is enabled. |
|
||||
| <a id="securitypolicytypename"></a>`name` | [`String!`](#string) | Name of the policy. |
|
||||
| <a id="securitypolicytypepolicyattributes"></a>`policyAttributes` | [`PolicyAttributesUnion!`](#policyattributesunion) | Attributes specific to the policy type. |
|
||||
| <a id="securitypolicytypepolicyscope"></a>`policyScope` | [`PolicyScope`](#policyscope) | Scope of the policy. |
|
||||
| <a id="securitypolicytypetype"></a>`type` | [`String`](#string) | Description of the policy type. |
|
||||
| <a id="securitypolicytypeupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
|
||||
| <a id="securitypolicytypeyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `SecurityPolicyValidationError`
|
||||
|
||||
Security policy validation error.
|
||||
|
|
@ -41219,6 +41377,16 @@ Represents the vulnerability management policy.
|
|||
| <a id="vulnerabilitymanagementpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
|
||||
| <a id="vulnerabilitymanagementpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `VulnerabilityManagementPolicyAttributesType`
|
||||
|
||||
Represents policy fields related to the vulnerability management policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="vulnerabilitymanagementpolicyattributestypesource"></a>`source` | [`SecurityPolicySource!`](#securitypolicysource) | Source of the policy. Its fields depend on the source type. |
|
||||
|
||||
### `VulnerabilityNamespaceStatisticType`
|
||||
|
||||
Counts for each vulnerability severity in the group and its subgroups.
|
||||
|
|
@ -47742,6 +47910,18 @@ One of:
|
|||
- [`PypiMetadata`](#pypimetadata)
|
||||
- [`TerraformModuleMetadata`](#terraformmodulemetadata)
|
||||
|
||||
#### `PolicyAttributesUnion`
|
||||
|
||||
Represents specific policy types. Its fields depend on the policy type.
|
||||
|
||||
One of:
|
||||
|
||||
- [`ApprovalPolicyAttributesType`](#approvalpolicyattributestype)
|
||||
- [`PipelineExecutionPolicyAttributesType`](#pipelineexecutionpolicyattributestype)
|
||||
- [`PipelineExecutionScheduledPolicyAttributesType`](#pipelineexecutionscheduledpolicyattributestype)
|
||||
- [`ScanExecutionPolicyAttributesType`](#scanexecutionpolicyattributestype)
|
||||
- [`VulnerabilityManagementPolicyAttributesType`](#vulnerabilitymanagementpolicyattributestype)
|
||||
|
||||
#### `Registrable`
|
||||
|
||||
One of:
|
||||
|
|
@ -48336,6 +48516,7 @@ Implementations:
|
|||
- [`PipelineExecutionSchedulePolicy`](#pipelineexecutionschedulepolicy)
|
||||
- [`ScanExecutionPolicy`](#scanexecutionpolicy)
|
||||
- [`ScanResultPolicy`](#scanresultpolicy)
|
||||
- [`SecurityPolicyType`](#securitypolicytype)
|
||||
- [`VulnerabilityManagementPolicy`](#vulnerabilitymanagementpolicy)
|
||||
|
||||
##### Fields
|
||||
|
|
|
|||
|
|
@ -168,3 +168,16 @@ To resolve this issue:
|
|||
For more information about the relations and batches that failed to export,
|
||||
use the export status API endpoints for [projects](../../../api/project_relations_export.md#export-status)
|
||||
and [groups](../../../api/group_relations_export.md#export-status) on the source instance.
|
||||
|
||||
## Error: `duplicate key value violates unique constraint`
|
||||
|
||||
You might get the following error when importing records:
|
||||
|
||||
```plaintext
|
||||
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint
|
||||
```
|
||||
|
||||
This error occurs when a Sidekiq worker processing the import
|
||||
restarts due to high CPU or memory usage during import.
|
||||
To configure workers for imports, see
|
||||
[Sidekiq configuration](../../project/import/_index.md#sidekiq-configuration).
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ To view the organizations you have access to:
|
|||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
In [Cells 1.0](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/iterations/cells-1.0/) organizations can be only private.
|
||||
Support for only private organizations is proposed for [cells 1.0](https://gitlab.com/groups/gitlab-org/-/epics/12383).
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ In [Cells 1.0](https://handbook.gitlab.com/handbook/engineering/architecture/des
|
|||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
In [Cells 1.0](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/iterations/cells-1.0/) organizations can be only private.
|
||||
Support for only private organizations is proposed for [cells 1.0](https://gitlab.com/groups/gitlab-org/-/epics/12383).
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
@ -92,8 +92,8 @@ In [Cells 1.0](https://handbook.gitlab.com/handbook/engineering/architecture/des
|
|||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
Switching between organizations is not supported in [Cells 1.0](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/iterations/cells-1.0/),
|
||||
but is supported in [Cells 1.5](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/iterations/cells-1.5/).
|
||||
Switching between organizations is not supported in cells 1.0.
|
||||
Support for switching organizations is proposed for [cells 1.5](https://gitlab.com/groups/gitlab-org/-/epics/12505).
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
@ -152,7 +152,3 @@ The Organization description field supports a limited subset of [GitLab Flavored
|
|||
- [Emphasis](../markdown.md#emphasis)
|
||||
- [Links](../markdown.md#links)
|
||||
- [Superscripts / Subscripts](../markdown.md#superscripts-and-subscripts)
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Organization design document](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/organization/)
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ To create a project that uses SHA-256 hashing:
|
|||
|
||||
### Why SHA-256?
|
||||
|
||||
By default, Git uses the SHA-1 [hashing algorithm](https://handbook.gitlab.com/handbook/security/cryptographic-standard/#algorithmic-standards)
|
||||
By default, Git uses the SHA-1 hashing algorithm
|
||||
to generate a 40-character
|
||||
ID for objects such as commits, blobs, trees, and tags. The SHA-1 algorithm was proven to be insecure when
|
||||
[Google was able to produce a hash collision](https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html).
|
||||
|
|
|
|||
|
|
@ -14,15 +14,6 @@ module Authn
|
|||
|
||||
Gitlab::CurrentSettings.current_application_settings.instance_token_prefix
|
||||
end
|
||||
|
||||
def self.default_instance_prefix(prefix)
|
||||
return prefix unless Feature.enabled?(:custom_prefix_for_all_token_types, :instance)
|
||||
|
||||
return prefix unless prefix.starts_with?(instance_prefix)
|
||||
|
||||
# remove the configured instance prefix and add the default:
|
||||
"#{ApplicationSetting.defaults[:instance_token_prefix]}#{prefix.delete_prefix(instance_prefix)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Authn
|
|||
class DeployToken
|
||||
def self.prefix?(plaintext)
|
||||
deploy_token_prefixes = [::DeployToken.prefix_for_deploy_token,
|
||||
Authn::TokenField::PrefixHelper.default_instance_prefix(::DeployToken.prefix_for_deploy_token)]
|
||||
::DeployToken::DEPLOY_TOKEN_PREFIX].uniq
|
||||
|
||||
plaintext.start_with?(*deploy_token_prefixes)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ module Authn
|
|||
module Tokens
|
||||
class FeatureFlagsClientToken
|
||||
def self.prefix?(plaintext)
|
||||
current_prefix = ::Operations::FeatureFlagsClient.prefix_for_feature_flags_client_token
|
||||
default_prefix = Authn::TokenField::PrefixHelper.default_instance_prefix(current_prefix)
|
||||
plaintext.start_with?(current_prefix, default_prefix)
|
||||
feature_flags_client_token_prefixes = [::Operations::FeatureFlagsClient.prefix_for_feature_flags_client_token,
|
||||
::Operations::FeatureFlagsClient::FEATURE_FLAGS_CLIENT_TOKEN_PREFIX].uniq
|
||||
|
||||
plaintext.start_with?(*feature_flags_client_token_prefixes)
|
||||
end
|
||||
|
||||
attr_reader :revocable, :source
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ module Authn
|
|||
module Tokens
|
||||
class FeedToken
|
||||
def self.prefix?(plaintext)
|
||||
plaintext.start_with?(::User.prefix_for_feed_token,
|
||||
Authn::TokenField::PrefixHelper.default_instance_prefix(::User.prefix_for_feed_token))
|
||||
feed_token_prefixes = [::User.prefix_for_feed_token,
|
||||
::User::FEED_TOKEN_PREFIX].uniq
|
||||
|
||||
plaintext.start_with?(*feed_token_prefixes)
|
||||
end
|
||||
|
||||
attr_reader :revocable, :source
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
models_with_attr_encrypted_attributes.each do |model|
|
||||
models_with_attributes[model] += model.attr_encrypted_attributes.keys
|
||||
models_with_attributes[model] += model.attr_encrypted_encrypted_attributes.keys
|
||||
end
|
||||
|
||||
models_with_encrypted_tokens.each do |model|
|
||||
|
|
@ -87,7 +87,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def models_with_attr_encrypted_attributes
|
||||
all_models.select { |d| d.attr_encrypted_attributes.present? }
|
||||
all_models.select { |d| d.attr_encrypted_encrypted_attributes.present? }
|
||||
end
|
||||
|
||||
def models_with_encrypted_tokens
|
||||
|
|
|
|||
|
|
@ -169,9 +169,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def remove_encrypted_attributes!
|
||||
return unless relation_class.respond_to?(:attr_encrypted_attributes) && relation_class.attr_encrypted_attributes.any?
|
||||
return unless relation_class.respond_to?(:attr_encrypted_encrypted_attributes) && relation_class.attr_encrypted_encrypted_attributes.any?
|
||||
|
||||
relation_class.attr_encrypted_attributes.each_key do |key|
|
||||
relation_class.attr_encrypted_encrypted_attributes.each_key do |key|
|
||||
@relation_hash[key.to_s] = nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ module Gitlab
|
|||
attr_reader :old_key, :new_key
|
||||
|
||||
def otp_secret_settings
|
||||
@otp_secret_settings ||= User.attr_encrypted_attributes[:otp_secret]
|
||||
@otp_secret_settings ||= User.attr_encrypted_encrypted_attributes[:otp_secret]
|
||||
end
|
||||
|
||||
def reencrypt(user, old_key, new_key)
|
||||
|
|
|
|||
|
|
@ -1191,6 +1191,9 @@ msgid_plural "%{no_of_days} days"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%{noteable_type} is not supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{numOfSteps} steps across %{numOfFiles} files"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -66855,7 +66858,7 @@ msgstr ""
|
|||
msgid "View all"
|
||||
msgstr ""
|
||||
|
||||
msgid "View all (one more item)"
|
||||
msgid "View all (%d more item)"
|
||||
msgid_plural "View all (%d more items)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set
|
|||
let(:application_settings) { ApplicationSetting.current }
|
||||
|
||||
context 'with valid prefix' do
|
||||
let(:prefix) { 'instance-prefix-' }
|
||||
let(:prefix) { 'instanceprefix' }
|
||||
|
||||
it 'updates instance_token_prefix setting' do
|
||||
put :update, params: { application_setting: { instance_token_prefix: prefix } }
|
||||
|
|
@ -339,6 +339,7 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set
|
|||
this-prefix-is-longer-than-20-characters
|
||||
@
|
||||
:
|
||||
-
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -319,9 +319,10 @@ RSpec.describe SentNotificationsController, feature_category: :shared do
|
|||
|
||||
context 'when unsubscribing from design' do
|
||||
let(:design) do
|
||||
# reload necessary as namespace_id is set in a DB trigger
|
||||
create(:design, issue: issue) do |design|
|
||||
design.subscriptions.create!(user: user, project: project, subscribed: true)
|
||||
end
|
||||
end.reload
|
||||
end
|
||||
|
||||
let(:sent_notification) do
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ describe('AccessTokens', () => {
|
|||
const accessTokenCreate = '/api/v4/groups/1/service_accounts/:id/personal_access_tokens/';
|
||||
const accessTokenRevoke = '/api/v4/groups/2/service_accounts/:id/personal_access_tokens/';
|
||||
const accessTokenRotate = '/api/v4/groups/3/service_accounts/:id/personal_access_tokens/';
|
||||
const accessTokenShow = '/api/v4/personal_access_tokens';
|
||||
const accessTokenShow = '/api/v4/groups/4/service_accounts/:id/personal_access_token';
|
||||
const id = 235;
|
||||
|
||||
const createComponent = () => {
|
||||
|
|
@ -56,7 +56,7 @@ describe('AccessTokens', () => {
|
|||
urlCreate: '/api/v4/groups/1/service_accounts/:id/personal_access_tokens/',
|
||||
urlRevoke: '/api/v4/groups/2/service_accounts/:id/personal_access_tokens/',
|
||||
urlRotate: '/api/v4/groups/3/service_accounts/:id/personal_access_tokens/',
|
||||
urlShow: '/api/v4/personal_access_tokens',
|
||||
urlShow: '/api/v4/groups/4/service_accounts/:id/personal_access_token',
|
||||
});
|
||||
expect(store.fetchTokens).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ describe('useAccessTokens store', () => {
|
|||
const urlCreate = '/api/v4/groups/1/service_accounts/:id/personal_access_tokens';
|
||||
const urlRevoke = '/api/v4/groups/2/service_accounts/:id/personal_access_tokens';
|
||||
const urlRotate = '/api/v4/groups/3/service_accounts/:id/personal_access_tokens';
|
||||
const urlShow = '/api/v4/groups/4/service_accounts/:id/personal_access_token';
|
||||
const urlShow = '/api/v4/personal_access_tokens?user_id=:id';
|
||||
|
||||
const headers = {
|
||||
'X-Page': 1,
|
||||
|
|
@ -173,7 +173,6 @@ describe('useAccessTokens store', () => {
|
|||
page: 1,
|
||||
sort: 'expires_asc',
|
||||
search: 'my token',
|
||||
user_id: 235,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
@ -194,12 +193,11 @@ describe('useAccessTokens store', () => {
|
|||
expect(mockAxios.history.get).toHaveLength(1);
|
||||
expect(mockAxios.history.get[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
url: '/api/v4/groups/4/service_accounts/235/personal_access_token',
|
||||
url: '/api/v4/personal_access_tokens?user_id=235',
|
||||
params: {
|
||||
page: 1,
|
||||
sort: 'expires_asc',
|
||||
search: 'dummy',
|
||||
user_id: 235,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
@ -286,13 +284,12 @@ describe('useAccessTokens store', () => {
|
|||
expect(mockAxios.history.get).toHaveLength(1);
|
||||
expect(mockAxios.history.get[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
url: '/api/v4/groups/4/service_accounts/235/personal_access_token',
|
||||
url: '/api/v4/personal_access_tokens?user_id=235',
|
||||
params: {
|
||||
page: 1,
|
||||
sort: 'expires_asc',
|
||||
state: 'inactive',
|
||||
search: 'my token',
|
||||
user_id: 235,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
@ -405,7 +402,6 @@ describe('useAccessTokens store', () => {
|
|||
page: 1,
|
||||
sort: 'expires_asc',
|
||||
search: 'my token',
|
||||
user_id: 235,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
@ -510,7 +506,6 @@ describe('useAccessTokens store', () => {
|
|||
page: 1,
|
||||
sort: 'expires_asc',
|
||||
search: 'my token',
|
||||
user_id: 235,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import WorkItemNoteReplying from '~/work_items/components/notes/work_item_note_r
|
|||
import WorkItemAddNote from '~/work_items/components/notes/work_item_add_note.vue';
|
||||
import toggleWorkItemNoteResolveDiscussion from '~/work_items/graphql/notes/toggle_work_item_note_resolve_discussion.mutation.graphql';
|
||||
import {
|
||||
mockWorkItemCommentNote,
|
||||
mockWorkItemDiscussion,
|
||||
mockToggleResolveDiscussionResponse,
|
||||
mockWorkItemNotesResponseWithComments,
|
||||
} from 'jest/work_items/mock_data';
|
||||
|
|
@ -40,7 +40,7 @@ describe('Work Item Discussion', () => {
|
|||
.mockResolvedValue(mockToggleResolveDiscussionResponse);
|
||||
|
||||
const createComponent = ({
|
||||
discussion = [mockWorkItemCommentNote],
|
||||
discussion = mockWorkItemDiscussion,
|
||||
workItemId = mockWorkItemId,
|
||||
workItemType = 'Task',
|
||||
isExpandedOnLoad = true,
|
||||
|
|
@ -85,7 +85,7 @@ describe('Work Item Discussion', () => {
|
|||
describe('when hideFullscreenMarkdownButton is true', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes,
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0],
|
||||
hideFullscreenMarkdownButton: true,
|
||||
});
|
||||
});
|
||||
|
|
@ -100,18 +100,17 @@ describe('Work Item Discussion', () => {
|
|||
describe('When the main comments has threads', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes,
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0],
|
||||
});
|
||||
});
|
||||
|
||||
it('should render timeline-entry-item with required data attributes', () => {
|
||||
const expectedDiscussion =
|
||||
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes[0];
|
||||
const expectedDiscussion = mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0];
|
||||
|
||||
expect(findTimelineEntryItem().attributes()).toEqual({
|
||||
class: expect.any(String),
|
||||
'data-note-id': expectedDiscussion.id.split('/').pop(),
|
||||
'data-discussion-id': expectedDiscussion.discussion.id,
|
||||
'data-note-id': expectedDiscussion.notes.nodes[0].id.split('/').pop(),
|
||||
'data-discussion-id': expectedDiscussion.id,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -154,7 +153,7 @@ describe('Work Item Discussion', () => {
|
|||
describe('When replying to any comment', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes,
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0],
|
||||
});
|
||||
const mainComment = findThreadAtIndex(0);
|
||||
|
||||
|
|
@ -187,7 +186,7 @@ describe('Work Item Discussion', () => {
|
|||
createComponent();
|
||||
findThreadAtIndex(0).vm.$emit('deleteNote');
|
||||
|
||||
expect(wrapper.emitted('deleteNote')).toEqual([[mockWorkItemCommentNote]]);
|
||||
expect(wrapper.emitted('deleteNote')).toEqual([[mockWorkItemDiscussion.notes.nodes[0]]]);
|
||||
});
|
||||
|
||||
it('emits `error` event when child note emits an `error`', () => {
|
||||
|
|
@ -204,28 +203,20 @@ describe('Work Item Discussion', () => {
|
|||
window.gon.current_user_id = 'gid://gitlab/User/1';
|
||||
window.gon.current_user_fullname = 'Administrator';
|
||||
});
|
||||
const resolvedDiscussionList =
|
||||
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes.slice();
|
||||
resolvedDiscussionList.forEach((note) => {
|
||||
return {
|
||||
...note,
|
||||
discussion: {
|
||||
id: note.discussion.id,
|
||||
resolved: true,
|
||||
resolvable: true,
|
||||
resolvedBy: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Administrator',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
};
|
||||
});
|
||||
const resolvedDiscussion = {
|
||||
...mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0],
|
||||
resolved: true,
|
||||
resolvable: true,
|
||||
resolvedBy: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Administrator',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
};
|
||||
|
||||
it('Resolved discussion is not expanded on default', () => {
|
||||
createComponent({
|
||||
discussion: resolvedDiscussionList,
|
||||
discussion: resolvedDiscussion,
|
||||
isExpandedOnLoad: false,
|
||||
});
|
||||
|
||||
|
|
@ -234,7 +225,7 @@ describe('Work Item Discussion', () => {
|
|||
|
||||
it('should pass `isDiscussionResolvable` prop as true when user has resolveNote permission', () => {
|
||||
createComponent({
|
||||
discussion: resolvedDiscussionList,
|
||||
discussion: resolvedDiscussion,
|
||||
isExpandedOnLoad: false,
|
||||
});
|
||||
|
||||
|
|
@ -242,23 +233,25 @@ describe('Work Item Discussion', () => {
|
|||
});
|
||||
|
||||
it('should pass `isDiscussionResolvable` prop as false when user does not have resolveNote permission', () => {
|
||||
const resolvedDiscussionListWithoutPermissions = resolvedDiscussionList.map((note) => {
|
||||
return {
|
||||
...note,
|
||||
userPermissions: {
|
||||
adminNote: true,
|
||||
awardEmoji: true,
|
||||
readNote: true,
|
||||
createNote: true,
|
||||
resolveNote: false,
|
||||
repositionNote: true,
|
||||
__typename: 'NotePermissions',
|
||||
},
|
||||
};
|
||||
});
|
||||
const resolvedDiscussionWithoutPermissions = { ...resolvedDiscussion };
|
||||
|
||||
resolvedDiscussionWithoutPermissions.notes.nodes = resolvedDiscussion.notes.nodes.map(
|
||||
(note) => {
|
||||
return {
|
||||
...note,
|
||||
userPermissions: {
|
||||
adminNote: true,
|
||||
awardEmoji: true,
|
||||
createNote: true,
|
||||
resolveNote: false,
|
||||
__typename: 'NotePermissions',
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
createComponent({
|
||||
discussion: resolvedDiscussionListWithoutPermissions,
|
||||
discussion: resolvedDiscussionWithoutPermissions,
|
||||
isExpandedOnLoad: false,
|
||||
});
|
||||
|
||||
|
|
@ -267,8 +260,7 @@ describe('Work Item Discussion', () => {
|
|||
|
||||
it('toggles resolved status when toggle icon is clicked from note header', async () => {
|
||||
createComponent({
|
||||
discussion:
|
||||
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes.slice(),
|
||||
discussion: mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0],
|
||||
isExpandedOnLoad: true,
|
||||
});
|
||||
|
||||
|
|
@ -287,14 +279,13 @@ describe('Work Item Discussion', () => {
|
|||
});
|
||||
|
||||
describe('quote-reply event', () => {
|
||||
const mockDiscussions =
|
||||
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes;
|
||||
const mockDiscussion = mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0];
|
||||
let mockAppendTextSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
window.gon.current_user_id = 1;
|
||||
createComponent({
|
||||
discussion: mockDiscussions,
|
||||
discussion: mockDiscussion,
|
||||
});
|
||||
|
||||
mockAppendTextSpy = jest
|
||||
|
|
@ -304,7 +295,7 @@ describe('Work Item Discussion', () => {
|
|||
event: {
|
||||
preventDefault: jest.fn(),
|
||||
},
|
||||
discussionId: mockDiscussions[0].discussion.id,
|
||||
discussionId: mockDiscussion.id,
|
||||
text: 'quoted text',
|
||||
});
|
||||
await nextTick();
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ describe('WorkItemNotes component', () => {
|
|||
const findNotesLoading = () => wrapper.findComponent(WorkItemNotesLoading);
|
||||
const findActivityHeader = () => wrapper.findComponent(WorkItemNotesActivityHeader);
|
||||
const findSystemNoteAtIndex = (index) => findAllSystemNotes().at(index);
|
||||
const findAllWorkItemCommentNotes = () => wrapper.findAllComponents(WorkItemDiscussion);
|
||||
const findWorkItemCommentNoteAtIndex = (index) => findAllWorkItemCommentNotes().at(index);
|
||||
const findAllWorkItemDiscussions = () => wrapper.findAllComponents(WorkItemDiscussion);
|
||||
const findWorkItemDiscussionAtIndex = (index) => findAllWorkItemDiscussions().at(index);
|
||||
const findDeleteNoteModal = () => wrapper.findComponent(GlModal);
|
||||
const findWorkItemAddNote = () => wrapper.findComponent(WorkItemAddNote);
|
||||
|
||||
|
|
@ -276,8 +276,8 @@ describe('WorkItemNotes component', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findWorkItemCommentNoteAtIndex(0).props('discussion')).toEqual(
|
||||
mockWorkItemNoteResponse.data.note.discussion.notes.nodes,
|
||||
expect(findWorkItemDiscussionAtIndex(0).props('discussion')).toEqual(
|
||||
mockWorkItemNoteResponse.data.note.discussion,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -380,15 +380,15 @@ describe('WorkItemNotes component', () => {
|
|||
|
||||
it('should have work item notes', () => {
|
||||
expect(workItemNotesWithCommentsQueryHandler).toHaveBeenCalled();
|
||||
expect(findAllWorkItemCommentNotes()).toHaveLength(mockDiscussions.length);
|
||||
expect(findAllWorkItemDiscussions()).toHaveLength(mockDiscussions.length);
|
||||
});
|
||||
|
||||
it('should pass all the correct props to work item comment note', () => {
|
||||
const commentIndex = 0;
|
||||
const firstCommentNote = findWorkItemCommentNoteAtIndex(commentIndex);
|
||||
const firstDiscussion = findWorkItemDiscussionAtIndex(commentIndex);
|
||||
|
||||
expect(firstCommentNote.props()).toMatchObject({
|
||||
discussion: mockDiscussions[commentIndex].notes.nodes,
|
||||
expect(firstDiscussion.props()).toMatchObject({
|
||||
discussion: mockDiscussions[commentIndex],
|
||||
autocompleteDataSources: autocompleteDataSources({
|
||||
fullPath: 'test-path',
|
||||
iid: mockWorkItemIid,
|
||||
|
|
@ -407,7 +407,7 @@ describe('WorkItemNotes component', () => {
|
|||
});
|
||||
await waitForPromises();
|
||||
|
||||
findWorkItemCommentNoteAtIndex(0).vm.$emit('deleteNote', { id: '1', isLastNote: false });
|
||||
findWorkItemDiscussionAtIndex(0).vm.$emit('deleteNote', { id: '1', isLastNote: false });
|
||||
expect(showModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -422,7 +422,7 @@ describe('WorkItemNotes component', () => {
|
|||
it('sends the mutation with correct variables', () => {
|
||||
const noteId = 'some-test-id';
|
||||
|
||||
findWorkItemCommentNoteAtIndex(0).vm.$emit('deleteNote', { id: noteId });
|
||||
findWorkItemDiscussionAtIndex(0).vm.$emit('deleteNote', { id: noteId });
|
||||
findDeleteNoteModal().vm.$emit('primary');
|
||||
|
||||
expect(deleteWorkItemNoteMutationSuccessHandler).toHaveBeenCalledWith({
|
||||
|
|
@ -433,22 +433,22 @@ describe('WorkItemNotes component', () => {
|
|||
});
|
||||
|
||||
it('successfully removes the note from the discussion', async () => {
|
||||
expect(findWorkItemCommentNoteAtIndex(0).props('discussion')).toHaveLength(2);
|
||||
expect(findWorkItemDiscussionAtIndex(0).props('discussion').notes.nodes).toHaveLength(2);
|
||||
|
||||
findWorkItemCommentNoteAtIndex(0).vm.$emit('deleteNote', {
|
||||
findWorkItemDiscussionAtIndex(0).vm.$emit('deleteNote', {
|
||||
id: mockDiscussions[0].notes.nodes[0].id,
|
||||
});
|
||||
findDeleteNoteModal().vm.$emit('primary');
|
||||
|
||||
await waitForPromises();
|
||||
expect(findWorkItemCommentNoteAtIndex(0).props('discussion')).toHaveLength(1);
|
||||
expect(findWorkItemDiscussionAtIndex(0).props('discussion').notes.nodes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('successfully removes the discussion from work item if discussion only had one note', async () => {
|
||||
const secondDiscussion = findWorkItemCommentNoteAtIndex(1);
|
||||
const secondDiscussion = findWorkItemDiscussionAtIndex(1);
|
||||
|
||||
expect(findAllWorkItemCommentNotes()).toHaveLength(2);
|
||||
expect(secondDiscussion.props('discussion')).toHaveLength(1);
|
||||
expect(findAllWorkItemDiscussions()).toHaveLength(2);
|
||||
expect(secondDiscussion.props('discussion').notes.nodes).toHaveLength(1);
|
||||
|
||||
secondDiscussion.vm.$emit('deleteNote', {
|
||||
id: mockDiscussions[1].notes.nodes[0].id,
|
||||
|
|
@ -457,7 +457,7 @@ describe('WorkItemNotes component', () => {
|
|||
findDeleteNoteModal().vm.$emit('primary');
|
||||
|
||||
await waitForPromises();
|
||||
expect(findAllWorkItemCommentNotes()).toHaveLength(1);
|
||||
expect(findAllWorkItemDiscussions()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -468,7 +468,7 @@ describe('WorkItemNotes component', () => {
|
|||
});
|
||||
await waitForPromises();
|
||||
|
||||
findWorkItemCommentNoteAtIndex(0).vm.$emit('deleteNote', {
|
||||
findWorkItemDiscussionAtIndex(0).vm.$emit('deleteNote', {
|
||||
id: mockDiscussions[0].notes.nodes[0].id,
|
||||
});
|
||||
findDeleteNoteModal().vm.$emit('primary');
|
||||
|
|
@ -514,7 +514,7 @@ describe('WorkItemNotes component', () => {
|
|||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findWorkItemCommentNoteAtIndex(0).props('isWorkItemConfidential')).toBe(true);
|
||||
expect(findWorkItemDiscussionAtIndex(0).props('isWorkItemConfidential')).toBe(true);
|
||||
});
|
||||
|
||||
describe('when project context', () => {
|
||||
|
|
@ -532,7 +532,7 @@ describe('WorkItemNotes component', () => {
|
|||
defaultWorkItemNotesQueryHandler: workItemNotesWithCommentsQueryHandler,
|
||||
});
|
||||
await waitForPromises();
|
||||
expect(findAllWorkItemCommentNotes().at(0).props('isExpandedOnLoad')).toBe(true);
|
||||
expect(findAllWorkItemDiscussions().at(0).props('isExpandedOnLoad')).toBe(true);
|
||||
});
|
||||
|
||||
it('should be collapsed when the discussion is resolved', async () => {
|
||||
|
|
@ -543,7 +543,7 @@ describe('WorkItemNotes component', () => {
|
|||
});
|
||||
|
||||
await waitForPromises();
|
||||
expect(findAllWorkItemCommentNotes().at(0).props('isExpandedOnLoad')).toBe(false);
|
||||
expect(findAllWorkItemDiscussions().at(0).props('isExpandedOnLoad')).toBe(false);
|
||||
});
|
||||
|
||||
it('should be expanded when the notes are resolved but the target note hash has note id', async () => {
|
||||
|
|
@ -558,7 +558,7 @@ describe('WorkItemNotes component', () => {
|
|||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(findAllWorkItemCommentNotes().at(0).props('isExpandedOnLoad')).toBe(true);
|
||||
expect(findAllWorkItemDiscussions().at(0).props('isExpandedOnLoad')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -614,7 +614,7 @@ describe('WorkItemNotes component', () => {
|
|||
expect(findWorkItemAddNote().props('hideFullscreenMarkdownButton')).toBe(true);
|
||||
});
|
||||
it('passes prop to work-item-discussion', () => {
|
||||
expect(findAllWorkItemCommentNotes().at(0).props('hideFullscreenMarkdownButton')).toBe(true);
|
||||
expect(findAllWorkItemDiscussions().at(0).props('hideFullscreenMarkdownButton')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -717,10 +717,6 @@ describe('WorkItemNotes component', () => {
|
|||
expect(gfmEventHub.$emit).toHaveBeenCalledWith('edit-note', {
|
||||
note: {
|
||||
...mockLastNote,
|
||||
discussion: {
|
||||
...mockLastNote.discussion,
|
||||
resolvable: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4036,6 +4036,9 @@ export const mockWorkItemNotesResponse = {
|
|||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/8bbc4890b6ff0f2cde93a5a0947cd2b8a13d3b6e',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4054,13 +4057,6 @@ export const mockWorkItemNotesResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723561234',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4091,6 +4087,9 @@ export const mockWorkItemNotesResponse = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/7b08b89a728a5ceb7de8334246837ba1d07270dc',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4109,9 +4108,6 @@ export const mockWorkItemNotesResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723565678',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4142,6 +4138,9 @@ export const mockWorkItemNotesResponse = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/0f2f195ec0d1ef95ee9d5b10446b8e96a7d83864',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4159,13 +4158,6 @@ export const mockWorkItemNotesResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723560987',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4234,6 +4226,9 @@ export const mockWorkItemNotesByIidResponse = {
|
|||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/8bbc4890b6ff0f2cde93a5a0947cd2b8a13d3b6e',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4252,13 +4247,6 @@ export const mockWorkItemNotesByIidResponse = {
|
|||
maxAccessLevelOfAuthor: null,
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723561234',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4292,6 +4280,9 @@ export const mockWorkItemNotesByIidResponse = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/7b08b89a728a5ceb7de8334246837ba1d07270dc',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4310,13 +4301,6 @@ export const mockWorkItemNotesByIidResponse = {
|
|||
maxAccessLevelOfAuthor: null,
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723568765',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4350,6 +4334,9 @@ export const mockWorkItemNotesByIidResponse = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/addbc177f7664699a135130ab05ffb78c57e4db3',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4368,13 +4355,6 @@ export const mockWorkItemNotesByIidResponse = {
|
|||
maxAccessLevelOfAuthor: null,
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723569876',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4445,6 +4425,9 @@ export const mockMoreWorkItemNotesResponse = {
|
|||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/8bbc4890b6ff0f2cde93a5a0947cd2b8a13d3b6e',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4463,13 +4446,6 @@ export const mockMoreWorkItemNotesResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da1112356a59e',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4503,6 +4479,9 @@ export const mockMoreWorkItemNotesResponse = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/7b08b89a728a5ceb7de8334246837ba1d07270dc',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4521,13 +4500,6 @@ export const mockMoreWorkItemNotesResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da1272356a59e',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4561,6 +4533,9 @@ export const mockMoreWorkItemNotesResponse = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/0f2f195ec0d1ef95ee9d5b10446b8e96a7d83864',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4578,13 +4553,6 @@ export const mockMoreWorkItemNotesResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723569876',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4640,6 +4608,9 @@ export const createWorkItemNoteResponse = ({
|
|||
id: 'gid://gitlab/Note/569',
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122',
|
||||
resolved: false,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4657,13 +4628,6 @@ export const createWorkItemNoteResponse = ({
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122',
|
||||
resolved: false,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -4720,13 +4684,6 @@ export const mockWorkItemCommentNote = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723569876',
|
||||
resolved: false,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4759,6 +4716,16 @@ export const mockWorkItemCommentByMaintainer = {
|
|||
maxAccessLevelOfAuthor: 'Maintainer',
|
||||
};
|
||||
|
||||
export const mockWorkItemDiscussion = {
|
||||
id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122',
|
||||
resolved: false,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [mockWorkItemCommentNote],
|
||||
},
|
||||
};
|
||||
|
||||
export const mockWorkItemNotesResponseWithComments = (resolved = false) => {
|
||||
return {
|
||||
data: {
|
||||
|
|
@ -4786,6 +4753,9 @@ export const mockWorkItemNotesResponseWithComments = (resolved = false) => {
|
|||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/8bbc4890b6ff0f2cde93a5a0947cd2b8a13d3b6e',
|
||||
resolved,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4803,13 +4773,6 @@ export const mockWorkItemNotesResponseWithComments = (resolved = false) => {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/2bb1162fd0d39297d1a68fdd7d4083d3780af0f3',
|
||||
resolved,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -4848,13 +4811,6 @@ export const mockWorkItemNotesResponseWithComments = (resolved = false) => {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/2bb1162fd0d39297d1a68fdd7d4083d3780af0f3',
|
||||
resolved,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -4885,6 +4841,9 @@ export const mockWorkItemNotesResponseWithComments = (resolved = false) => {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/0f2f195ec0d1ef95ee9d5b10446b8e96a7d83864',
|
||||
resolved,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4902,13 +4861,6 @@ export const mockWorkItemNotesResponseWithComments = (resolved = false) => {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723560987',
|
||||
resolved,
|
||||
resolvable: true,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -4966,6 +4918,9 @@ export const workItemNotesCreateSubscriptionResponse = {
|
|||
imported: false,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/8bbc4890b6ff0f2cde93a5a0947cd2b8a13d3b6e',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -4983,13 +4938,6 @@ export const workItemNotesCreateSubscriptionResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723560987',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -5065,13 +5013,6 @@ export const workItemNotesUpdateSubscriptionResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723560987',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
userPermissions: {
|
||||
adminNote: false,
|
||||
awardEmoji: true,
|
||||
|
|
@ -5126,13 +5067,6 @@ export const workItemSystemNoteWithMetadata = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/7d4a46ea0525e2eeed451f7b718b0ebe73205374',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -5194,6 +5128,9 @@ export const workItemNotesWithSystemNotesWithChangedDescription = {
|
|||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/aa72f4c2f3eef66afa6d79a805178801ce4bd89f',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -5212,13 +5149,6 @@ export const workItemNotesWithSystemNotesWithChangedDescription = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/aa72f4c2f3eef66afa6d79a805178801ce4bd89f',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -5262,6 +5192,9 @@ export const workItemNotesWithSystemNotesWithChangedDescription = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/a7d3cf7bd72f7a98f802845f538af65cb11a02cc',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -5280,13 +5213,6 @@ export const workItemNotesWithSystemNotesWithChangedDescription = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/a7d3cf7bd72f7a98f802845f538af65cb11a02cc',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -5330,6 +5256,9 @@ export const workItemNotesWithSystemNotesWithChangedDescription = {
|
|||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/391eed1ee0a258cc966a51dde900424f3b51b95d',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -5348,13 +5277,6 @@ export const workItemNotesWithSystemNotesWithChangedDescription = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
authorIsContributor: false,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/391eed1ee0a258cc966a51dde900424f3b51b95d',
|
||||
resolved: false,
|
||||
resolvable: false,
|
||||
resolvedBy: null,
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -6131,6 +6053,13 @@ export const mockToggleResolveDiscussionResponse = {
|
|||
discussionToggleResolve: {
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/c4be5bec43a737e0966dbc4c040b1517e7febfa9',
|
||||
resolved: true,
|
||||
resolvable: true,
|
||||
resolvedBy: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Administrator',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
|
|
@ -6147,17 +6076,6 @@ export const mockToggleResolveDiscussionResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
lastEditedBy: null,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/c4be5bec43a737e0966dbc4c040b1517e7febfa9',
|
||||
resolved: true,
|
||||
resolvable: true,
|
||||
resolvedBy: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Administrator',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
@ -6196,17 +6114,6 @@ export const mockToggleResolveDiscussionResponse = {
|
|||
maxAccessLevelOfAuthor: 'Owner',
|
||||
lastEditedBy: null,
|
||||
externalAuthor: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/c4be5bec43a737e0966dbc4c040b1517e7febfa9',
|
||||
resolved: true,
|
||||
resolvable: true,
|
||||
resolvedBy: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
name: 'Administrator',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
__typename: 'Discussion',
|
||||
},
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ RSpec.describe AccessTokensHelper, feature_category: :system_access do
|
|||
create: '/-/user_settings/personal_access_tokens',
|
||||
revoke: '/api/v4/personal_access_tokens',
|
||||
rotate: '/api/v4/personal_access_tokens',
|
||||
show: '/api/v4/personal_access_tokens'
|
||||
show: '/api/v4/personal_access_tokens?user_id=:id'
|
||||
}
|
||||
}))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
'new_project_path' => '/projects/new',
|
||||
'association_counts' => stubbed_results,
|
||||
'organization_groups_projects_sort' => 'name_asc',
|
||||
'organization_groups_projects_display' => 'projects'
|
||||
'organization_groups_projects_display' => 'groups'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
@ -203,9 +203,9 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
'new_group_path' => '/-/organizations/default/groups/new',
|
||||
'new_project_path' => '/projects/new',
|
||||
'organization_groups_projects_sort' => 'name_asc',
|
||||
'organization_groups_projects_display' => 'projects',
|
||||
'organization_groups_projects_display' => 'groups',
|
||||
'user_preference_sort' => 'name_asc',
|
||||
'user_preference_display' => 'projects'
|
||||
'user_preference_display' => 'groups'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ RSpec.describe Authn::AgnosticTokenIdentifier, feature_category: :system_access
|
|||
end
|
||||
|
||||
context 'with custom instance prefix' do
|
||||
let_it_be(:instance_prefix) { 'instance-prefix-' }
|
||||
let_it_be(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ RSpec.describe Authn::TokenField::PrefixHelper, feature_category: :system_access
|
|||
subject(:prepend_instance_prefix) { described_class.prepend_instance_prefix(prefix) }
|
||||
|
||||
context 'with application config default value' do
|
||||
it 'prepends the instance wide token prefix' do
|
||||
expect(prepend_instance_prefix).to eq("gl#{prefix}")
|
||||
it 'returns the prefix without prepending the instance prefix' do
|
||||
expect(prepend_instance_prefix).to eq(prefix)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with application config set to custom value' do
|
||||
let(:instance_prefix) { 'instance-prefix-' }
|
||||
let(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
|
|
@ -36,56 +36,4 @@ RSpec.describe Authn::TokenField::PrefixHelper, feature_category: :system_access
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default_instance_prefix' do
|
||||
let(:prefix) { 'glprefix' }
|
||||
|
||||
subject(:default_instance_prefix) { described_class.default_instance_prefix(prefix) }
|
||||
|
||||
context 'with application config default value' do
|
||||
it 'returns the default prefix' do
|
||||
expect(default_instance_prefix).to eq(prefix)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with application config set to custom value' do
|
||||
let(:instance_prefix) { 'instance-prefix-' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
end
|
||||
|
||||
context 'for the default prefix' do
|
||||
it 'still returns the default prefix' do
|
||||
expect(default_instance_prefix).to eq(prefix)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for the current instance prefix' do
|
||||
let(:prefix) { "#{instance_prefix}token-prefix" }
|
||||
|
||||
it 'changes the instance prefix to the default prefix' do
|
||||
expect(default_instance_prefix).to eq('gltoken-prefix')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a non-matching prefix' do
|
||||
let(:prefix) { 'different-token-prefix' }
|
||||
|
||||
it 'keeps the prefix unchanged' do
|
||||
expect(default_instance_prefix).to eq(prefix)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag custom_prefix_for_all_token_types disabled' do
|
||||
before do
|
||||
stub_feature_flags(custom_prefix_for_all_token_types: false)
|
||||
end
|
||||
|
||||
it 'keeps the prefix unchanged' do
|
||||
expect(default_instance_prefix).to eq(prefix)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ RSpec.describe Authn::Tokens::DeployToken, feature_category: :system_access do
|
|||
end
|
||||
|
||||
context 'with custom instance prefix' do
|
||||
let_it_be(:instance_prefix) { 'instance-prefix-' }
|
||||
let_it_be(:instance_prefix) { 'instanceprefix' }
|
||||
let(:valid_revocable) { create(:deploy_token) }
|
||||
let(:plaintext) { valid_revocable.token }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ RSpec.describe Authn::Tokens::FeedToken, feature_category: :system_access do
|
|||
it_behaves_like 'finding the valid revocable'
|
||||
|
||||
context 'with different instance prefix' do
|
||||
let(:instance_prefix) { 'instance-prefix-' }
|
||||
let(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ RSpec.describe Gitlab::Email::Handler::UnsubscribeHandler do
|
|||
stub_config_setting(host: 'localhost')
|
||||
end
|
||||
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX}") }
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:user) { create(:user) }
|
||||
let(:noteable) { create(:issue, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:noteable) { create(:issue, project: project) }
|
||||
|
||||
let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ RSpec.describe 'Import/Export attribute configuration', feature_category: :impor
|
|||
it 'has no new columns' do
|
||||
relation_names_for(:project).each do |relation_name|
|
||||
relation_class = relation_class_for_name(relation_name)
|
||||
relation_attributes = relation_class.new.attributes.keys - relation_class.attr_encrypted_attributes.keys.map(&:to_s)
|
||||
relation_attributes = relation_class.new.attributes.keys - relation_class.attr_encrypted_encrypted_attributes.keys.map(&:to_s)
|
||||
|
||||
current_attributes = parsed_attributes(relation_name, relation_attributes)
|
||||
safe_attributes = safe_model_attributes[relation_class.to_s].dup || []
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ RSpec.describe 'Import/Export Project configuration', feature_category: :importe
|
|||
context "where relation #{params[:relation_path]}" do
|
||||
it 'does not have prohibited keys' do
|
||||
relation_class = relation_class_for_name(relation_name)
|
||||
relation_attributes = relation_class.new.attributes.keys - relation_class.attr_encrypted_attributes.keys.map(&:to_s)
|
||||
relation_attributes = relation_class.new.attributes.keys - relation_class.attr_encrypted_encrypted_attributes.keys.map(&:to_s)
|
||||
current_attributes = parsed_attributes(relation_name, relation_attributes)
|
||||
prohibited_keys = current_attributes.select do |attribute|
|
||||
prohibited_key?(attribute) || !relation_class.attribute_method?(attribute)
|
||||
|
|
|
|||
|
|
@ -679,6 +679,8 @@ RSpec.describe Notify, feature_category: :code_review_workflow do
|
|||
end
|
||||
|
||||
shared_examples 'a discussion note email' do |model|
|
||||
let(:noteable_url_args) { {} }
|
||||
|
||||
it_behaves_like 'it should have Gmail Actions links'
|
||||
|
||||
# Two tests with flakiness:1 are coming from this test:
|
||||
|
|
@ -702,7 +704,12 @@ RSpec.describe Notify, feature_category: :code_review_workflow do
|
|||
issuable_url = "project_wiki_url" if note.for_wiki_page?
|
||||
anchor = "note_#{note.id}"
|
||||
|
||||
is_expected.to have_body_text "started a new <a href=\"#{public_send(issuable_url, project, note.noteable, anchor: anchor)}\">discussion</a>"
|
||||
is_expected.to have_body_text(
|
||||
format(
|
||||
"started a new <a href=\"%{url}\">discussion</a>",
|
||||
url: public_send(issuable_url, project, note.noteable, anchor: anchor, **noteable_url_args)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when a comment on an existing discussion' do
|
||||
|
|
@ -826,12 +833,14 @@ RSpec.describe Notify, feature_category: :code_review_workflow do
|
|||
|
||||
before do
|
||||
allow(note).to receive(:noteable).and_return(wiki_page_meta)
|
||||
allow(wiki_page_meta).to receive(:id).and_return(wiki_page_meta.canonical_slug)
|
||||
end
|
||||
|
||||
subject { described_class.note_wiki_page_email(recipient.id, note.id) }
|
||||
|
||||
it_behaves_like 'a discussion note email', :discussion_note_on_wiki_page
|
||||
it_behaves_like 'a discussion note email', :discussion_note_on_wiki_page do
|
||||
let(:noteable_url_args) { { id: wiki_page_meta.canonical_slug } }
|
||||
end
|
||||
|
||||
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
|
||||
let(:model) { wiki_page_meta }
|
||||
end
|
||||
|
|
@ -842,7 +851,7 @@ RSpec.describe Notify, feature_category: :code_review_workflow do
|
|||
it_behaves_like 'appearance header and footer not enabled'
|
||||
|
||||
it 'has the correct subject' do
|
||||
is_expected.to have_subject('Re: a-known-name | Page 1 (slug)')
|
||||
is_expected.to have_subject("Re: a-known-name | #{wiki_page_meta.title} (slug)")
|
||||
end
|
||||
|
||||
it 'contains a link to the wiki page note' do
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe MigrateAnonymousSearchesFlagToApplicationSettings, feature_category: :global_search do
|
||||
let!(:application_setting) { table(:application_settings).create! }
|
||||
|
||||
describe '#down' do
|
||||
let(:migration) { described_class.new }
|
||||
|
||||
context 'when search settings is already set' do
|
||||
it 'removes the global search settings' do
|
||||
migration.up
|
||||
expected_search = application_setting.reload.search
|
||||
expected_search.delete('anonymous_searches_allowed')
|
||||
expect { migration.down }.to change { application_setting.reload.search }.to(expected_search)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#up' do
|
||||
context 'when ff is enabled' do
|
||||
it 'migrates search from the feature flags in the application_settings successfully' do
|
||||
search_settings = application_setting.reload.search
|
||||
expected_settings = {
|
||||
'anonymous_searches_allowed' => true
|
||||
}
|
||||
|
||||
expected_search = search_settings.merge(expected_settings)
|
||||
|
||||
# Use a manually created migration instance with stubbed method
|
||||
migration = described_class.new
|
||||
allow(migration).to receive(:feature_flag_enabled?).with('allow_anonymous_searches').and_return(true)
|
||||
|
||||
# Run the migration manually instead of using migrate!
|
||||
expect { migration.up }.to change {
|
||||
application_setting.reload.search
|
||||
}.to eq(expected_search)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the feature flag is disabled' do
|
||||
it 'migrates the feature flag to the application_settings successfully' do
|
||||
search_settings = application_setting.reload.search
|
||||
expected_settings = {
|
||||
'anonymous_searches_allowed' => false
|
||||
}
|
||||
|
||||
expected_search = search_settings.merge(expected_settings)
|
||||
|
||||
# Use a manually created migration instance with stubbed method
|
||||
migration = described_class.new
|
||||
allow(migration).to receive(:feature_flag_enabled?).with('allow_anonymous_searches').and_return(false)
|
||||
|
||||
# Run the migration manually instead of using migrate!
|
||||
expect { migration.up }.to change {
|
||||
application_setting.reload.search
|
||||
}.to eq(expected_search)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -54,7 +54,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
|
|||
project_api_limit: 400,
|
||||
project_invited_groups_api_limit: 60,
|
||||
projects_api_limit: 2000,
|
||||
instance_token_prefix: 'gl',
|
||||
instance_token_prefix: '',
|
||||
use_clickhouse_for_analytics: false,
|
||||
user_contributed_projects_api_limit: 100,
|
||||
user_projects_api_limit: 300,
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'GitLab monkey-patches to AttrEncrypted', feature_category: :shared do
|
||||
# See https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23306
|
||||
describe '#attribute_instance_methods_as_symbols_available?' do
|
||||
let(:klass) do
|
||||
Class.new(ActiveRecord::Base) do
|
||||
# We need some sort of table to work on
|
||||
self.table_name = 'projects'
|
||||
|
||||
attr_encrypted :foo
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(klass.__send__(:attribute_instance_methods_as_symbols_available?)).to be_falsy
|
||||
end
|
||||
|
||||
it 'does not define virtual attributes' do
|
||||
instance = klass.new
|
||||
|
||||
aggregate_failures do
|
||||
%w[
|
||||
encrypted_foo encrypted_foo=
|
||||
encrypted_foo_iv encrypted_foo_iv=
|
||||
encrypted_foo_salt encrypted_foo_salt=
|
||||
].each do |method_name|
|
||||
expect(instance).not_to respond_to(method_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls attr_changed? method with kwargs' do
|
||||
obj = klass.new
|
||||
|
||||
expect(obj.foo_changed?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attr_encrypted_attributes' do
|
||||
let(:class_with_attr_encrypted) do
|
||||
Class.new(ActiveRecord::Base) do
|
||||
self.table_name = 'projects'
|
||||
|
||||
attr_accessor :encrypted_foo
|
||||
attr_accessor :encrypted_foo_iv
|
||||
|
||||
attr_encrypted :foo, key: 'This is a key that is 256 bits!!'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not share state with other instances' do
|
||||
instance = class_with_attr_encrypted.new
|
||||
instance.foo = 'bar'
|
||||
|
||||
another_instance = class_with_attr_encrypted.new
|
||||
|
||||
expect(instance.attr_encrypted_attributes[:foo][:operation]).to eq(:encrypting)
|
||||
expect(another_instance.attr_encrypted_attributes[:foo][:operation]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -172,9 +172,19 @@ RSpec.describe BulkInsertSafe, feature_category: :database do
|
|||
|
||||
bulk_insert_item_class.bulk_insert!(items)
|
||||
|
||||
attribute_names = bulk_insert_item_class.attribute_names - %w[id created_at updated_at]
|
||||
expect(bulk_insert_item_class.last(items.size).pluck(*attribute_names)).to eq(
|
||||
items.pluck(*attribute_names))
|
||||
encrypted_attribute_names = bulk_insert_item_class.attr_encrypted_encrypted_attributes.keys.map(&:to_s)
|
||||
attribute_names = bulk_insert_item_class.attribute_names - %w[id created_at updated_at] - encrypted_attribute_names
|
||||
|
||||
decrypted_values = bulk_insert_item_class.last(items.size).map do |record|
|
||||
encrypted_attribute_names.index_with { |attr| record.public_send(attr) }
|
||||
end
|
||||
|
||||
expected_values = items.map do |record|
|
||||
encrypted_attribute_names.index_with { |attr| record.public_send(attr) }
|
||||
end
|
||||
|
||||
expect(bulk_insert_item_class.last(items.size).pluck(*attribute_names)).to eq(items.pluck(*attribute_names))
|
||||
expect(decrypted_values).to eq(expected_values)
|
||||
end
|
||||
|
||||
it 'rolls back the transaction when any item is invalid' do
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ RSpec.describe Gitlab::EncryptedAttribute, feature_category: :shared do
|
|||
describe key_method do
|
||||
context 'when encrypting' do
|
||||
before do
|
||||
record.attr_encrypted_attributes[:token][:operation] = :encrypting
|
||||
record.attr_encrypted_encrypted_attributes[:token][:operation] = :encrypting
|
||||
end
|
||||
|
||||
it 'returns the encryption key secret' do
|
||||
|
|
@ -35,7 +35,7 @@ RSpec.describe Gitlab::EncryptedAttribute, feature_category: :shared do
|
|||
|
||||
context 'when decrypting' do
|
||||
before do
|
||||
record.attr_encrypted_attributes[:token][:operation] = :decrypting
|
||||
record.attr_encrypted_encrypted_attributes[:token][:operation] = :decrypting
|
||||
end
|
||||
|
||||
it 'returns the encryption key secret' do
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ RSpec.describe Gitlab::SensitiveSerializableHash, feature_category: :shared do
|
|||
context "for #{klass.name}\##{attribute_name}" do
|
||||
let(:attributes) { [attribute_name, "encrypted_#{attribute_name}", "encrypted_#{attribute_name}_iv"] }
|
||||
|
||||
it 'has a attr_encrypted_attributes field' do
|
||||
expect(klass.attr_encrypted_attributes).to include(attribute_name.to_sym)
|
||||
it 'has a attr_encrypted_encrypted_attributes field' do
|
||||
expect(klass.attr_encrypted_encrypted_attributes).to include(attribute_name.to_sym)
|
||||
end
|
||||
|
||||
it 'does not include the attribute in serializable_hash', :aggregate_failures do
|
||||
|
|
|
|||
|
|
@ -1448,7 +1448,7 @@ RSpec.describe Integration, feature_category: :integrations do
|
|||
|
||||
options = record.send(:evaluated_attr_encrypted_options_for, :properties)
|
||||
.merge(iv: hash['encrypted_properties_iv'])
|
||||
decrypted = described_class.attr_decrypt(
|
||||
decrypted = described_class.attr_encrypted_decrypt(
|
||||
:properties,
|
||||
hash['encrypted_properties'],
|
||||
options
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ RSpec.describe Integrations::IssueTrackerData, feature_category: :integrations d
|
|||
it_behaves_like Integrations::BaseDataFields
|
||||
|
||||
describe 'encrypted attributes' do
|
||||
subject { described_class.attr_encrypted_attributes.keys }
|
||||
subject { described_class.attr_encrypted_encrypted_attributes.keys }
|
||||
|
||||
it { is_expected.to contain_exactly(:issues_url, :new_issue_url, :project_url) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RSpec.describe Integrations::JiraTrackerData, feature_category: :integrations do
|
|||
end
|
||||
|
||||
describe 'encrypted attributes' do
|
||||
subject { described_class.attr_encrypted_attributes.keys }
|
||||
subject { described_class.attr_encrypted_encrypted_attributes.keys }
|
||||
|
||||
it { is_expected.to contain_exactly(:api_url, :password, :url, :username) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RSpec.describe Integrations::ZentaoTrackerData, feature_category: :integrations
|
|||
end
|
||||
|
||||
describe 'encrypted attributes' do
|
||||
subject { described_class.attr_encrypted_attributes.keys }
|
||||
subject { described_class.attr_encrypted_encrypted_attributes.keys }
|
||||
|
||||
it { is_expected.to contain_exactly(:url, :api_url, :zentao_product_xid, :api_token) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,14 +31,14 @@ RSpec.describe Operations::FeatureFlagsClient do
|
|||
end
|
||||
|
||||
context 'with custom instance prefix' do
|
||||
let_it_be(:instance_prefix) { 'instance-prefix-' }
|
||||
let_it_be(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
end
|
||||
|
||||
it 'starts with instance prefix' do
|
||||
expect(subject.token).to match(/instance-prefix-ffct-[A-Za-z0-9_-]{20}/)
|
||||
expect(subject.token).to match(/instanceprefixglffct-[A-Za-z0-9_-]{20}/)
|
||||
end
|
||||
|
||||
context 'with feature flag custom_prefix_for_all_token_types disabled' do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe SentNotification, :request_store, feature_category: :shared do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :repository, group: group) }
|
||||
|
||||
describe 'validation' do
|
||||
describe 'note validity' do
|
||||
|
|
@ -46,6 +47,63 @@ RSpec.describe SentNotification, :request_store, feature_category: :shared do
|
|||
it { is_expected.to belong_to(:issue_email_participant) }
|
||||
end
|
||||
|
||||
describe 'callbacks' do
|
||||
describe '#ensure_sharding_key' do
|
||||
let(:additional_args) { {} }
|
||||
|
||||
subject(:notification_namespace_id) do
|
||||
record = described_class.new(noteable: noteable, **additional_args)
|
||||
record.valid?
|
||||
|
||||
record.namespace_id
|
||||
end
|
||||
|
||||
context 'when noteable is a DesignManagement::Design' do
|
||||
let(:noteable) { create(:design, issue: create(:issue, project: project)).reload }
|
||||
|
||||
# Using project.namespace_id here instead of project.project_namespace_id as that's the value the trigger
|
||||
# sets for design_management_designs records in the DB. That shouls also be a valid sharding key.
|
||||
it { is_expected.to eq(project.namespace_id) }
|
||||
end
|
||||
|
||||
context 'when noteable is a Issue' do
|
||||
let(:noteable) { create(:issue, project: project) }
|
||||
|
||||
it { is_expected.to eq(noteable.namespace_id) }
|
||||
end
|
||||
|
||||
context 'when noteable is a MergeRequest' do
|
||||
let(:noteable) { create(:merge_request, source_project: project) }
|
||||
|
||||
it { is_expected.to eq(project.project_namespace_id) }
|
||||
end
|
||||
|
||||
context 'when noteable is a ProjectSnippet' do
|
||||
let(:noteable) { create(:project_snippet, project: project) }
|
||||
|
||||
it { is_expected.to eq(project.project_namespace_id) }
|
||||
end
|
||||
|
||||
context 'when noteable is a Commit' do
|
||||
let(:commit) { create(:commit, project: project) }
|
||||
let(:noteable) { nil }
|
||||
let(:additional_args) { { project: project, noteable_type: commit.class.name, commit_id: commit.id } }
|
||||
|
||||
it { is_expected.to eq(project.project_namespace_id) }
|
||||
end
|
||||
|
||||
context 'when noteable type is not supported' do
|
||||
let(:noteable) { create(:personal_snippet) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
notification_namespace_id
|
||||
end.to raise_error(SentNotification::INVALID_NOTEABLE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a successful sent notification' do
|
||||
it 'creates a new SentNotification' do
|
||||
expect { subject }.to change { described_class.count }.by(1)
|
||||
|
|
@ -141,10 +199,6 @@ RSpec.describe SentNotification, :request_store, feature_category: :shared do
|
|||
let(:noteable) { project.commit }
|
||||
end
|
||||
|
||||
it_behaves_like 'a non-unsubscribable notification', 'personal snippet' do
|
||||
let(:noteable) { create(:personal_snippet) }
|
||||
end
|
||||
|
||||
it_behaves_like 'a non-unsubscribable notification', 'project snippet' do
|
||||
let(:noteable) { create(:project_snippet, project: project) }
|
||||
end
|
||||
|
|
@ -180,10 +234,6 @@ RSpec.describe SentNotification, :request_store, feature_category: :shared do
|
|||
let(:noteable) { project.commit }
|
||||
end
|
||||
|
||||
it_behaves_like 'a non-commit notification', 'personal snippet' do
|
||||
let(:noteable) { create(:personal_snippet) }
|
||||
end
|
||||
|
||||
it_behaves_like 'a non-commit notification', 'project snippet' do
|
||||
let(:noteable) { create(:project_snippet, project: project) }
|
||||
end
|
||||
|
|
@ -219,10 +269,6 @@ RSpec.describe SentNotification, :request_store, feature_category: :shared do
|
|||
let(:noteable) { project.commit }
|
||||
end
|
||||
|
||||
it_behaves_like 'a snippet notification', 'personal snippet' do
|
||||
let(:noteable) { create(:personal_snippet) }
|
||||
end
|
||||
|
||||
it_behaves_like 'a snippet notification', 'project snippet' do
|
||||
let(:noteable) { create(:project_snippet, project: project) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -88,10 +88,10 @@ RSpec.describe UserPreference, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
describe 'organization_groups_projects_display' do
|
||||
it 'is set to 0 by default' do
|
||||
it 'is set to 1 by default' do
|
||||
pref = described_class.new
|
||||
|
||||
expect(pref.organization_groups_projects_display).to eq('projects')
|
||||
expect(pref.organization_groups_projects_display).to eq('groups')
|
||||
end
|
||||
|
||||
it { is_expected.to define_enum_for(:organization_groups_projects_display).with_values(projects: 0, groups: 1) }
|
||||
|
|
|
|||
|
|
@ -2523,7 +2523,7 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
context 'with instance prefix configured' do
|
||||
let(:instance_prefix) { 'instance-prefix-' }
|
||||
let(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
|
|
@ -2532,7 +2532,7 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
it 'returns feed token with instance prefix' do
|
||||
user = create(:user)
|
||||
|
||||
expect(user.feed_token).to start_with("#{instance_prefix}ft-")
|
||||
expect(user.feed_token).to start_with("#{instance_prefix}glft-")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1733,8 +1733,8 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
|
|||
|
||||
context 'wiki page note', :deliver_mails_inline do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let(:wiki_page_meta) { create(:wiki_page_meta, :for_wiki_page, container: project) }
|
||||
let(:note) { create(:note, noteable: wiki_page_meta, project: project) }
|
||||
let_it_be(:wiki_page_meta) { create(:wiki_page_meta, :for_wiki_page, container: project) }
|
||||
let_it_be(:note) { create(:note, noteable: wiki_page_meta, project: project) }
|
||||
|
||||
before_all do
|
||||
build_team(project)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
- Security::PipelineExecutionPoliciesFinder
|
||||
- Security::PipelineExecutionSchedulePoliciesFinder
|
||||
- Security::VulnerabilityManagementPoliciesFinder
|
||||
- Security::AllPoliciesFinder
|
||||
- Security::SecurityPoliciesFinder
|
||||
- Security::SecurityProjectGroupFinder
|
||||
- SentryIssueFinder
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ end
|
|||
|
||||
RSpec.shared_examples 'contains instance prefix when enabled' do
|
||||
context 'with default instance prefix' do
|
||||
let_it_be(:instance_prefix) { 'instance-prefix-' }
|
||||
let_it_be(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
|
|
@ -91,7 +91,7 @@ RSpec.shared_examples 'contains instance prefix when enabled' do
|
|||
end
|
||||
|
||||
context 'with custom instance prefix' do
|
||||
let_it_be(:instance_prefix) { 'instance-prefix-' }
|
||||
let_it_be(:instance_prefix) { 'instanceprefix' }
|
||||
|
||||
before do
|
||||
stub_application_setting(instance_token_prefix: instance_prefix)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
RSpec.shared_examples 'encrypted attribute' do |attr, key_method|
|
||||
describe "encrypted attribute :#{attr}" do
|
||||
it 'uses correct key method' do
|
||||
expect(record.attr_encrypted_attributes[attr][:key]).to eq(key_method)
|
||||
expect(record.attr_encrypted_encrypted_attributes[attr][:key]).to eq(key_method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ RSpec.shared_examples 'a webhook' do |factory:|
|
|||
end
|
||||
|
||||
describe 'encrypted attributes' do
|
||||
subject { described_class.attr_encrypted_attributes.keys }
|
||||
subject { described_class.attr_encrypted_encrypted_attributes.keys }
|
||||
|
||||
it { is_expected.to contain_exactly(:token, :url, :url_variables, :custom_headers) }
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue