Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8f9b584cb1
commit
81240d1817
|
|
@ -30,7 +30,9 @@ linters:
|
|||
max_consecutive: 2
|
||||
|
||||
DocumentationLinks:
|
||||
enabled: true
|
||||
# Will be enabled again after offenses are resolved.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/419673
|
||||
enabled: false
|
||||
include:
|
||||
- 'app/views/**/*.haml'
|
||||
- 'ee/app/views/**/*.haml'
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ Gitlab/FeatureFlagWithoutActor:
|
|||
- 'lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb'
|
||||
- 'lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch.rb'
|
||||
- 'lib/gitlab/database/reindexing.rb'
|
||||
- 'lib/gitlab/experiment/rollout/feature.rb'
|
||||
- 'lib/gitlab/experiment_feature_rollout.rb'
|
||||
- 'lib/gitlab/git/diff.rb'
|
||||
- 'lib/gitlab/git/repository.rb'
|
||||
- 'lib/gitlab/git/user.rb'
|
||||
|
|
|
|||
|
|
@ -1088,7 +1088,7 @@ Gitlab/NamespacedClass:
|
|||
- 'lib/gitlab/environment_logger.rb'
|
||||
- 'lib/gitlab/exceptions_app.rb'
|
||||
- 'lib/gitlab/exclusive_lease.rb'
|
||||
- 'lib/gitlab/experiment/rollout/feature.rb'
|
||||
- 'lib/gitlab/experiment_feature_rollout.rb'
|
||||
- 'lib/gitlab/fake_application_settings.rb'
|
||||
- 'lib/gitlab/favicon.rb'
|
||||
- 'lib/gitlab/feature_categories.rb'
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default {
|
|||
return getIdFromGraphQLId(this.author.id);
|
||||
},
|
||||
showEditButton() {
|
||||
return this.note.userPermissions.resolveNote;
|
||||
return false;
|
||||
},
|
||||
editedAtClasses() {
|
||||
return this.showReplyButton ? 'gl-text-secondary gl-pl-3' : 'gl-text-secondary gl-pl-8';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#import "~/graphql_shared/fragments/author.fragment.graphql"
|
||||
#import "./abuse_report_note_permissions.fragment.graphql"
|
||||
|
||||
fragment AbuseReportNote on Note {
|
||||
fragment AbuseReportNote on AbuseReportNote {
|
||||
id
|
||||
body
|
||||
bodyHtml
|
||||
|
|
@ -16,9 +15,6 @@ fragment AbuseReportNote on Note {
|
|||
...Author
|
||||
webPath
|
||||
}
|
||||
userPermissions {
|
||||
...AbuseReportNotePermissions
|
||||
}
|
||||
discussion {
|
||||
id
|
||||
notes {
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
fragment AbuseReportNotePermissions on NotePermissions {
|
||||
resolveNote
|
||||
}
|
||||
|
|
@ -1,16 +1,9 @@
|
|||
#import "./abuse_report_note.fragment.graphql"
|
||||
|
||||
mutation createAbuseReportNote($input: CreateNoteInput!) {
|
||||
createNote(input: $input) {
|
||||
note {
|
||||
id
|
||||
discussion {
|
||||
id
|
||||
notes {
|
||||
nodes {
|
||||
...AbuseReportNote
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
#import "./abuse_report_note.fragment.graphql"
|
||||
|
||||
mutation updateAbuseReportNote($input: UpdateNoteInput!) {
|
||||
updateNote(input: $input) {
|
||||
note {
|
||||
...AbuseReportNote
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ export default {
|
|||
.post(`${this.link}.json`)
|
||||
.then(() => {
|
||||
this.isLoading = false;
|
||||
this.isDisabled = false;
|
||||
|
||||
this.$emit('pipelineActionRequestComplete');
|
||||
})
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,77 +7,93 @@ import {
|
|||
INLINE_DIFF_LINES_KEY,
|
||||
DIFF_COMPARE_BASE_VERSION_INDEX,
|
||||
DIFF_COMPARE_HEAD_VERSION_INDEX,
|
||||
} from '../../constants';
|
||||
} from '~/diffs/constants';
|
||||
import { useNotes } from '~/notes/store/legacy_notes';
|
||||
import { useMrNotes } from '~/mr_notes/store/legacy_mr_notes';
|
||||
import { computeSuggestionCommitMessage } from '../../utils/suggestions';
|
||||
import { parallelizeDiffLines } from '../../store/utils';
|
||||
|
||||
export const isParallelView = (state) => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
|
||||
export function isParallelView() {
|
||||
return this.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
|
||||
}
|
||||
|
||||
export const isInlineView = (state) => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
|
||||
export function isInlineView() {
|
||||
return this.diffViewType === INLINE_DIFF_VIEW_TYPE;
|
||||
}
|
||||
|
||||
export const whichCollapsedTypes = (state) => {
|
||||
const automatic = state.diffFiles.some((file) => file.viewer?.automaticallyCollapsed);
|
||||
const manual = state.diffFiles.some((file) => file.viewer?.manuallyCollapsed);
|
||||
export function whichCollapsedTypes() {
|
||||
const automatic = this.diffFiles.some((file) => file.viewer?.automaticallyCollapsed);
|
||||
const manual = this.diffFiles.some((file) => file.viewer?.manuallyCollapsed);
|
||||
|
||||
return {
|
||||
any: automatic || manual,
|
||||
automatic,
|
||||
manual,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const commitId = (state) => (state.commit && state.commit.id ? state.commit.id : null);
|
||||
export function commitId() {
|
||||
return this.commit && this.commit.id ? this.commit.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the diff has all discussions expanded
|
||||
* @param {Object} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasAllExpandedDiscussions = (state, getters) => (diff) => {
|
||||
const discussions = getters.getDiffFileDiscussions(diff);
|
||||
export function diffHasAllExpandedDiscussions() {
|
||||
return (diff) => {
|
||||
const discussions = this.getDiffFileDiscussions(diff);
|
||||
|
||||
return (
|
||||
(discussions && discussions.length && discussions.every((discussion) => discussion.expanded)) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
return (
|
||||
(discussions &&
|
||||
discussions.length &&
|
||||
discussions.every((discussion) => discussion.expanded)) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the diff has all discussions collapsed
|
||||
* @param {Object} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasAllCollapsedDiscussions = (state, getters) => (diff) => {
|
||||
const discussions = getters.getDiffFileDiscussions(diff);
|
||||
export function diffHasAllCollapsedDiscussions() {
|
||||
return (diff) => {
|
||||
const discussions = this.getDiffFileDiscussions(diff);
|
||||
|
||||
return (
|
||||
(discussions &&
|
||||
discussions.length &&
|
||||
discussions.every((discussion) => !discussion.expanded)) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
return (
|
||||
(discussions &&
|
||||
discussions.length &&
|
||||
discussions.every((discussion) => !discussion.expanded)) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the diff has any open discussions
|
||||
* @param {Object} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasExpandedDiscussions = () => (diff) => {
|
||||
const diffLineDiscussionsExpanded = diff[INLINE_DIFF_LINES_KEY].filter(
|
||||
(l) => l.discussions.length >= 1,
|
||||
).some((l) => l.discussionsExpanded);
|
||||
const diffFileDiscussionsExpanded = diff.discussions?.some((d) => d.expandedOnDiff);
|
||||
export function diffHasExpandedDiscussions() {
|
||||
return (diff) => {
|
||||
const diffLineDiscussionsExpanded = diff[INLINE_DIFF_LINES_KEY].filter(
|
||||
(l) => l.discussions.length >= 1,
|
||||
).some((l) => l.discussionsExpanded);
|
||||
const diffFileDiscussionsExpanded = diff.discussions?.some((d) => d.expandedOnDiff);
|
||||
|
||||
return diffFileDiscussionsExpanded || diffLineDiscussionsExpanded;
|
||||
};
|
||||
return diffFileDiscussionsExpanded || diffLineDiscussionsExpanded;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if every diff has every discussion open
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const allDiffDiscussionsExpanded = (state) => {
|
||||
return state.diffFiles.every((diff) => {
|
||||
export function allDiffDiscussionsExpanded() {
|
||||
return this.diffFiles.every((diff) => {
|
||||
const highlightedLines = diff[INLINE_DIFF_LINES_KEY];
|
||||
if (highlightedLines.length) {
|
||||
return highlightedLines
|
||||
|
|
@ -89,45 +105,51 @@ export const allDiffDiscussionsExpanded = (state) => {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the diff has any discussion
|
||||
* @param {Boolean} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasDiscussions = () => (diff) => {
|
||||
return (
|
||||
diff.discussions?.length >= 1 ||
|
||||
diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1)
|
||||
);
|
||||
};
|
||||
export function diffHasDiscussions() {
|
||||
return (diff) => {
|
||||
return (
|
||||
diff.discussions?.length >= 1 ||
|
||||
diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the discussions of the given diff
|
||||
* @param {Object} diff
|
||||
* @returns {Array}
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) => (diff) =>
|
||||
rootGetters.discussions.filter(
|
||||
(discussion) => discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
|
||||
) || [];
|
||||
export function getDiffFileDiscussions() {
|
||||
return (diff) =>
|
||||
useNotes().discussions.filter(
|
||||
(discussion) =>
|
||||
discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
|
||||
) || [];
|
||||
}
|
||||
|
||||
export const getDiffFileByHash = (state) => (fileHash) =>
|
||||
state.diffFiles.find((file) => file.file_hash === fileHash);
|
||||
export function getDiffFileByHash() {
|
||||
return (fileHash) => this.diffFiles.find((file) => file.file_hash === fileHash);
|
||||
}
|
||||
|
||||
export function isTreePathLoaded(state) {
|
||||
export function isTreePathLoaded() {
|
||||
return (path) => {
|
||||
return Boolean(state.treeEntries[path]?.diffLoaded);
|
||||
return Boolean(this.treeEntries[path]?.diffLoaded);
|
||||
};
|
||||
}
|
||||
|
||||
export const flatBlobsList = (state) =>
|
||||
Object.values(state.treeEntries).filter((f) => f.type === 'blob');
|
||||
export function flatBlobsList() {
|
||||
return Object.values(this.treeEntries).filter((f) => f.type === 'blob');
|
||||
}
|
||||
|
||||
export const allBlobs = (state, getters) =>
|
||||
getters.flatBlobsList.reduce((acc, file) => {
|
||||
export function allBlobs() {
|
||||
return this.flatBlobsList.reduce((acc, file) => {
|
||||
const { parentPath } = file;
|
||||
|
||||
if (parentPath && !acc.some((f) => f.path === parentPath)) {
|
||||
|
|
@ -142,9 +164,11 @@ export const allBlobs = (state, getters) =>
|
|||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const getCommentFormForDiffFile = (state) => (fileHash) =>
|
||||
state.commentForms.find((form) => form.fileHash === fileHash);
|
||||
export function getCommentFormForDiffFile() {
|
||||
return (fileHash) => this.commentForms.find((form) => form.fileHash === fileHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test coverage hits for a specific line of a given file
|
||||
|
|
@ -152,114 +176,131 @@ export const getCommentFormForDiffFile = (state) => (fileHash) =>
|
|||
* @param {number} line
|
||||
* @returns {number}
|
||||
*/
|
||||
export const fileLineCoverage = (state) => (file, line) => {
|
||||
if (!state.coverageFiles.files) return {};
|
||||
const fileCoverage = state.coverageFiles.files[file];
|
||||
if (!fileCoverage) return {};
|
||||
const lineCoverage = fileCoverage[String(line)];
|
||||
export function fileLineCoverage() {
|
||||
return (file, line) => {
|
||||
if (!this.coverageFiles.files) return {};
|
||||
const fileCoverage = this.coverageFiles.files[file];
|
||||
if (!fileCoverage) return {};
|
||||
const lineCoverage = fileCoverage[String(line)];
|
||||
|
||||
if (lineCoverage === 0) {
|
||||
return { text: __('No test coverage'), class: 'no-coverage' };
|
||||
}
|
||||
if (lineCoverage >= 0) {
|
||||
return {
|
||||
text: n__('Test coverage: %d hit', 'Test coverage: %d hits', lineCoverage),
|
||||
class: 'coverage',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
if (lineCoverage === 0) {
|
||||
return { text: __('No test coverage'), class: 'no-coverage' };
|
||||
}
|
||||
if (lineCoverage >= 0) {
|
||||
return {
|
||||
text: n__('Test coverage: %d hit', 'Test coverage: %d hits', lineCoverage),
|
||||
class: 'coverage',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of a currently selected diff in diffFiles
|
||||
* @returns {number}
|
||||
*/
|
||||
export const currentDiffIndex = (state) =>
|
||||
Math.max(
|
||||
export function currentDiffIndex() {
|
||||
return Math.max(
|
||||
0,
|
||||
flatBlobsList(state).findIndex((diff) => diff.fileHash === state.currentDiffFileId),
|
||||
this.flatBlobsList.findIndex((diff) => diff.fileHash === this.currentDiffFileId),
|
||||
);
|
||||
}
|
||||
|
||||
export const diffLines = (state) => (file) => {
|
||||
return parallelizeDiffLines(
|
||||
file.highlighted_diff_lines || [],
|
||||
state.diffViewType === INLINE_DIFF_VIEW_TYPE,
|
||||
);
|
||||
};
|
||||
export function diffLines() {
|
||||
return (file) => {
|
||||
return parallelizeDiffLines(
|
||||
file.highlighted_diff_lines || [],
|
||||
this.diffViewType === INLINE_DIFF_VIEW_TYPE,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function suggestionCommitMessage(state, _, rootState) {
|
||||
return (values = {}) =>
|
||||
computeSuggestionCommitMessage({
|
||||
message: state.defaultSuggestionCommitMessage,
|
||||
export function suggestionCommitMessage() {
|
||||
return (values = {}) => {
|
||||
const { mrMetadata } = useMrNotes().page;
|
||||
return computeSuggestionCommitMessage({
|
||||
message: this.defaultSuggestionCommitMessage,
|
||||
values: {
|
||||
branch_name: rootState.page.mrMetadata.branch_name,
|
||||
project_path: rootState.page.mrMetadata.project_path,
|
||||
project_name: rootState.page.mrMetadata.project_name,
|
||||
username: rootState.page.mrMetadata.username,
|
||||
user_full_name: rootState.page.mrMetadata.user_full_name,
|
||||
branch_name: mrMetadata.branch_name,
|
||||
project_path: mrMetadata.project_path,
|
||||
project_name: mrMetadata.project_name,
|
||||
username: mrMetadata.username,
|
||||
user_full_name: mrMetadata.user_full_name,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const isVirtualScrollingEnabled = (state) => {
|
||||
if (state.disableVirtualScroller || getParameterValues('virtual_scrolling')[0] === 'false') {
|
||||
export function isVirtualScrollingEnabled() {
|
||||
if (this.disableVirtualScroller || getParameterValues('virtual_scrolling')[0] === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !state.viewDiffsFileByFile;
|
||||
};
|
||||
return !this.viewDiffsFileByFile;
|
||||
}
|
||||
|
||||
export const isBatchLoading = (state) => state.batchLoadingState === 'loading';
|
||||
export const isBatchLoadingError = (state) => state.batchLoadingState === 'error';
|
||||
export function isBatchLoading() {
|
||||
return this.batchLoadingState === 'loading';
|
||||
}
|
||||
export function isBatchLoadingError() {
|
||||
return this.batchLoadingState === 'error';
|
||||
}
|
||||
|
||||
export const diffFiles = (state, getters) => {
|
||||
const { linkedFile } = getters;
|
||||
if (linkedFile) {
|
||||
const diffs = state.diffFiles.slice(0);
|
||||
diffs.splice(diffs.indexOf(linkedFile), 1);
|
||||
return [linkedFile, ...diffs];
|
||||
export function diffFilesFiltered() {
|
||||
const { linkedFile: file } = this;
|
||||
if (file) {
|
||||
const diffs = this.diffFiles.slice(0);
|
||||
diffs.splice(diffs.indexOf(file), 1);
|
||||
return [file, ...diffs];
|
||||
}
|
||||
return state.diffFiles;
|
||||
};
|
||||
return this.diffFiles;
|
||||
}
|
||||
|
||||
export const linkedFile = (state) => {
|
||||
if (!state.linkedFileHash) return null;
|
||||
return state.diffFiles.find((file) => file.file_hash === state.linkedFileHash);
|
||||
};
|
||||
export function linkedFile() {
|
||||
if (!this.linkedFileHash) return null;
|
||||
return this.diffFiles.find((file) => file.file_hash === this.linkedFileHash);
|
||||
}
|
||||
|
||||
export const selectedTargetIndex = (state) =>
|
||||
state.startVersion?.version_index || DIFF_COMPARE_BASE_VERSION_INDEX;
|
||||
export function selectedTargetIndex() {
|
||||
return this.startVersion?.version_index || DIFF_COMPARE_BASE_VERSION_INDEX;
|
||||
}
|
||||
|
||||
export const selectedSourceIndex = (state) => state.mergeRequestDiff.version_index;
|
||||
export function selectedSourceIndex() {
|
||||
if (!this.mergeRequestDiff) return undefined;
|
||||
return this.mergeRequestDiff.version_index;
|
||||
}
|
||||
|
||||
export const selectedContextCommitsDiffs = (state) =>
|
||||
state.contextCommitsDiff && state.contextCommitsDiff.showing_context_commits_diff;
|
||||
export function selectedContextCommitsDiff() {
|
||||
return this.contextCommitsDiff && this.contextCommitsDiff.showing_context_commits_diff;
|
||||
}
|
||||
|
||||
export const diffCompareDropdownTargetVersions = (state, getters) => {
|
||||
export function diffCompareDropdownTargetVersions() {
|
||||
if (!this.mergeRequestDiff) return [];
|
||||
// startVersion only exists if the user has selected a version other
|
||||
// than "base" so if startVersion is null then base must be selected
|
||||
|
||||
const diffHeadParam = getParameterByName('diff_head');
|
||||
const diffHead = parseBoolean(diffHeadParam) || !diffHeadParam;
|
||||
const isBaseSelected = !state.startVersion;
|
||||
const isHeadSelected = !state.startVersion && diffHead;
|
||||
const isBaseSelected = !this.startVersion;
|
||||
const isHeadSelected = !this.startVersion && diffHead;
|
||||
let baseVersion = null;
|
||||
|
||||
if (!state.mergeRequestDiff.head_version_path) {
|
||||
if (!this.mergeRequestDiff.head_version_path) {
|
||||
baseVersion = {
|
||||
versionName: state.targetBranchName,
|
||||
versionName: this.targetBranchName,
|
||||
version_index: DIFF_COMPARE_BASE_VERSION_INDEX,
|
||||
href: state.mergeRequestDiff.base_version_path,
|
||||
href: this.mergeRequestDiff.base_version_path,
|
||||
isBase: true,
|
||||
selected: isBaseSelected,
|
||||
};
|
||||
}
|
||||
|
||||
const headVersion = {
|
||||
versionName: state.targetBranchName,
|
||||
versionName: this.targetBranchName,
|
||||
version_index: DIFF_COMPARE_HEAD_VERSION_INDEX,
|
||||
href: state.mergeRequestDiff.head_version_path,
|
||||
href: this.mergeRequestDiff.head_version_path,
|
||||
isHead: true,
|
||||
selected: isHeadSelected,
|
||||
};
|
||||
|
|
@ -268,21 +309,21 @@ export const diffCompareDropdownTargetVersions = (state, getters) => {
|
|||
return {
|
||||
href: v.compare_path,
|
||||
versionName: sprintf(__(`version %{versionIndex}`), { versionIndex: v.version_index }),
|
||||
selected: v.version_index === getters.selectedTargetIndex,
|
||||
selected: v.version_index === this.selectedTargetIndex,
|
||||
...v,
|
||||
};
|
||||
};
|
||||
|
||||
return [
|
||||
...state.mergeRequestDiffs.slice(1).map(formatVersion),
|
||||
...this.mergeRequestDiffs.slice(1).map(formatVersion),
|
||||
baseVersion,
|
||||
state.mergeRequestDiff.head_version_path && headVersion,
|
||||
this.mergeRequestDiff.head_version_path && headVersion,
|
||||
].filter((a) => a);
|
||||
};
|
||||
}
|
||||
|
||||
export const diffCompareDropdownSourceVersions = (state, getters) => {
|
||||
export function diffCompareDropdownSourceVersions() {
|
||||
// Appended properties here are to make the compare_dropdown_layout easier to reason about
|
||||
const versions = state.mergeRequestDiffs.map((v, i) => {
|
||||
const versions = this.mergeRequestDiffs.map((v, i) => {
|
||||
const isLatestVersion = i === 0;
|
||||
|
||||
return {
|
||||
|
|
@ -293,20 +334,19 @@ export const diffCompareDropdownSourceVersions = (state, getters) => {
|
|||
versionName: isLatestVersion
|
||||
? __('latest version')
|
||||
: sprintf(__(`version %{versionIndex}`), { versionIndex: v.version_index }),
|
||||
selected:
|
||||
v.version_index === getters.selectedSourceIndex && !getters.selectedContextCommitsDiffs,
|
||||
selected: v.version_index === this.selectedSourceIndex && !this.selectedContextCommitsDiffs,
|
||||
};
|
||||
});
|
||||
|
||||
const { contextCommitsDiff } = state;
|
||||
const { contextCommitsDiff } = this;
|
||||
if (contextCommitsDiff) {
|
||||
versions.push({
|
||||
href: contextCommitsDiff.diffs_path,
|
||||
commitsText: n__(`%d commit`, `%d commits`, contextCommitsDiff.commits_count),
|
||||
versionName: __('previously merged commits'),
|
||||
selected: getters.selectedContextCommitsDiffs,
|
||||
addDivider: state.mergeRequestDiffs.length > 0,
|
||||
selected: this.selectedContextCommitsDiffs,
|
||||
addDivider: this.mergeRequestDiffs.length > 0,
|
||||
});
|
||||
}
|
||||
return versions;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export const useLegacyDiffs = defineStore('legacyDiffs', {
|
|||
mrReviews: {},
|
||||
latestDiff: true,
|
||||
disableVirtualScroller: false,
|
||||
pinnedFileHash: null,
|
||||
linkedFileHash: null,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,8 @@ import {
|
|||
markTreeEntriesLoaded,
|
||||
} from '../../store/utils';
|
||||
|
||||
function updateDiffFilesInState(state, files) {
|
||||
return Object.assign(state, { diffFiles: files });
|
||||
}
|
||||
|
||||
export default {
|
||||
[types.SET_BASE_CONFIG](state, options) {
|
||||
[types.SET_BASE_CONFIG](options) {
|
||||
const {
|
||||
endpoint,
|
||||
endpointMetadata,
|
||||
|
|
@ -40,7 +36,7 @@ export default {
|
|||
diffViewType,
|
||||
perPage,
|
||||
} = options;
|
||||
Object.assign(state, {
|
||||
Object.assign(this, {
|
||||
endpoint,
|
||||
endpointMetadata,
|
||||
endpointBatch,
|
||||
|
|
@ -58,79 +54,79 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
[types.SET_LOADING](state, isLoading) {
|
||||
Object.assign(state, { isLoading });
|
||||
[types.SET_LOADING](isLoading) {
|
||||
Object.assign(this, { isLoading });
|
||||
},
|
||||
|
||||
[types.SET_BATCH_LOADING_STATE](state, batchLoadingState) {
|
||||
Object.assign(state, { batchLoadingState });
|
||||
[types.SET_BATCH_LOADING_STATE](batchLoadingState) {
|
||||
Object.assign(this, { batchLoadingState });
|
||||
},
|
||||
|
||||
[types.SET_RETRIEVING_BATCHES](state, retrievingBatches) {
|
||||
Object.assign(state, { retrievingBatches });
|
||||
[types.SET_RETRIEVING_BATCHES](retrievingBatches) {
|
||||
Object.assign(this, { retrievingBatches });
|
||||
},
|
||||
|
||||
[types.SET_DIFF_FILES](state, files) {
|
||||
updateDiffFilesInState(state, files);
|
||||
[types.SET_DIFF_FILES](files) {
|
||||
return Object.assign(this, { diffFiles: files });
|
||||
},
|
||||
|
||||
[types.SET_DIFF_METADATA](state, data) {
|
||||
Object.assign(state, {
|
||||
[types.SET_DIFF_METADATA](data) {
|
||||
Object.assign(this, {
|
||||
...convertObjectPropsToCamelCase(data),
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_DIFF_DATA_BATCH](state, { diff_files: diffFiles, updatePosition = true }) {
|
||||
Object.assign(state, {
|
||||
[types.SET_DIFF_DATA_BATCH]({ diff_files: diffFiles, updatePosition = true }) {
|
||||
Object.assign(this, {
|
||||
diffFiles: prepareDiffData({
|
||||
diff: { diff_files: diffFiles },
|
||||
priorFiles: state.diffFiles,
|
||||
priorFiles: this.diffFiles,
|
||||
// when a linked file is added to diffs its position may be incorrect since it's loaded out of order
|
||||
// we need to ensure when we load it in batched request it updates it position
|
||||
updatePosition,
|
||||
}),
|
||||
treeEntries: markTreeEntriesLoaded({
|
||||
priorEntries: state.treeEntries,
|
||||
priorEntries: this.treeEntries,
|
||||
loadedFiles: diffFiles,
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_DIFF_TREE_ENTRY](state, diffFile) {
|
||||
Object.assign(state, {
|
||||
[types.SET_DIFF_TREE_ENTRY](diffFile) {
|
||||
Object.assign(this, {
|
||||
treeEntries: markTreeEntriesLoaded({
|
||||
priorEntries: state.treeEntries,
|
||||
priorEntries: this.treeEntries,
|
||||
loadedFiles: [diffFile],
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_COVERAGE_DATA](state, coverageFiles) {
|
||||
Object.assign(state, { coverageFiles, coverageLoaded: true });
|
||||
[types.SET_COVERAGE_DATA](coverageFiles) {
|
||||
Object.assign(this, { coverageFiles, coverageLoaded: true });
|
||||
},
|
||||
|
||||
[types.SET_MERGE_REQUEST_DIFFS](state, mergeRequestDiffs) {
|
||||
Object.assign(state, {
|
||||
[types.SET_MERGE_REQUEST_DIFFS](mergeRequestDiffs) {
|
||||
Object.assign(this, {
|
||||
mergeRequestDiffs,
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_DIFF_VIEW_TYPE](state, diffViewType) {
|
||||
Object.assign(state, { diffViewType });
|
||||
[types.SET_DIFF_VIEW_TYPE](diffViewType) {
|
||||
Object.assign(this, { diffViewType });
|
||||
},
|
||||
|
||||
[types.TOGGLE_LINE_HAS_FORM](state, { lineCode, fileHash, hasForm }) {
|
||||
const diffFile = state.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
[types.TOGGLE_LINE_HAS_FORM]({ lineCode, fileHash, hasForm }) {
|
||||
const diffFile = this.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
|
||||
if (!diffFile) return;
|
||||
|
||||
diffFile[INLINE_DIFF_LINES_KEY].find((l) => l.line_code === lineCode).hasForm = hasForm;
|
||||
},
|
||||
|
||||
[types.ADD_CONTEXT_LINES](state, options) {
|
||||
[types.ADD_CONTEXT_LINES](options) {
|
||||
const { lineNumbers, contextLines, fileHash, isExpandDown, nextLineNumbers } = options;
|
||||
const { bottom } = options.params;
|
||||
const diffFile = findDiffFile(state.diffFiles, fileHash);
|
||||
const diffFile = findDiffFile(this.diffFiles, fileHash);
|
||||
|
||||
removeMatchLine(diffFile, lineNumbers, bottom);
|
||||
|
||||
|
|
@ -163,18 +159,18 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
|
||||
[types.ADD_COLLAPSED_DIFFS]({ file, data }) {
|
||||
const files = prepareDiffData({ diff: data });
|
||||
const [newFileData] = files.filter((f) => f.file_hash === file.file_hash);
|
||||
const selectedFile = state.diffFiles.find((f) => f.file_hash === file.file_hash);
|
||||
const selectedFile = this.diffFiles.find((f) => f.file_hash === file.file_hash);
|
||||
Object.assign(selectedFile, {
|
||||
...newFileData,
|
||||
whitespaceOnlyChange: selectedFile.whitespaceOnlyChange,
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode, hash }) {
|
||||
const { latestDiff } = state;
|
||||
[types.SET_LINE_DISCUSSIONS_FOR_FILE]({ discussion, diffPositionByLineCode, hash }) {
|
||||
const { latestDiff } = this;
|
||||
const originalStartLineCode = discussion.original_position?.line_range?.start?.line_code;
|
||||
const positionType = discussion.position?.position_type;
|
||||
const discussionLineCodes = [
|
||||
|
|
@ -205,7 +201,7 @@ export default {
|
|||
const addDiscussion = (discussions) =>
|
||||
discussions.filter(({ id }) => discussion.id !== id).concat(discussion);
|
||||
|
||||
const file = state.diffFiles.find((diff) => diff.file_hash === fileHash);
|
||||
const file = this.diffFiles.find((diff) => diff.file_hash === fileHash);
|
||||
// a file batch might not be loaded yet when we try to add a discussion
|
||||
if (!file) return;
|
||||
const diffLines = file[INLINE_DIFF_LINES_KEY];
|
||||
|
|
@ -227,19 +223,19 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
[types.TOGGLE_FILE_DISCUSSION_EXPAND](
|
||||
state,
|
||||
{ discussion, expandedOnDiff = !discussion.expandedOnDiff },
|
||||
) {
|
||||
[types.TOGGLE_FILE_DISCUSSION_EXPAND]({
|
||||
discussion,
|
||||
expandedOnDiff = !discussion.expandedOnDiff,
|
||||
}) {
|
||||
Object.assign(discussion, { expandedOnDiff });
|
||||
const fileHash = discussion.diff_file.file_hash;
|
||||
const diff = state.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
const diff = this.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
// trigger Vue reactivity
|
||||
Object.assign(diff, { discussions: [...diff.discussions] });
|
||||
},
|
||||
|
||||
[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
|
||||
const selectedFile = state.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE]({ fileHash, lineCode }) {
|
||||
const selectedFile = this.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
if (selectedFile) {
|
||||
updateLineInFile(selectedFile, lineCode, (line) =>
|
||||
Object.assign(line, {
|
||||
|
|
@ -255,17 +251,17 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
[types.TOGGLE_LINE_DISCUSSIONS](state, { fileHash, lineCode, expanded }) {
|
||||
const selectedFile = state.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
[types.TOGGLE_LINE_DISCUSSIONS]({ fileHash, lineCode, expanded }) {
|
||||
const selectedFile = this.diffFiles.find((f) => f.file_hash === fileHash);
|
||||
|
||||
updateLineInFile(selectedFile, lineCode, (line) => {
|
||||
Object.assign(line, { discussionsExpanded: expanded });
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_EXPAND_ALL_DIFF_DISCUSSIONS](state, expanded) {
|
||||
[types.SET_EXPAND_ALL_DIFF_DISCUSSIONS](expanded) {
|
||||
const lineHasDiscussion = (line) => Boolean(line.discussions?.length);
|
||||
state.diffFiles.forEach((file) => {
|
||||
this.diffFiles.forEach((file) => {
|
||||
const highlightedLines = file[INLINE_DIFF_LINES_KEY];
|
||||
if (highlightedLines.length) {
|
||||
const discussionLines = highlightedLines.filter(lineHasDiscussion);
|
||||
|
|
@ -284,33 +280,33 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
[types.TOGGLE_FOLDER_OPEN](state, path) {
|
||||
state.treeEntries[path].opened = !state.treeEntries[path].opened;
|
||||
[types.TOGGLE_FOLDER_OPEN](path) {
|
||||
this.treeEntries[path].opened = !this.treeEntries[path].opened;
|
||||
},
|
||||
[types.TREE_ENTRY_DIFF_LOADING](state, { path, loading = true }) {
|
||||
state.treeEntries[path].diffLoading = loading;
|
||||
[types.TREE_ENTRY_DIFF_LOADING]({ path, loading = true }) {
|
||||
this.treeEntries[path].diffLoading = loading;
|
||||
},
|
||||
[types.SET_SHOW_TREE_LIST](state, showTreeList) {
|
||||
state.showTreeList = showTreeList;
|
||||
[types.SET_SHOW_TREE_LIST](showTreeList) {
|
||||
this.showTreeList = showTreeList;
|
||||
},
|
||||
[types.SET_CURRENT_DIFF_FILE](state, fileId) {
|
||||
state.currentDiffFileId = fileId;
|
||||
[types.SET_CURRENT_DIFF_FILE](fileId) {
|
||||
this.currentDiffFileId = fileId;
|
||||
},
|
||||
[types.SET_DIFF_FILE_VIEWED](state, { id, seen }) {
|
||||
state.viewedDiffFileIds = {
|
||||
...state.viewedDiffFileIds,
|
||||
[types.SET_DIFF_FILE_VIEWED]({ id, seen }) {
|
||||
this.viewedDiffFileIds = {
|
||||
...this.viewedDiffFileIds,
|
||||
[id]: seen,
|
||||
};
|
||||
},
|
||||
[types.OPEN_DIFF_FILE_COMMENT_FORM](state, formData) {
|
||||
state.commentForms.push({
|
||||
[types.OPEN_DIFF_FILE_COMMENT_FORM](formData) {
|
||||
this.commentForms.push({
|
||||
...formData,
|
||||
});
|
||||
},
|
||||
[types.UPDATE_DIFF_FILE_COMMENT_FORM](state, formData) {
|
||||
[types.UPDATE_DIFF_FILE_COMMENT_FORM](formData) {
|
||||
const { fileHash } = formData;
|
||||
|
||||
state.commentForms = state.commentForms.map((form) => {
|
||||
this.commentForms = this.commentForms.map((form) => {
|
||||
if (form.fileHash === fileHash) {
|
||||
return {
|
||||
...formData,
|
||||
|
|
@ -320,48 +316,45 @@ export default {
|
|||
return form;
|
||||
});
|
||||
},
|
||||
[types.CLOSE_DIFF_FILE_COMMENT_FORM](state, fileHash) {
|
||||
state.commentForms = state.commentForms.filter((form) => form.fileHash !== fileHash);
|
||||
[types.CLOSE_DIFF_FILE_COMMENT_FORM](fileHash) {
|
||||
this.commentForms = this.commentForms.filter((form) => form.fileHash !== fileHash);
|
||||
},
|
||||
[types.SET_HIGHLIGHTED_ROW](state, lineCode) {
|
||||
state.highlightedRow = lineCode;
|
||||
[types.SET_HIGHLIGHTED_ROW](lineCode) {
|
||||
this.highlightedRow = lineCode;
|
||||
},
|
||||
[types.SET_TREE_DATA](state, { treeEntries, tree }) {
|
||||
state.treeEntries = treeEntries;
|
||||
state.tree = tree;
|
||||
state.isTreeLoaded = true;
|
||||
[types.SET_TREE_DATA]({ treeEntries, tree }) {
|
||||
this.treeEntries = treeEntries;
|
||||
this.tree = tree;
|
||||
this.isTreeLoaded = true;
|
||||
},
|
||||
[types.SET_RENDER_TREE_LIST](state, renderTreeList) {
|
||||
state.renderTreeList = renderTreeList;
|
||||
[types.SET_RENDER_TREE_LIST](renderTreeList) {
|
||||
this.renderTreeList = renderTreeList;
|
||||
},
|
||||
[types.SET_SHOW_WHITESPACE](state, showWhitespace) {
|
||||
state.showWhitespace = showWhitespace;
|
||||
state.diffFiles = [];
|
||||
[types.SET_SHOW_WHITESPACE](showWhitespace) {
|
||||
this.showWhitespace = showWhitespace;
|
||||
this.diffFiles = [];
|
||||
},
|
||||
[types.TOGGLE_FILE_FINDER_VISIBLE](state, visible) {
|
||||
state.fileFinderVisible = visible;
|
||||
[types.TOGGLE_FILE_FINDER_VISIBLE](visible) {
|
||||
this.fileFinderVisible = visible;
|
||||
},
|
||||
[types.REQUEST_FULL_DIFF](state, filePath) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.REQUEST_FULL_DIFF](filePath) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file.isLoadingFullFile = true;
|
||||
},
|
||||
[types.RECEIVE_FULL_DIFF_ERROR](state, filePath) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.RECEIVE_FULL_DIFF_ERROR](filePath) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file.isLoadingFullFile = false;
|
||||
},
|
||||
[types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath }) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.RECEIVE_FULL_DIFF_SUCCESS]({ filePath }) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file.isShowingFullFile = true;
|
||||
file.isLoadingFullFile = false;
|
||||
},
|
||||
[types.SET_FILE_COLLAPSED](
|
||||
state,
|
||||
{ filePath, collapsed, trigger = DIFF_FILE_AUTOMATIC_COLLAPSE },
|
||||
) {
|
||||
const file = state.diffFiles.find((f) => f.file_path === filePath);
|
||||
[types.SET_FILE_COLLAPSED]({ filePath, collapsed, trigger = DIFF_FILE_AUTOMATIC_COLLAPSE }) {
|
||||
const file = this.diffFiles.find((f) => f.file_path === filePath);
|
||||
|
||||
if (file && file.viewer) {
|
||||
if (trigger === DIFF_FILE_MANUAL_COLLAPSE) {
|
||||
|
|
@ -373,58 +366,58 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
[types.SET_FILE_FORCED_OPEN](state, { filePath, forced = true }) {
|
||||
const file = state.diffFiles.find((f) => f.file_path === filePath);
|
||||
[types.SET_FILE_FORCED_OPEN]({ filePath, forced = true }) {
|
||||
const file = this.diffFiles.find((f) => f.file_path === filePath);
|
||||
file.viewer.forceOpen = forced;
|
||||
},
|
||||
[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
|
||||
const file = state.diffFiles.find((f) => f.file_path === filePath);
|
||||
[types.SET_CURRENT_VIEW_DIFF_FILE_LINES]({ filePath, lines }) {
|
||||
const file = this.diffFiles.find((f) => f.file_path === filePath);
|
||||
|
||||
file[INLINE_DIFF_LINES_KEY] = lines;
|
||||
file[INLINE_DIFF_LINES_KEY] = [...lines];
|
||||
},
|
||||
[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, line }) {
|
||||
const file = state.diffFiles.find((f) => f.file_path === filePath);
|
||||
[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES]({ filePath, line }) {
|
||||
const file = this.diffFiles.find((f) => f.file_path === filePath);
|
||||
|
||||
file[INLINE_DIFF_LINES_KEY].push(line);
|
||||
},
|
||||
[types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, filePath) {
|
||||
const file = state.diffFiles.find((f) => f.file_path === filePath);
|
||||
[types.TOGGLE_DIFF_FILE_RENDERING_MORE](filePath) {
|
||||
const file = this.diffFiles.find((f) => f.file_path === filePath);
|
||||
|
||||
file.renderingLines = !file.renderingLines;
|
||||
},
|
||||
[types.SET_DIFF_FILE_VIEWER](state, { filePath, viewer }) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.SET_DIFF_FILE_VIEWER]({ filePath, viewer }) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file.viewer = viewer;
|
||||
},
|
||||
[types.SET_SHOW_SUGGEST_POPOVER](state) {
|
||||
state.showSuggestPopover = false;
|
||||
[types.SET_SHOW_SUGGEST_POPOVER]() {
|
||||
this.showSuggestPopover = false;
|
||||
},
|
||||
[types.SET_FILE_BY_FILE](state, fileByFile) {
|
||||
state.viewDiffsFileByFile = fileByFile;
|
||||
[types.SET_FILE_BY_FILE](fileByFile) {
|
||||
this.viewDiffsFileByFile = fileByFile;
|
||||
},
|
||||
[types.SET_MR_FILE_REVIEWS](state, newReviews) {
|
||||
state.mrReviews = newReviews;
|
||||
[types.SET_MR_FILE_REVIEWS](newReviews) {
|
||||
this.mrReviews = newReviews;
|
||||
},
|
||||
[types.DISABLE_VIRTUAL_SCROLLING](state) {
|
||||
state.disableVirtualScroller = true;
|
||||
[types.DISABLE_VIRTUAL_SCROLLING]() {
|
||||
this.disableVirtualScroller = true;
|
||||
},
|
||||
[types.TOGGLE_FILE_COMMENT_FORM](state, filePath) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.TOGGLE_FILE_COMMENT_FORM](filePath) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file.hasCommentForm = !file.hasCommentForm;
|
||||
},
|
||||
[types.SET_FILE_COMMENT_FORM](state, { filePath, expanded }) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.SET_FILE_COMMENT_FORM]({ filePath, expanded }) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file.hasCommentForm = expanded;
|
||||
},
|
||||
[types.ADD_DRAFT_TO_FILE](state, { filePath, draft }) {
|
||||
const file = findDiffFile(state.diffFiles, filePath, 'file_path');
|
||||
[types.ADD_DRAFT_TO_FILE]({ filePath, draft }) {
|
||||
const file = findDiffFile(this.diffFiles, filePath, 'file_path');
|
||||
|
||||
file?.drafts.push(draft);
|
||||
},
|
||||
[types.SET_LINKED_FILE_HASH](state, fileHash) {
|
||||
state.linkedFileHash = fileHash;
|
||||
[types.SET_LINKED_FILE_HASH](fileHash) {
|
||||
this.linkedFileHash = fileHash;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,10 +11,18 @@
|
|||
"GroupAuditEventStreamingDestination",
|
||||
"InstanceAuditEventStreamingDestination"
|
||||
],
|
||||
"BaseDiscussionInterface": [
|
||||
"AbuseReportDiscussion",
|
||||
"Discussion"
|
||||
],
|
||||
"BaseHeaderInterface": [
|
||||
"AuditEventStreamingHeader",
|
||||
"AuditEventsStreamingInstanceHeader"
|
||||
],
|
||||
"BaseNoteInterface": [
|
||||
"AbuseReportNote",
|
||||
"Note"
|
||||
],
|
||||
"CiRunnerCloudProvisioning": [
|
||||
"CiRunnerGoogleCloudProvisioning"
|
||||
],
|
||||
|
|
@ -85,7 +93,6 @@
|
|||
"ProjectMember"
|
||||
],
|
||||
"NoteableInterface": [
|
||||
"AbuseReport",
|
||||
"AlertManagementAlert",
|
||||
"BoardEpic",
|
||||
"Design",
|
||||
|
|
@ -138,6 +145,8 @@
|
|||
"UploadRegistry"
|
||||
],
|
||||
"ResolvableInterface": [
|
||||
"AbuseReportDiscussion",
|
||||
"AbuseReportNote",
|
||||
"Discussion",
|
||||
"Note"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useMrNotes = defineStore('legacyMrNotes', {});
|
||||
|
|
@ -89,6 +89,7 @@ export default {
|
|||
:value="commentLineStart"
|
||||
:options="commentLineOptions"
|
||||
width="sm"
|
||||
class="gl-w-auto"
|
||||
@change="updateCommentLineStart"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useNotes = defineStore('legacyNotes', {});
|
||||
|
|
@ -177,18 +177,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.user-authored {
|
||||
cursor: default;
|
||||
background-color: $gray-10;
|
||||
border-color: $gray-100;
|
||||
color: $gl-text-color-disabled;
|
||||
|
||||
gl-emoji {
|
||||
opacity: 0.4;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn {
|
||||
&:focus {
|
||||
outline: 0;
|
||||
|
|
@ -196,7 +184,6 @@
|
|||
}
|
||||
|
||||
&.is-loading {
|
||||
.award-control-icon-normal,
|
||||
.emoji-icon {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,6 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.clear-block {
|
||||
margin-bottom: $gl-padding - 1px;
|
||||
padding-bottom: $gl-padding;
|
||||
}
|
||||
|
||||
&.second-block {
|
||||
margin-top: -1px;
|
||||
margin-bottom: 0;
|
||||
|
|
@ -77,10 +72,6 @@
|
|||
> .controls {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.new-branch {
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-block-small {
|
||||
|
|
|
|||
|
|
@ -32,25 +32,3 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This temporarily restores the legacy breadcrumbs styles on the primary HAML breadcrumbs.
|
||||
* Those styles got changed in https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3663,
|
||||
* causing a regression in this particular instance which does not use a Vue component and is
|
||||
* therefore unable to collapse overflowing items within a disclosure dropdown.
|
||||
* These temporary overrides will be removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/358113.
|
||||
*/
|
||||
.tmp-breadcrumbs-fix {
|
||||
.gl-breadcrumb-list {
|
||||
flex-wrap: wrap;
|
||||
max-width: none;
|
||||
|
||||
.gl-breadcrumb-item {
|
||||
> a {
|
||||
@include media-breakpoint-down(xs) {
|
||||
@include str-truncated($breadcrumb-max-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.toggle-colors {
|
||||
input {
|
||||
min-height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-broadcast-message-content p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@
|
|||
color: $text;
|
||||
border-color: $border;
|
||||
|
||||
&.btn-border-color {
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
color: $text;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,12 +199,6 @@ p.time {
|
|||
// Fix issue with notes & lists creating a bunch of bottom borders.
|
||||
li.note {
|
||||
img { max-width: 100%; }
|
||||
|
||||
.note-title {
|
||||
li {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.markdown {
|
||||
|
|
@ -287,6 +281,7 @@ li.note {
|
|||
}
|
||||
}
|
||||
|
||||
// these classes override styles from the dropzone node package
|
||||
.dropzone .dz-preview .dz-progress {
|
||||
border-color: $border-color !important;
|
||||
|
||||
|
|
@ -345,16 +340,6 @@ li.note {
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.checkbox-icon-inline-wrapper {
|
||||
.checkbox {
|
||||
display: inline;
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** COMMON CLASSES **/
|
||||
/**
|
||||
🚨 Do not use these classes — they are deprecated and being removed. 🚨
|
||||
|
|
|
|||
|
|
@ -82,11 +82,6 @@ $diff-file-header: 41px;
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-mode-changed {
|
||||
padding: 10px;
|
||||
@apply gl-text-subtle;
|
||||
}
|
||||
|
||||
.suppressed-container {
|
||||
padding: ($padding-base-vertical + 5px) $padding-base-horizontal;
|
||||
text-align: center;
|
||||
|
|
@ -455,12 +450,6 @@ table.code {
|
|||
span {
|
||||
white-space: break-spaces;
|
||||
|
||||
&.context-cell {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.line {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
|
@ -820,10 +809,6 @@ table.code {
|
|||
&[aria-expanded="false"] {
|
||||
@apply gl-border-b;
|
||||
}
|
||||
|
||||
.reply-author-avatar {
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,10 +104,6 @@
|
|||
color: $gl-text-color !important;
|
||||
}
|
||||
|
||||
&.no-outline {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.large {
|
||||
width: 200px;
|
||||
}
|
||||
|
|
@ -241,13 +237,6 @@
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-play {
|
||||
fill: $gl-text-color-secondary;
|
||||
margin-right: 6px;
|
||||
height: 12px;
|
||||
width: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
|
|
@ -272,11 +261,6 @@
|
|||
margin-bottom: $dropdown-vertical-offset;
|
||||
}
|
||||
|
||||
&.dropdown-open-left {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
|
|
@ -360,12 +344,6 @@
|
|||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.dropdown-bold-header {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
line-height: $gl-line-height;
|
||||
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
|
||||
}
|
||||
|
||||
.unclickable {
|
||||
cursor: not-allowed;
|
||||
padding: 5px 8px;
|
||||
|
|
@ -376,10 +354,6 @@
|
|||
// Expects up to 3 digits on the badge
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.dropdown-menu-content {
|
||||
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
|
|
@ -462,10 +436,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-large {
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
.dropdown-menu-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -537,7 +507,7 @@
|
|||
li {
|
||||
a,
|
||||
button,
|
||||
.dropdown-item:not(.open-with-link) {
|
||||
.dropdown-item {
|
||||
padding: 8px 40px;
|
||||
position: relative;
|
||||
|
||||
|
|
@ -656,8 +626,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-input-field,
|
||||
.default-dropdown-input {
|
||||
.dropdown-input-field {
|
||||
background-color: $input-bg;
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
@ -808,31 +777,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-content-faded-mask {
|
||||
position: relative;
|
||||
|
||||
.dropdown-list {
|
||||
max-height: $dropdown-max-height;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&::after {
|
||||
height: $dropdown-fade-mask-height;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, $white 0, rgba($white, 0));
|
||||
transition: opacity $fade-mask-transition-duration $fade-mask-transition-curve;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.fade-out::after {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.labels-select-wrapper {
|
||||
&.is-standalone {
|
||||
min-width: $input-md-width;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Transition classes are built dynamically, please read about vue-transitions
|
||||
* here before removing https://vuejs.org/guide/built-ins/transition
|
||||
**/
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active,
|
||||
.fade-in-enter-active,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ module Resolvers
|
|||
end
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
# TODO: Implement as part of completion https://gitlab.com/gitlab-org/gitlab/-/issues/458264
|
||||
return [] if object.is_a?(AbuseReport)
|
||||
|
||||
notes = NotesFinder.new(current_user, build_params(args)).execute
|
||||
apply_lookahead(notes)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ module Types
|
|||
class AbuseReportType < BaseObject
|
||||
graphql_name 'AbuseReport'
|
||||
|
||||
implements Types::Notes::NoteableInterface
|
||||
|
||||
description 'An abuse report'
|
||||
|
||||
authorize :read_abuse_report
|
||||
|
|
@ -15,5 +13,10 @@ module Types
|
|||
|
||||
field :labels, ::Types::LabelType.connection_type,
|
||||
null: true, description: 'Labels of the abuse report.'
|
||||
|
||||
field :discussions, ::Types::Notes::AbuseReport::DiscussionType.connection_type,
|
||||
null: false, description: "All discussions on the noteable."
|
||||
field :notes, ::Types::Notes::AbuseReport::NoteType.connection_type,
|
||||
null: false, description: "All notes on the noteable."
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Notes
|
||||
module AbuseReport
|
||||
class DiscussionType < BaseObject
|
||||
graphql_name 'AbuseReportDiscussion'
|
||||
|
||||
authorize :read_note
|
||||
|
||||
DiscussionID = ::Types::GlobalIDType[::Discussion]
|
||||
|
||||
implements Types::Notes::BaseDiscussionInterface
|
||||
|
||||
field :abuse_report, Types::AbuseReportType, null: true,
|
||||
description: 'Abuse report which the discussion belongs to.'
|
||||
|
||||
field :notes, Types::Notes::AbuseReport::NoteType.connection_type, null: false,
|
||||
description: 'All notes in the discussion.'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Notes
|
||||
module AbuseReport
|
||||
class NoteType < BaseObject
|
||||
graphql_name 'AbuseReportNote'
|
||||
|
||||
implements Types::Notes::BaseNoteInterface
|
||||
|
||||
authorize :read_note
|
||||
|
||||
field :id, ::Types::GlobalIDType[::AntiAbuse::Reports::Note],
|
||||
null: false,
|
||||
description: 'ID of the note.'
|
||||
|
||||
field :discussion, Types::Notes::AbuseReport::DiscussionType,
|
||||
null: true,
|
||||
description: 'Discussion the note is a part of.'
|
||||
|
||||
def note_project
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Notes
|
||||
module BaseDiscussionInterface
|
||||
include Types::BaseInterface
|
||||
|
||||
DiscussionID = ::Types::GlobalIDType[::Discussion]
|
||||
|
||||
implements Types::ResolvableInterface
|
||||
|
||||
field :created_at, Types::TimeType, null: false,
|
||||
description: "Timestamp of the discussion's creation."
|
||||
field :id, DiscussionID, null: false,
|
||||
description: "ID of the discussion."
|
||||
field :reply_id, DiscussionID, null: false,
|
||||
description: 'ID used to reply to the discussion.'
|
||||
|
||||
# DiscussionID.coerce_result is suitable here, but will always mark this
|
||||
# as being a 'Discussion'. Using `GlobalId.build` guarantees that we get
|
||||
# the correct class, and that it matches `id`.
|
||||
def reply_id
|
||||
::Gitlab::GlobalId.build(object, id: object.reply_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Notes
|
||||
module BaseNoteInterface
|
||||
include Types::BaseInterface
|
||||
|
||||
implements Types::ResolvableInterface
|
||||
|
||||
field :author, Types::UserType,
|
||||
null: true,
|
||||
description: 'User who wrote the note.'
|
||||
|
||||
field :award_emoji, Types::AwardEmojis::AwardEmojiType.connection_type,
|
||||
null: true,
|
||||
description: 'List of emoji reactions associated with the note.'
|
||||
|
||||
field :body, GraphQL::Types::String,
|
||||
null: false,
|
||||
method: :note,
|
||||
description: 'Content of the note.'
|
||||
|
||||
field :body_first_line_html, GraphQL::Types::String,
|
||||
null: false,
|
||||
description: 'First line of the note content.'
|
||||
|
||||
field :body_html, GraphQL::Types::String,
|
||||
method: :note_html,
|
||||
null: true,
|
||||
description: "GitLab Flavored Markdown rendering of the content of the note."
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
null: false,
|
||||
description: 'Timestamp of the note creation.'
|
||||
|
||||
field :last_edited_at, Types::TimeType,
|
||||
null: true,
|
||||
description: 'Timestamp when note was last edited.'
|
||||
|
||||
field :last_edited_by, Types::UserType,
|
||||
null: true,
|
||||
description: 'User who last edited the note.'
|
||||
|
||||
field :updated_at, Types::TimeType,
|
||||
null: false,
|
||||
description: "Timestamp of the note's last activity."
|
||||
|
||||
field :url, GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'URL to view the note in the Web UI.'
|
||||
|
||||
def author
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
|
||||
end
|
||||
|
||||
def url
|
||||
::Gitlab::UrlBuilder.build(object)
|
||||
end
|
||||
|
||||
def body_first_line_html
|
||||
first_line_in_markdown(object, :note, 125, project: note_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,29 +5,14 @@ module Types
|
|||
class DiscussionType < BaseObject
|
||||
graphql_name 'Discussion'
|
||||
|
||||
DiscussionID = ::Types::GlobalIDType[::Discussion]
|
||||
|
||||
authorize :read_note
|
||||
|
||||
implements Types::ResolvableInterface
|
||||
implements Types::Notes::BaseDiscussionInterface
|
||||
|
||||
field :created_at, Types::TimeType, null: false,
|
||||
description: "Timestamp of the discussion's creation."
|
||||
field :id, DiscussionID, null: false,
|
||||
description: "ID of this discussion."
|
||||
field :noteable, Types::NoteableType, null: true,
|
||||
description: 'Object which the discussion belongs to.'
|
||||
field :notes, Types::Notes::NoteType.connection_type, null: false,
|
||||
description: 'All notes in the discussion.'
|
||||
field :reply_id, DiscussionID, null: false,
|
||||
description: 'ID used to reply to this discussion.'
|
||||
|
||||
# DiscussionID.coerce_result is suitable here, but will always mark this
|
||||
# as being a 'Discussion'. Using `GlobalId.build` guarantees that we get
|
||||
# the correct class, and that it matches `id`.
|
||||
def reply_id
|
||||
::Gitlab::GlobalId.build(object, id: object.reply_id)
|
||||
end
|
||||
|
||||
def noteable
|
||||
noteable = object.noteable
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module Types
|
|||
|
||||
expose_permissions Types::PermissionTypes::Note
|
||||
|
||||
implements Types::ResolvableInterface
|
||||
implements Types::Notes::BaseNoteInterface
|
||||
|
||||
present_using NotePresenter
|
||||
|
||||
|
|
@ -31,10 +31,6 @@ module Types
|
|||
null: true,
|
||||
description: 'Project associated with the note.'
|
||||
|
||||
field :author, Types::UserType,
|
||||
null: true,
|
||||
description: 'User who wrote the note.'
|
||||
|
||||
field :system, GraphQL::Types::Boolean,
|
||||
null: false,
|
||||
description: 'Indicates whether the note was created by the system or by a user.'
|
||||
|
|
@ -43,19 +39,6 @@ module Types
|
|||
null: true,
|
||||
description: 'Name of the icon corresponding to a system note.'
|
||||
|
||||
field :body, GraphQL::Types::String,
|
||||
null: false,
|
||||
method: :note,
|
||||
description: 'Content of the note.'
|
||||
|
||||
field :body_first_line_html, GraphQL::Types::String,
|
||||
null: false,
|
||||
description: 'First line of the note content.'
|
||||
|
||||
field :award_emoji, Types::AwardEmojis::AwardEmojiType.connection_type,
|
||||
null: true,
|
||||
description: 'List of emoji reactions associated with the note.'
|
||||
|
||||
field :imported, GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
description: 'Indicates whether the note was imported.',
|
||||
|
|
@ -65,28 +48,12 @@ module Types
|
|||
description: 'Indicates if the note is internal.',
|
||||
method: :confidential?
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
null: false,
|
||||
description: 'Timestamp of the note creation.'
|
||||
field :discussion, Types::Notes::DiscussionType,
|
||||
null: true,
|
||||
description: 'Discussion the note is a part of.'
|
||||
field :position, Types::Notes::DiffPositionType,
|
||||
null: true,
|
||||
description: 'Position of the note on a diff.'
|
||||
field :updated_at, Types::TimeType,
|
||||
null: false,
|
||||
description: "Timestamp of the note's last activity."
|
||||
field :url, GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'URL to view the note in the Web UI.'
|
||||
|
||||
field :last_edited_at, Types::TimeType,
|
||||
null: true,
|
||||
description: 'Timestamp when note was last edited.'
|
||||
field :last_edited_by, Types::UserType,
|
||||
null: true,
|
||||
description: 'User who last edited the note.'
|
||||
|
||||
field :author_is_contributor, GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
|
|
@ -98,15 +65,6 @@ module Types
|
|||
null: true,
|
||||
description: 'Metadata for the given note if it is a system note.'
|
||||
|
||||
field :body_html, GraphQL::Types::String,
|
||||
method: :note_html,
|
||||
null: true,
|
||||
description: "GitLab Flavored Markdown rendering of the content of the note."
|
||||
|
||||
def url
|
||||
::Gitlab::UrlBuilder.build(object)
|
||||
end
|
||||
|
||||
def system_note_icon_name
|
||||
SystemNoteHelper.system_note_icon_name(object) if object.system?
|
||||
end
|
||||
|
|
@ -115,10 +73,6 @@ module Types
|
|||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
|
||||
end
|
||||
|
||||
def author
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
|
||||
end
|
||||
|
||||
# We now support also SyntheticNote notes as a NoteType, but SyntheticNote does not have a real note ID,
|
||||
# as SyntheticNote is generated dynamically from a ResourceEvent instance.
|
||||
def id
|
||||
|
|
@ -128,8 +82,8 @@ module Types
|
|||
::Gitlab::GlobalId.build(object, model_name: object.object.class.to_s, id: object.discussion_id)
|
||||
end
|
||||
|
||||
def body_first_line_html
|
||||
first_line_in_markdown(object, :note, 125, project: object.project)
|
||||
def note_project
|
||||
object.project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,17 +21,12 @@ module Types
|
|||
Types::DesignManagement::DesignType
|
||||
when ::AlertManagement::Alert
|
||||
Types::AlertManagement::AlertType
|
||||
when AbuseReport
|
||||
Types::AbuseReportType
|
||||
else
|
||||
raise "Unknown GraphQL type for #{object}"
|
||||
end
|
||||
end
|
||||
|
||||
def commenters
|
||||
# TODO: Implement as part of completion https://gitlab.com/gitlab-org/gitlab/-/issues/458264
|
||||
return [] if object.is_a?(AbuseReport)
|
||||
|
||||
object.commenters(user: current_user)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
module AntiAbuse
|
||||
module Reports
|
||||
class Discussion < ::Discussion
|
||||
delegate :abuse_report, to: :first_note
|
||||
|
||||
def self.base_discussion_id(_note)
|
||||
[:discussion, :abuse_report_id]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,6 +32,19 @@ module AntiAbuse
|
|||
validates :abuse_report, presence: true
|
||||
|
||||
scope :fresh, -> { order_created_asc.with_order_id_asc }
|
||||
scope :inc_relations_for_view, ->(_abuse_report = nil) do
|
||||
relations = [
|
||||
{ author: :status }, :updated_by, :award_emoji
|
||||
]
|
||||
|
||||
includes(relations)
|
||||
end
|
||||
|
||||
class << self
|
||||
def parent_object_field
|
||||
:abuse_report
|
||||
end
|
||||
end
|
||||
|
||||
def discussion_class(_noteable = nil)
|
||||
AntiAbuse::Reports::IndividualNoteDiscussion
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Notes
|
|||
|
||||
class_methods do
|
||||
def discussions(context_noteable = nil)
|
||||
::Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
|
||||
::Discussion.build_collection(all.includes(parent_object_field).fresh, context_noteable)
|
||||
end
|
||||
|
||||
def find_discussion(discussion_id)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
class InstanceIntegration < Integration
|
||||
self.table_name = 'instance_integrations'
|
||||
end
|
||||
end
|
||||
|
|
@ -230,6 +230,10 @@ class Note < ApplicationRecord
|
|||
ActiveModel::Name.new(self, nil, 'note')
|
||||
end
|
||||
|
||||
def parent_object_field
|
||||
:noteable
|
||||
end
|
||||
|
||||
# Group diff discussions by line code or file path.
|
||||
# It is not needed to group by line code when comment is
|
||||
# on an image.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AntiAbuse
|
||||
module Reports
|
||||
class NotePolicy < BasePolicy
|
||||
delegate { @subject.abuse_report }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= s_('ExternalAuthorization|External classification policy authorization.')
|
||||
= link_to _('Learn more.'), help_page_path('administration/settings/external_authorization'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Learn more.'), help_page_path('administration/settings/external_authorization.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_body do
|
||||
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-external-auth-settings'), html: { class: 'fieldset-form', id: 'external-auth-settings' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
impersonation: true,
|
||||
token: @impersonation_token,
|
||||
scopes: @scopes,
|
||||
help_path: help_page_path('api/rest/index', anchor: 'impersonation-tokens')
|
||||
help_path: help_page_path('api/rest/index.md', anchor: 'impersonation-tokens')
|
||||
|
||||
- c.with_body do
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_impersonation_tokens.to_json } }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
- return unless render_setting_to_allow_project_access_token_creation?(group)
|
||||
|
||||
.form-group.gl-mb-3
|
||||
- project_access_tokens_link = help_page_path('user/project/settings/project_access_tokens')
|
||||
- group_access_tokens_link = help_page_path('user/group/settings/group_access_tokens')
|
||||
- project_access_tokens_link = help_page_path('user/project/settings/project_access_tokens.md')
|
||||
- group_access_tokens_link = help_page_path('user/group/settings/group_access_tokens.md')
|
||||
- link_start_project = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: project_access_tokens_link }
|
||||
- link_start_group = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_access_tokens_link }
|
||||
= f.gitlab_ui_checkbox_component :resource_access_token_creation_allowed,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
- return unless group.parent_allows_two_factor_authentication?
|
||||
- docs_link_url = help_page_path('security/two_factor_authentication', anchor: 'enforce-2fa-for-all-users-in-a-group')
|
||||
- docs_link_url = help_page_path('security/two_factor_authentication.md', anchor: 'enforce-2fa-for-all-users-in-a-group')
|
||||
- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
|
||||
|
||||
%h5= _('Two-factor authentication')
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
= render ::Layouts::SettingsSectionComponent.new(page_title, options: { class: 'js-search-settings-section' }) do |c|
|
||||
- c.with_description do
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/settings/group_access_tokens') }
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/settings/group_access_tokens.md') }
|
||||
- if current_user.can?(:create_resource_access_tokens, @group)
|
||||
= _('Generate group access tokens scoped to this group for your applications that need access to the GitLab API.')
|
||||
= html_escape(_('You can also use group access tokens with Git to authenticate over HTTP(S). %{link_start}Learn more.%{link_end}')) % { link_start: help_link_start, link_end: '</a>'.html_safe }
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
default_access_level: Gitlab::Access::GUEST,
|
||||
prefix: :resource_access_token,
|
||||
description_prefix: :group_access_token,
|
||||
help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
|
||||
help_path: help_page_path('user/group/settings/group_access_tokens.md', anchor: 'scopes-for-a-group-access-token')
|
||||
|
||||
- c.with_body do
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This group has no active access tokens.'), show_role: true } }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
%p
|
||||
- register_2fa_token = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
|
||||
= register_2fa_token.html_safe
|
||||
= link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'enable-one-time-password'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-one-time-password'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.gl-p-2.gl-mb-3{ style: 'background: #fff' }
|
||||
= raw @qr_code
|
||||
.gl-mb-5
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
alert_options: { class: 'gl-mb-3' },
|
||||
dismissible: false) do |c|
|
||||
- c.with_body do
|
||||
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if current_password_required?
|
||||
.form-group
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
alert_options: { class: 'gl-mb-3' },
|
||||
dismissible: false) do |c|
|
||||
- c.with_body do
|
||||
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.js-manage-two-factor-form{ data: { current_password_required: current_password_required?.to_s, profile_two_factor_auth_path: profile_two_factor_auth_path, profile_two_factor_auth_method: 'delete', codes_profile_two_factor_auth_path: codes_profile_two_factor_auth_path, codes_profile_two_factor_auth_method: 'post' } }
|
||||
- else
|
||||
%p
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@
|
|||
default_access_level: Gitlab::Access::GUEST,
|
||||
prefix: :resource_access_token,
|
||||
description_prefix: :project_access_token,
|
||||
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
|
||||
help_path: help_page_path('user/project/settings/project_access_tokens.md', anchor: 'scopes-for-a-project-access-token')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
%h4.gl-my-0
|
||||
= page_title
|
||||
%p.gl-text-secondary
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/settings/project_access_tokens') }
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/settings/project_access_tokens.md') }
|
||||
- if current_user.can?(:create_resource_access_tokens, @project)
|
||||
= _('Generate project access tokens scoped to this project for your applications that need access to the GitLab API.')
|
||||
= _('You can also use project access tokens with Git to authenticate over HTTP(S). %{link_start}Learn more.%{link_end}').html_safe % { link_start: help_link_start, link_end: '</a>'.html_safe }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
close_button_options: { data: { testid: 'close-account-recovery-regular-check-callout' }}) do |c|
|
||||
- c.with_body do
|
||||
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')
|
||||
= link_to _('Learn more.'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'recovery-codes'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Learn more.'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'recovery-codes'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_actions do
|
||||
= link_button_to profile_two_factor_auth_path, class: 'deferred-link gl-alert-action', variant: :confirm do
|
||||
= s_('Profiles|Manage two-factor authentication')
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
path: user_settings_personal_access_tokens_path,
|
||||
token: @personal_access_token,
|
||||
scopes: @scopes,
|
||||
help_path: help_page_path('user/profile/personal_access_tokens', anchor: 'personal-access-token-scopes')
|
||||
help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
|
||||
|
||||
- c.with_body do
|
||||
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json } }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
name: self_hosted_model_destroyed
|
||||
description: A new self-hosted model configuration was destroyed
|
||||
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/477999
|
||||
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321
|
||||
feature_category: self-hosted_models
|
||||
milestone: '17.4'
|
||||
saved_to_database: true
|
||||
scope: [Instance, User]
|
||||
streamed: true
|
||||
|
|
@ -13,7 +13,7 @@ Gitlab::Experiment.configure do |config|
|
|||
# Customize the logic of our default rollout, which shouldn't include
|
||||
# assigning the control yet -- we specifically set it to false for now.
|
||||
#
|
||||
config.default_rollout = Gitlab::Experiment::Rollout.resolve(:feature)
|
||||
config.default_rollout = Gitlab::Experiment::Rollout.resolve('Gitlab::ExperimentFeatureRollout')
|
||||
|
||||
# Mount the engine and middleware at a gitlab friendly style path.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
table_name: instance_integrations
|
||||
classes:
|
||||
- Integrations::InstanceIntegration
|
||||
feature_categories:
|
||||
- integrations
|
||||
description: Support 3rd party instance-wide integrations
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164503
|
||||
milestone: '17.4'
|
||||
gitlab_schema: gitlab_main_clusterwide
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSppRepositoryPipelineAccessToProjectSettings < Gitlab::Database::Migration[2.2]
|
||||
enable_lock_retries!
|
||||
milestone '17.4'
|
||||
|
||||
def change
|
||||
add_column :project_settings, :spp_repository_pipeline_access, :boolean
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateInstanceIntegrationsTable < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
|
||||
# rubocop:disable Migration/EnsureFactoryForTable -- False Positive
|
||||
def up
|
||||
create_table :instance_integrations, id: :bigserial do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.integer :comment_detail
|
||||
t.boolean :active, default: false, null: false
|
||||
t.boolean :push_events, default: true
|
||||
t.boolean :issues_events, default: true
|
||||
t.boolean :merge_requests_events, default: true
|
||||
t.boolean :tag_push_events, default: true
|
||||
t.boolean :note_events, default: true, null: false
|
||||
t.boolean :wiki_page_events, default: true
|
||||
t.boolean :pipeline_events, default: false, null: false
|
||||
t.boolean :confidential_issues_events, default: true, null: false
|
||||
t.boolean :commit_events, default: true, null: false
|
||||
t.boolean :job_events, default: false, null: false
|
||||
t.boolean :confidential_note_events, default: true
|
||||
t.boolean :deployment_events, default: false, null: false
|
||||
t.boolean :comment_on_event_enabled, default: true, null: false
|
||||
t.boolean :alert_events
|
||||
t.boolean :vulnerability_events, default: false, null: false
|
||||
t.boolean :archive_trace_events, default: false, null: false
|
||||
t.boolean :incident_events, default: false, null: false
|
||||
t.boolean :group_mention_events, default: false, null: false
|
||||
t.boolean :group_confidential_mention_events, default: false, null: false
|
||||
t.text :category, default: 'common', limit: 255
|
||||
t.text :type, limit: 255
|
||||
t.binary :encrypted_properties
|
||||
t.binary :encrypted_properties_iv
|
||||
end
|
||||
end
|
||||
# rubocop:enable Migration/EnsureFactoryForTable -- False Positive
|
||||
|
||||
def down
|
||||
drop_table :instance_integrations, if_exists: true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
3a6da002969d32ed71e3a8700a007380d20193df5f3a3591e98136a135380f4f
|
||||
|
|
@ -0,0 +1 @@
|
|||
7f15b5117aa826e8e0dbbd334393a29c4da7bc6443308f1ceffbaa015ab999cd
|
||||
|
|
@ -12059,6 +12059,48 @@ CREATE SEQUENCE instance_audit_events_streaming_headers_id_seq
|
|||
|
||||
ALTER SEQUENCE instance_audit_events_streaming_headers_id_seq OWNED BY instance_audit_events_streaming_headers.id;
|
||||
|
||||
CREATE TABLE instance_integrations (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
comment_detail integer,
|
||||
active boolean DEFAULT false NOT NULL,
|
||||
push_events boolean DEFAULT true,
|
||||
issues_events boolean DEFAULT true,
|
||||
merge_requests_events boolean DEFAULT true,
|
||||
tag_push_events boolean DEFAULT true,
|
||||
note_events boolean DEFAULT true NOT NULL,
|
||||
wiki_page_events boolean DEFAULT true,
|
||||
pipeline_events boolean DEFAULT false NOT NULL,
|
||||
confidential_issues_events boolean DEFAULT true NOT NULL,
|
||||
commit_events boolean DEFAULT true NOT NULL,
|
||||
job_events boolean DEFAULT false NOT NULL,
|
||||
confidential_note_events boolean DEFAULT true,
|
||||
deployment_events boolean DEFAULT false NOT NULL,
|
||||
comment_on_event_enabled boolean DEFAULT true NOT NULL,
|
||||
alert_events boolean,
|
||||
vulnerability_events boolean DEFAULT false NOT NULL,
|
||||
archive_trace_events boolean DEFAULT false NOT NULL,
|
||||
incident_events boolean DEFAULT false NOT NULL,
|
||||
group_mention_events boolean DEFAULT false NOT NULL,
|
||||
group_confidential_mention_events boolean DEFAULT false NOT NULL,
|
||||
category text DEFAULT 'common'::text,
|
||||
type text,
|
||||
encrypted_properties bytea,
|
||||
encrypted_properties_iv bytea,
|
||||
CONSTRAINT check_611836812c CHECK ((char_length(category) <= 255)),
|
||||
CONSTRAINT check_69b7b09aa8 CHECK ((char_length(type) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE instance_integrations_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE instance_integrations_id_seq OWNED BY instance_integrations.id;
|
||||
|
||||
CREATE TABLE integrations (
|
||||
id bigint NOT NULL,
|
||||
project_id bigint,
|
||||
|
|
@ -16719,6 +16761,7 @@ CREATE TABLE project_settings (
|
|||
duo_features_enabled boolean DEFAULT true NOT NULL,
|
||||
require_reauthentication_to_approve boolean,
|
||||
observability_alerts_enabled boolean DEFAULT true NOT NULL,
|
||||
spp_repository_pipeline_access boolean,
|
||||
CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)),
|
||||
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
|
||||
CONSTRAINT check_3ca5cbffe6 CHECK ((char_length(issue_branch_template) <= 255)),
|
||||
|
|
@ -21709,6 +21752,8 @@ ALTER TABLE ONLY insights ALTER COLUMN id SET DEFAULT nextval('insights_id_seq':
|
|||
|
||||
ALTER TABLE ONLY instance_audit_events_streaming_headers ALTER COLUMN id SET DEFAULT nextval('instance_audit_events_streaming_headers_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY instance_integrations ALTER COLUMN id SET DEFAULT nextval('instance_integrations_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY integrations ALTER COLUMN id SET DEFAULT nextval('integrations_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY internal_ids ALTER COLUMN id SET DEFAULT nextval('internal_ids_id_seq'::regclass);
|
||||
|
|
@ -23973,6 +24018,9 @@ ALTER TABLE ONLY instance_audit_events
|
|||
ALTER TABLE ONLY instance_audit_events_streaming_headers
|
||||
ADD CONSTRAINT instance_audit_events_streaming_headers_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY instance_integrations
|
||||
ADD CONSTRAINT instance_integrations_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY integrations
|
||||
ADD CONSTRAINT integrations_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
|
|||
|
|
@ -10880,6 +10880,52 @@ Some of the types in the schema exist solely to model connections. Each connecti
|
|||
has a distinct, named type, with a distinct named edge type. These are listed separately
|
||||
below.
|
||||
|
||||
#### `AbuseReportDiscussionConnection`
|
||||
|
||||
The connection type for [`AbuseReportDiscussion`](#abusereportdiscussion).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportdiscussionconnectionedges"></a>`edges` | [`[AbuseReportDiscussionEdge]`](#abusereportdiscussionedge) | A list of edges. |
|
||||
| <a id="abusereportdiscussionconnectionnodes"></a>`nodes` | [`[AbuseReportDiscussion]`](#abusereportdiscussion) | A list of nodes. |
|
||||
| <a id="abusereportdiscussionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `AbuseReportDiscussionEdge`
|
||||
|
||||
The edge type for [`AbuseReportDiscussion`](#abusereportdiscussion).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportdiscussionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="abusereportdiscussionedgenode"></a>`node` | [`AbuseReportDiscussion`](#abusereportdiscussion) | The item at the end of the edge. |
|
||||
|
||||
#### `AbuseReportNoteConnection`
|
||||
|
||||
The connection type for [`AbuseReportNote`](#abusereportnote).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportnoteconnectionedges"></a>`edges` | [`[AbuseReportNoteEdge]`](#abusereportnoteedge) | A list of edges. |
|
||||
| <a id="abusereportnoteconnectionnodes"></a>`nodes` | [`[AbuseReportNote]`](#abusereportnote) | A list of nodes. |
|
||||
| <a id="abusereportnoteconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `AbuseReportNoteEdge`
|
||||
|
||||
The edge type for [`AbuseReportNote`](#abusereportnote).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportnoteedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="abusereportnoteedgenode"></a>`node` | [`AbuseReportNote`](#abusereportnote) | The item at the end of the edge. |
|
||||
|
||||
#### `AccessLevelDeployKeyConnection`
|
||||
|
||||
The connection type for [`AccessLevelDeployKey`](#accessleveldeploykey).
|
||||
|
|
@ -16813,28 +16859,49 @@ An abuse report.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="abusereportdiscussions"></a>`discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="abusereportdiscussions"></a>`discussions` | [`AbuseReportDiscussionConnection!`](#abusereportdiscussionconnection) | All discussions on the noteable. (see [Connections](#connections)) |
|
||||
| <a id="abusereportid"></a>`id` | [`AbuseReportID!`](#abusereportid) | Global ID of the abuse report. |
|
||||
| <a id="abusereportlabels"></a>`labels` | [`LabelConnection`](#labelconnection) | Labels of the abuse report. (see [Connections](#connections)) |
|
||||
| <a id="abusereportnotes"></a>`notes` | [`AbuseReportNoteConnection!`](#abusereportnoteconnection) | All notes on the noteable. (see [Connections](#connections)) |
|
||||
|
||||
#### Fields with arguments
|
||||
### `AbuseReportDiscussion`
|
||||
|
||||
##### `AbuseReport.notes`
|
||||
|
||||
All notes on this noteable.
|
||||
|
||||
Returns [`NoteConnection!`](#noteconnection).
|
||||
|
||||
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
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportnotesfilter"></a>`filter` | [`NotesFilterType`](#notesfiltertype) | Type of notes collection: ALL_NOTES, ONLY_COMMENTS, ONLY_ACTIVITY. |
|
||||
| <a id="abusereportdiscussionabusereport"></a>`abuseReport` | [`AbuseReport`](#abusereport) | Abuse report which the discussion belongs to. |
|
||||
| <a id="abusereportdiscussioncreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the discussion's creation. |
|
||||
| <a id="abusereportdiscussionid"></a>`id` | [`DiscussionID!`](#discussionid) | ID of the discussion. |
|
||||
| <a id="abusereportdiscussionnotes"></a>`notes` | [`AbuseReportNoteConnection!`](#abusereportnoteconnection) | All notes in the discussion. (see [Connections](#connections)) |
|
||||
| <a id="abusereportdiscussionreplyid"></a>`replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to the discussion. |
|
||||
| <a id="abusereportdiscussionresolvable"></a>`resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
|
||||
| <a id="abusereportdiscussionresolved"></a>`resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
|
||||
| <a id="abusereportdiscussionresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
|
||||
| <a id="abusereportdiscussionresolvedby"></a>`resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
|
||||
|
||||
### `AbuseReportNote`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="abusereportnoteauthor"></a>`author` | [`UserCore`](#usercore) | User who wrote the note. |
|
||||
| <a id="abusereportnoteawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of emoji reactions associated with the note. (see [Connections](#connections)) |
|
||||
| <a id="abusereportnotebody"></a>`body` | [`String!`](#string) | Content of the note. |
|
||||
| <a id="abusereportnotebodyfirstlinehtml"></a>`bodyFirstLineHtml` | [`String!`](#string) | First line of the note content. |
|
||||
| <a id="abusereportnotebodyhtml"></a>`bodyHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of the content of the note. |
|
||||
| <a id="abusereportnotecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the note creation. |
|
||||
| <a id="abusereportnotediscussion"></a>`discussion` | [`AbuseReportDiscussion`](#abusereportdiscussion) | Discussion the note is a part of. |
|
||||
| <a id="abusereportnoteid"></a>`id` | [`AntiAbuseReportsNoteID!`](#antiabusereportsnoteid) | ID of the note. |
|
||||
| <a id="abusereportnotelasteditedat"></a>`lastEditedAt` | [`Time`](#time) | Timestamp when note was last edited. |
|
||||
| <a id="abusereportnotelasteditedby"></a>`lastEditedBy` | [`UserCore`](#usercore) | User who last edited the note. |
|
||||
| <a id="abusereportnoteresolvable"></a>`resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
|
||||
| <a id="abusereportnoteresolved"></a>`resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
|
||||
| <a id="abusereportnoteresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
|
||||
| <a id="abusereportnoteresolvedby"></a>`resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
|
||||
| <a id="abusereportnoteupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of the note's last activity. |
|
||||
| <a id="abusereportnoteurl"></a>`url` | [`String`](#string) | URL to view the note in the Web UI. |
|
||||
|
||||
### `AccessLevel`
|
||||
|
||||
|
|
@ -17355,6 +17422,19 @@ Information about a connected Agent.
|
|||
| <a id="aggregationstatusestimatednextupdateat"></a>`estimatedNextUpdateAt` | [`Time`](#time) | Estimated time when the next incremental update will happen. |
|
||||
| <a id="aggregationstatuslastupdateat"></a>`lastUpdateAt` | [`Time`](#time) | Last incremental update time. |
|
||||
|
||||
### `AiAdditionalContext`
|
||||
|
||||
Additional context for AI message.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="aiadditionalcontextcategory"></a>`category` | [`AiAdditionalContextCategory!`](#aiadditionalcontextcategory) | Category of the additional context. |
|
||||
| <a id="aiadditionalcontextcontent"></a>`content` | [`String!`](#string) | Content of the additional context. |
|
||||
| <a id="aiadditionalcontextid"></a>`id` | [`ID!`](#id) | ID of the additional context. |
|
||||
| <a id="aiadditionalcontextmetadata"></a>`metadata` | [`JSON`](#json) | Metadata of the additional context. |
|
||||
|
||||
### `AiAgent`
|
||||
|
||||
An AI agent.
|
||||
|
|
@ -17403,6 +17483,7 @@ AI features communication message.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="aimessageadditionalcontext"></a>`additionalContext` | [`[AiAdditionalContext!]`](#aiadditionalcontext) | Additional context for the message. |
|
||||
| <a id="aimessageagentversionid"></a>`agentVersionId` | [`AiAgentVersionID`](#aiagentversionid) | Global ID of the agent version to answer the message. |
|
||||
| <a id="aimessagechunkid"></a>`chunkId` | [`Int`](#int) | Incremental ID for a chunk from a streamed message. Null when it is not a streamed message. |
|
||||
| <a id="aimessagecontent"></a>`content` | [`String`](#string) | Raw response content. |
|
||||
|
|
@ -21570,10 +21651,10 @@ Aggregated summary of changes.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="discussioncreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the discussion's creation. |
|
||||
| <a id="discussionid"></a>`id` | [`DiscussionID!`](#discussionid) | ID of this discussion. |
|
||||
| <a id="discussionid"></a>`id` | [`DiscussionID!`](#discussionid) | ID of the discussion. |
|
||||
| <a id="discussionnoteable"></a>`noteable` | [`NoteableType`](#noteabletype) | Object which the discussion belongs to. |
|
||||
| <a id="discussionnotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes in the discussion. (see [Connections](#connections)) |
|
||||
| <a id="discussionreplyid"></a>`replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to this discussion. |
|
||||
| <a id="discussionreplyid"></a>`replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to the discussion. |
|
||||
| <a id="discussionresolvable"></a>`resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
|
||||
| <a id="discussionresolved"></a>`resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
|
||||
| <a id="discussionresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
|
||||
|
|
@ -35427,8 +35508,11 @@ LLMs supported by the self-hosted model features.
|
|||
| <a id="aiacceptedselfhostedmodelscodestral"></a>`CODESTRAL` | Codestral 22B: Suitable for code completion and code generation. |
|
||||
| <a id="aiacceptedselfhostedmodelsdeepseekcoder"></a>`DEEPSEEKCODER` | Deepseek Coder 1.3b, 6.7b and 33b base or instruct. |
|
||||
| <a id="aiacceptedselfhostedmodelsmistral"></a>`MISTRAL` | Mistral 7B: Suitable for code generation and duo chat. |
|
||||
| <a id="aiacceptedselfhostedmodelsmistral_text"></a>`MISTRAL_TEXT` | Mistral-7B Text: Suitable for code completion. |
|
||||
| <a id="aiacceptedselfhostedmodelsmixtral"></a>`MIXTRAL` | Mixtral 8x7B: Suitable for code generation and duo chat. |
|
||||
| <a id="aiacceptedselfhostedmodelsmixtral_8x22b"></a>`MIXTRAL_8X22B` | Mixtral 8x22B: Suitable for code generation and duo chat. |
|
||||
| <a id="aiacceptedselfhostedmodelsmixtral_8x22b_text"></a>`MIXTRAL_8X22B_TEXT` | Mixtral-8x22B Text: Suitable for code completion. |
|
||||
| <a id="aiacceptedselfhostedmodelsmixtral_text"></a>`MIXTRAL_TEXT` | Mixtral-8x7B Text: Suitable for code completion. |
|
||||
|
||||
### `AiAction`
|
||||
|
||||
|
|
@ -38817,6 +38901,12 @@ A `AnalyticsDevopsAdoptionEnabledNamespaceID` is a global ID. It is encoded as a
|
|||
|
||||
An example `AnalyticsDevopsAdoptionEnabledNamespaceID` is: `"gid://gitlab/Analytics::DevopsAdoption::EnabledNamespace/1"`.
|
||||
|
||||
### `AntiAbuseReportsNoteID`
|
||||
|
||||
A `AntiAbuseReportsNoteID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `AntiAbuseReportsNoteID` is: `"gid://gitlab/AntiAbuse::Reports::Note/1"`.
|
||||
|
||||
### `AppSecFuzzingCoverageCorpusID`
|
||||
|
||||
A `AppSecFuzzingCoverageCorpusID` is a global ID. It is encoded as a string.
|
||||
|
|
@ -40001,6 +40091,25 @@ Implementations:
|
|||
| <a id="auditeventstreamingdestinationinterfaceid"></a>`id` | [`ID!`](#id) | ID of the destination. |
|
||||
| <a id="auditeventstreamingdestinationinterfacename"></a>`name` | [`String!`](#string) | Name of the external destination to send audit events to. |
|
||||
|
||||
#### `BaseDiscussionInterface`
|
||||
|
||||
Implementations:
|
||||
|
||||
- [`AbuseReportDiscussion`](#abusereportdiscussion)
|
||||
- [`Discussion`](#discussion)
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="basediscussioninterfacecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the discussion's creation. |
|
||||
| <a id="basediscussioninterfaceid"></a>`id` | [`DiscussionID!`](#discussionid) | ID of the discussion. |
|
||||
| <a id="basediscussioninterfacereplyid"></a>`replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to the discussion. |
|
||||
| <a id="basediscussioninterfaceresolvable"></a>`resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
|
||||
| <a id="basediscussioninterfaceresolved"></a>`resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
|
||||
| <a id="basediscussioninterfaceresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
|
||||
| <a id="basediscussioninterfaceresolvedby"></a>`resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
|
||||
|
||||
#### `BaseHeaderInterface`
|
||||
|
||||
Implementations:
|
||||
|
|
@ -40017,6 +40126,32 @@ Implementations:
|
|||
| <a id="baseheaderinterfacekey"></a>`key` | [`String!`](#string) | Key of the header. |
|
||||
| <a id="baseheaderinterfacevalue"></a>`value` | [`String!`](#string) | Value of the header. |
|
||||
|
||||
#### `BaseNoteInterface`
|
||||
|
||||
Implementations:
|
||||
|
||||
- [`AbuseReportNote`](#abusereportnote)
|
||||
- [`Note`](#note)
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="basenoteinterfaceauthor"></a>`author` | [`UserCore`](#usercore) | User who wrote the note. |
|
||||
| <a id="basenoteinterfaceawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of emoji reactions associated with the note. (see [Connections](#connections)) |
|
||||
| <a id="basenoteinterfacebody"></a>`body` | [`String!`](#string) | Content of the note. |
|
||||
| <a id="basenoteinterfacebodyfirstlinehtml"></a>`bodyFirstLineHtml` | [`String!`](#string) | First line of the note content. |
|
||||
| <a id="basenoteinterfacebodyhtml"></a>`bodyHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of the content of the note. |
|
||||
| <a id="basenoteinterfacecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the note creation. |
|
||||
| <a id="basenoteinterfacelasteditedat"></a>`lastEditedAt` | [`Time`](#time) | Timestamp when note was last edited. |
|
||||
| <a id="basenoteinterfacelasteditedby"></a>`lastEditedBy` | [`UserCore`](#usercore) | User who last edited the note. |
|
||||
| <a id="basenoteinterfaceresolvable"></a>`resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
|
||||
| <a id="basenoteinterfaceresolved"></a>`resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
|
||||
| <a id="basenoteinterfaceresolvedat"></a>`resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
|
||||
| <a id="basenoteinterfaceresolvedby"></a>`resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
|
||||
| <a id="basenoteinterfaceupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of the note's last activity. |
|
||||
| <a id="basenoteinterfaceurl"></a>`url` | [`String`](#string) | URL to view the note in the Web UI. |
|
||||
|
||||
#### `CiVariable`
|
||||
|
||||
Implementations:
|
||||
|
|
@ -40211,7 +40346,6 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
|
|||
|
||||
Implementations:
|
||||
|
||||
- [`AbuseReport`](#abusereport)
|
||||
- [`AlertManagementAlert`](#alertmanagementalert)
|
||||
- [`BoardEpic`](#boardepic)
|
||||
- [`Design`](#design)
|
||||
|
|
@ -40288,6 +40422,8 @@ Implementations:
|
|||
|
||||
Implementations:
|
||||
|
||||
- [`AbuseReportDiscussion`](#abusereportdiscussion)
|
||||
- [`AbuseReportNote`](#abusereportnote)
|
||||
- [`Discussion`](#discussion)
|
||||
- [`Note`](#note)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ DETAILS:
|
|||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Four types of pipelines exist:
|
||||
Multiple types of pipelines can run in a project, including:
|
||||
|
||||
- Branch pipelines
|
||||
- Tag pipelines
|
||||
- Merge request pipelines
|
||||
- Merge result pipelines
|
||||
- Merge trains
|
||||
|
|
@ -32,7 +33,24 @@ Branch pipelines:
|
|||
- Run when you push a new commit to a branch.
|
||||
- Have access to [some predefined variables](../variables/predefined_variables.md).
|
||||
- Have access to [protected variables](../variables/index.md#protect-a-cicd-variable)
|
||||
and [protected runners](../runners/configure_runners.md#prevent-runners-from-revealing-sensitive-information).
|
||||
and [protected runners](../runners/configure_runners.md#prevent-runners-from-revealing-sensitive-information)
|
||||
when the branch is a [protected branch](../../user/project/protected_branches.md).
|
||||
|
||||
## Tag pipeline
|
||||
|
||||
A pipeline can run every time you create or push a new [tag](../../user/project/repository/tags/index.md).
|
||||
|
||||
This type of pipeline is called a *tag pipeline*.
|
||||
|
||||
This pipeline runs by default. No configuration is required.
|
||||
|
||||
Tag pipelines:
|
||||
|
||||
- Run when you create/push a new tag to your repository.
|
||||
- Have access to [some predefined variables](../variables/predefined_variables.md).
|
||||
- Have access to [protected variables](../variables/index.md#protect-a-cicd-variable)
|
||||
and [protected runners](../runners/configure_runners.md#prevent-runners-from-revealing-sensitive-information)
|
||||
when the tag is a [protected tag](../../user/project/protected_tags.md).
|
||||
|
||||
## Merge request pipeline
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Prerequisites:
|
|||
|
||||
- Save your Akeyless access ID as a [CI/CD variable in your GitLab project](../variables/index.md#for-a-project)
|
||||
named `AKEYLESS_ACCESS_ID`.
|
||||
- This integration only supports [static secrets](https://docs.akeyless.io/docs/static-secrets).
|
||||
|
||||
To retrieve secrets from Akeyless, review the CI/CD configuration example that matches
|
||||
your use case. The `akeyless:name` keyword can contain any secrets type.
|
||||
|
|
|
|||
|
|
@ -117,17 +117,6 @@ For more information, see the:
|
|||
- [Deprecations and removals documentation](../../update/deprecations.md#non-expiring-access-tokens).
|
||||
- [Deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369122).
|
||||
|
||||
## 17.4.0
|
||||
|
||||
- Starting with GitLab 17.4, new GitLab installations have a different database schema regarding ID columns.
|
||||
- All previous integer (32 bits) ID columns (for example columns like `id`, `%_id`, `%_ids`) are now created as `bigint` (64 bits).
|
||||
- Existing installations will migrate from 32 bit to 64 bit integers in later releases when database migrations ship to perform this change.
|
||||
- If you are building a new GitLab environment to test upgrades, install GitLab 17.3 or earlier to get
|
||||
the same integer types as your existing environments. You can then upgrade to later releases to run the same
|
||||
database migrations as your existing environments. This isn't necessary if you're restoring from backup into the
|
||||
new environment as the database restore removes the existing database schema definition and uses the definition
|
||||
that's stored as part of the backup.
|
||||
|
||||
## Issues to be aware of when upgrading from 17.1 and earlier
|
||||
|
||||
- If the customer is using GitLab Duo and upgrading to GitLab 17.2.3 or earlier, they must do both of the following:
|
||||
|
|
@ -211,6 +200,17 @@ bits with a `certificate key too weak` error message.
|
|||
Check the [GitLab documentation on securing your installation](../../security/index.md).
|
||||
for more details.
|
||||
|
||||
## 17.4.0
|
||||
|
||||
- Starting with GitLab 17.4, new GitLab installations have a different database schema regarding ID columns.
|
||||
- All previous integer (32 bits) ID columns (for example columns like `id`, `%_id`, `%_ids`) are now created as `bigint` (64 bits).
|
||||
- Existing installations will migrate from 32 bit to 64 bit integers in later releases when database migrations ship to perform this change.
|
||||
- If you are building a new GitLab environment to test upgrades, install GitLab 17.3 or earlier to get
|
||||
the same integer types as your existing environments. You can then upgrade to later releases to run the same
|
||||
database migrations as your existing environments. This isn't necessary if you're restoring from backup into the
|
||||
new environment as the database restore removes the existing database schema definition and uses the definition
|
||||
that's stored as part of the backup.
|
||||
|
||||
## 17.3.0
|
||||
|
||||
- Git 2.45.0 and later is required by Gitaly. For installations from source, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ Audit event types belong to the following product categories.
|
|||
|
||||
| Name | Description | Saved to database | Streamed | Introduced in | Scope |
|
||||
|:------------|:------------|:------------------|:---------|:--------------|:--------------|
|
||||
| [`self_hosted_model_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321) | A new self-hosted model configuration was destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance, User |
|
||||
| [`self_hosted_model_feature_changed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489) | A self-hosted model feature had its configuration changed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/463215) | Project |
|
||||
| [`self_hosted_model_terms_accepted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165480) | Terms for usage of self-hosted models were accepted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance, User |
|
||||
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ After you set up your identity provider to work with GitLab, you must configure
|
|||
- In GitLab 17.4 and later, **Disable password authentication for enterprise users**.
|
||||
For more information, see the [Disable password authentication for enterprise users documentation](#disable-password-authentication-for-enterprise-users).
|
||||
- **Enforce SSO-only authentication for web activity for this group**.
|
||||
- **Enforce SSO-only authentication for Git activity for this group**.
|
||||
- **Enforce SSO-only authentication for Git and Dependency Proxy activity for this group**.
|
||||
For more information, see the [SSO enforcement documentation](#sso-enforcement).
|
||||
1. Select **Save changes**.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ module HamlLint
|
|||
record_lint(node, "anchor (#{match[:anchor]}) is missing in: #{path_to_file}")
|
||||
end
|
||||
|
||||
record_lint(node, "remove .md extension from the link: #{link}") if link.end_with?('.md')
|
||||
record_lint(node, "add .md extension to the link: #{link}") unless link.end_with?('.md')
|
||||
end
|
||||
|
||||
def extract_link_and_anchor(ast_tree)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ module Banzai
|
|||
include Gitlab::Utils::StrongMemoize
|
||||
extend Gitlab::Utils::SanitizeNodeLink
|
||||
|
||||
RENDER_TIMEOUT = 5.seconds
|
||||
|
||||
UNSAFE_PROTOCOLS = %w[data javascript vbscript].freeze
|
||||
|
||||
def call
|
||||
|
|
@ -57,9 +55,8 @@ module Banzai
|
|||
allowlist[:attributes]['img'].push('data-diagram-src')
|
||||
|
||||
# Allow any protocol in `a` elements
|
||||
# and then remove links with unsafe protocols
|
||||
# and then remove links with unsafe protocols in SanitizeLinkFilter
|
||||
allowlist[:protocols].delete('a')
|
||||
allowlist[:transformers].push(self.class.method(:sanitize_unsafe_links))
|
||||
|
||||
# Remove `rel` attribute from `a` elements
|
||||
allowlist[:transformers].push(self.class.remove_rel)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
module Banzai
|
||||
module Filter
|
||||
# HTML Filter to modify the attributes of external links
|
||||
# HTML Filter to modify the attributes of external links.
|
||||
# This is considered a sanitization filter.
|
||||
class ExternalLinkFilter < HTML::Pipeline::Filter
|
||||
prepend Concerns::TimeoutFilterHandler
|
||||
prepend Concerns::PipelineTimingCheck
|
||||
|
||||
SCHEMES = ['http', 'https', nil].freeze
|
||||
RTLO = "\u202E"
|
||||
|
|
@ -46,7 +46,7 @@ module Banzai
|
|||
# partial un-sanitized results.
|
||||
# It's ok to allow any following filters to run since this is safe HTML.
|
||||
def returned_timeout_value
|
||||
HTML::Pipeline.parse(COMPLEX_MARKDOWN_MESSAGE)
|
||||
HTML::Pipeline.parse(Banzai::Filter::SanitizeLinkFilter::TIMEOUT_MARKDOWN_MESSAGE)
|
||||
end
|
||||
|
||||
# if this is a link to a proxied image, then `src` is already the correct
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ module Banzai
|
|||
def sanitized_content_tag(name, content, options = {})
|
||||
html = content_tag(name, content, options)
|
||||
node = Banzai::Filter::SanitizationFilter.new(html).call
|
||||
node = Banzai::Filter::SanitizeLinkFilter.new(node).call
|
||||
|
||||
node&.children&.first
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ module Banzai
|
|||
prepend Concerns::TimeoutFilterHandler
|
||||
prepend Concerns::PipelineTimingCheck
|
||||
|
||||
RENDER_TIMEOUT = 2.seconds
|
||||
|
||||
def initialize(doc, context = nil, result = nil)
|
||||
super
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Banzai
|
||||
module Filter
|
||||
# Validate links and remove unsafe protocols.
|
||||
# This can be intensive, so it was split from BaseSanitizationFilter in order
|
||||
# for it to have its own time period.
|
||||
class SanitizeLinkFilter < HTML::Pipeline::Filter
|
||||
prepend Concerns::TimeoutFilterHandler
|
||||
include Gitlab::Utils::SanitizeNodeLink
|
||||
|
||||
# [href], [src], [data-src], [data-canonical-src]
|
||||
CSS = Gitlab::Utils::SanitizeNodeLink::ATTRS_TO_SANITIZE.map { |x| "[#{x}]" }.join(', ')
|
||||
XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
|
||||
|
||||
TIMEOUT_MARKDOWN_MESSAGE =
|
||||
<<~HTML
|
||||
<p>Timeout while sanitizing links - rendering aborted. Please reduce the number of links if possible.</p>
|
||||
HTML
|
||||
|
||||
def call
|
||||
doc.xpath(self.class::XPATH).each do |el|
|
||||
sanitize_unsafe_links({ node: el })
|
||||
end
|
||||
|
||||
doc
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_timeout
|
||||
SANITIZATION_RENDER_TIMEOUT
|
||||
end
|
||||
|
||||
# If sanitization times out, we can not return partial un-sanitized results.
|
||||
# It's ok to allow any following filters to run since this is safe HTML.
|
||||
def returned_timeout_value
|
||||
HTML::Pipeline.parse(TIMEOUT_MARKDOWN_MESSAGE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -17,8 +17,8 @@ module Banzai
|
|||
# This is a small extension to the CommonMark spec. If they start allowing
|
||||
# spaces in urls, we could then remove this filter.
|
||||
#
|
||||
# Note: Filter::SanitizationFilter should always be run sometime after this filter
|
||||
# to prevent XSS attacks
|
||||
# Note: Filter::SanitizationFilter/Filter::SanitizeLinkFilter should always be run sometime
|
||||
# after this filter to prevent XSS attacks
|
||||
#
|
||||
class SpacedLinkFilter < HTML::Pipeline::Filter
|
||||
prepend Concerns::PipelineTimingCheck
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Banzai
|
|||
def self.filters
|
||||
FilterArray[
|
||||
Filter::AsciiDocSanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::CodeLanguageFilter,
|
||||
Filter::GollumTagsFilter,
|
||||
Filter::WikiLinkGollumFilter,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module Banzai
|
|||
Filter::BlockquoteFenceLegacyFilter,
|
||||
Filter::MarkdownFilter,
|
||||
Filter::BroadcastMessageSanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::EmojiFilter,
|
||||
Filter::ColorFilter,
|
||||
Filter::AutolinkFilter,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module Banzai
|
|||
@filters ||= FilterArray[
|
||||
Filter::HtmlEntityFilter,
|
||||
Filter::SanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::EmojiFilter
|
||||
]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ module Banzai
|
|||
@filters ||= FilterArray[
|
||||
Filter::CodeLanguageFilter,
|
||||
Filter::PlantumlFilter,
|
||||
# Must always be before the SanitizationFilter to prevent XSS attacks
|
||||
# Must always be before the SanitizationFilter/SanitizeLinkFilter to prevent XSS attacks
|
||||
Filter::SpacedLinkFilter,
|
||||
Filter::SanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::EscapedCharFilter,
|
||||
Filter::KrokiFilter,
|
||||
Filter::GollumTagsFilter,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module Banzai
|
|||
@filters ||= FilterArray[
|
||||
*super,
|
||||
Filter::SanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
*Banzai::Pipeline::GfmPipeline.reference_filters,
|
||||
Filter::EmojiFilter,
|
||||
Filter::ExternalLinkFilter,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Banzai
|
|||
def self.filters
|
||||
@filters ||= FilterArray[
|
||||
Filter::SanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::References::LabelReferenceFilter
|
||||
]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Banzai
|
|||
def self.filters
|
||||
@filters ||= FilterArray[
|
||||
Filter::SanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::CodeLanguageFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
Filter::ExternalLinkFilter,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module Banzai
|
|||
@filters ||= FilterArray[
|
||||
Filter::HtmlEntityFilter,
|
||||
Filter::SanitizationFilter,
|
||||
Filter::SanitizeLinkFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
Filter::EmojiFilter,
|
||||
Filter::CustomEmojiFilter,
|
||||
|
|
|
|||
|
|
@ -90,12 +90,16 @@ module Gitlab
|
|||
.batch(key: context.user) do |projects, loader, args|
|
||||
projects.uniq.each do |project|
|
||||
context.logger.instrument(:config_file_project_validate_access) do
|
||||
loader.call(project, Ability.allowed?(args[:key], :download_code, project))
|
||||
loader.call(project, project_access_allowed?(args[:key], project))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def project_access_allowed?(user, project)
|
||||
Ability.allowed?(user, :download_code, project)
|
||||
end
|
||||
|
||||
def sha
|
||||
return if project.nil?
|
||||
|
||||
|
|
@ -179,3 +183,5 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Ci::Config::External::File::Project.prepend_mod
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
delegate :content, :source, :url, to: :@config, allow_nil: true
|
||||
delegate :content, :source, :url, :pipeline_policy_context, to: :@config, allow_nil: true
|
||||
delegate :internal_include_prepended?, to: :@config
|
||||
|
||||
def exists?
|
||||
|
|
|
|||
|
|
@ -42,10 +42,12 @@ module Gitlab
|
|||
nil
|
||||
end
|
||||
|
||||
attr_reader :pipeline_policy_context
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge, :triggered_for_branch,
|
||||
:ref, :pipeline_policy_context
|
||||
:ref
|
||||
|
||||
def ci_config_path
|
||||
@ci_config_path ||= project.ci_config_path_or_default
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class Experiment
|
||||
module Rollout
|
||||
class Feature < Percent
|
||||
# For this rollout strategy to consider an experiment as enabled, we
|
||||
# must:
|
||||
#
|
||||
# - have a feature flag yaml file that declares it.
|
||||
# - be in an environment that permits it.
|
||||
# - not have rolled out the feature flag at all (no percent of actors,
|
||||
# no inclusions, etc.)
|
||||
def enabled?
|
||||
return false unless feature_flag_defined?
|
||||
return false unless available?
|
||||
return false unless ::Feature.enabled?(:gitlab_experiment, type: :ops)
|
||||
|
||||
feature_flag_instance.state != :off
|
||||
end
|
||||
|
||||
# For assignment we first check to see if our feature flag is enabled
|
||||
# for "self". This is done by calling `#flipper_id` (used behind the
|
||||
# scenes by `Feature`). By default this is our `experiment.id` (or more
|
||||
# specifically, the context key, which is an anonymous SHA generated
|
||||
# using the details of an experiment.
|
||||
#
|
||||
# If the `Feature.enabled?` check is false, we return nil implicitly,
|
||||
# which will assign the control. Otherwise we call super, which will
|
||||
# assign a variant based on our provided distribution rules.
|
||||
# Otherwise we will assign a variant evenly across the behaviours without control.
|
||||
def execute_assignment
|
||||
super if ::Feature.enabled?(feature_flag_name, self, type: :experiment)
|
||||
end
|
||||
|
||||
# This is what's provided to the `Feature.enabled?` call that will be
|
||||
# used to determine experiment inclusion. An experiment may provide an
|
||||
# override for this method to make the experiment work on user, group,
|
||||
# or projects.
|
||||
#
|
||||
# For example, when running an experiment on a project, you could make
|
||||
# the experiment assignable by project (using chatops) by implementing
|
||||
# a `flipper_id` method in the experiment:
|
||||
#
|
||||
# def flipper_id
|
||||
# context.project.flipper_id
|
||||
# end
|
||||
#
|
||||
# Or even cleaner, simply delegate it:
|
||||
#
|
||||
# delegate :flipper_id, to: -> { context.project }
|
||||
def flipper_id
|
||||
return experiment.flipper_id if experiment.respond_to?(:flipper_id)
|
||||
|
||||
"Experiment;#{id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available?
|
||||
ApplicationExperiment.available?
|
||||
end
|
||||
|
||||
def feature_flag_instance
|
||||
::Feature.get(feature_flag_name) # rubocop:disable Gitlab/AvoidFeatureGet -- We are using at a lower layer here in experiment framework
|
||||
end
|
||||
|
||||
def feature_flag_defined?
|
||||
::Feature::Definition.get(feature_flag_name).present?
|
||||
end
|
||||
|
||||
def feature_flag_name
|
||||
experiment.name.tr('/', '_')
|
||||
end
|
||||
|
||||
def behavior_names
|
||||
super - [:control]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class ExperimentFeatureRollout < Gitlab::Experiment::Rollout::Percent
|
||||
# For this rollout strategy to consider an experiment as enabled, we
|
||||
# must:
|
||||
#
|
||||
# - have a feature flag yaml file that declares it.
|
||||
# - be in an environment that permits it.
|
||||
# - not have rolled out the feature flag at all (no percent of actors,
|
||||
# no inclusions, etc.)
|
||||
def enabled?
|
||||
return false unless feature_flag_defined?
|
||||
return false unless available?
|
||||
return false unless ::Feature.enabled?(:gitlab_experiment, type: :ops)
|
||||
|
||||
feature_flag_instance.state != :off
|
||||
end
|
||||
|
||||
# For assignment we first check to see if our feature flag is enabled
|
||||
# for "self". This is done by calling `#flipper_id` (used behind the
|
||||
# scenes by `Feature`). By default this is our `experiment.id` (or more
|
||||
# specifically, the context key, which is an anonymous SHA generated
|
||||
# using the details of an experiment.
|
||||
#
|
||||
# If the `Feature.enabled?` check is false, we return nil implicitly,
|
||||
# which will assign the control. Otherwise we call super, which will
|
||||
# assign a variant based on our provided distribution rules.
|
||||
# Otherwise we will assign a variant evenly across the behaviours without control.
|
||||
def execute_assignment
|
||||
super if ::Feature.enabled?(feature_flag_name, self, type: :experiment)
|
||||
end
|
||||
|
||||
# This is what's provided to the `Feature.enabled?` call that will be
|
||||
# used to determine experiment inclusion. An experiment may provide an
|
||||
# override for this method to make the experiment work on user, group,
|
||||
# or projects.
|
||||
#
|
||||
# For example, when running an experiment on a project, you could make
|
||||
# the experiment assignable by project (using chatops) by implementing
|
||||
# a `flipper_id` method in the experiment:
|
||||
#
|
||||
# def flipper_id
|
||||
# context.project.flipper_id
|
||||
# end
|
||||
#
|
||||
# Or even cleaner, simply delegate it:
|
||||
#
|
||||
# delegate :flipper_id, to: -> { context.project }
|
||||
def flipper_id
|
||||
return experiment.flipper_id if experiment.respond_to?(:flipper_id)
|
||||
|
||||
"Experiment;#{id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available?
|
||||
ApplicationExperiment.available?
|
||||
end
|
||||
|
||||
def feature_flag_instance
|
||||
::Feature.get(feature_flag_name) # rubocop:disable Gitlab/AvoidFeatureGet -- We are using at a lower layer here in experiment framework
|
||||
end
|
||||
|
||||
def feature_flag_defined?
|
||||
::Feature::Definition.get(feature_flag_name).present?
|
||||
end
|
||||
|
||||
def feature_flag_name
|
||||
experiment.name.tr('/', '_')
|
||||
end
|
||||
|
||||
def behavior_names
|
||||
super - [:control]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -38,6 +38,8 @@ module Gitlab
|
|||
instance.milestone_url(object, **options)
|
||||
when Note
|
||||
note_url(object, **options)
|
||||
when AntiAbuse::Reports::Note
|
||||
abuse_report_note_url(object, **options)
|
||||
when Release
|
||||
instance.release_url(object, **options)
|
||||
when Organizations::Organization
|
||||
|
|
@ -102,6 +104,10 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def abuse_report_note_url(note, **options)
|
||||
instance.admin_abuse_report_url(note.abuse_report, anchor: dom_id(note), **options)
|
||||
end
|
||||
|
||||
def snippet_url(snippet, **options)
|
||||
if options[:file].present?
|
||||
file, ref = options.values_at(:file, :ref)
|
||||
|
|
|
|||
|
|
@ -2276,9 +2276,6 @@ msgstr ""
|
|||
msgid "AI|Explain with AI"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI|Explain your rating to help us improve! (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI|For example: Organizations should be able to forecast into the future by using value stream analytics charts. This feature would help them understand how their metrics are trending."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13655,7 +13652,7 @@ msgstr ""
|
|||
msgid "Commit message"
|
||||
msgstr ""
|
||||
|
||||
msgid "Commit message generated by AI"
|
||||
msgid "Commit message generated by GitLab Duo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
|
||||
|
|
@ -60375,9 +60372,6 @@ msgstr ""
|
|||
msgid "Vulnerability|Comments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Could not load prompt."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Crash address:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60408,9 +60402,6 @@ msgstr ""
|
|||
msgid "Vulnerability|Evidence:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Explain this vulnerability"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|External Security Report"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60423,12 +60414,6 @@ msgstr ""
|
|||
msgid "Vulnerability|GitLab Security Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|GitLab has identified sensitive strings in the code snippet for the AI prompt, indicating a possible leaked secret. Please review your code before utilizing the Explain This Vulnerability feature. If you still wish to proceed and send the %{linkStart}code%{linkEnd} to the AI, click the checkbox below."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Hide prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Identifier"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60462,9 +60447,6 @@ msgstr ""
|
|||
msgid "Vulnerability|Project:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Providing the source code improves the response quality. If security is a concern, you can send basic vulnerability info for a generic example."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Remove identifier row"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60474,9 +60456,6 @@ msgstr ""
|
|||
msgid "Vulnerability|Request/Response"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Response generated by AI"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Scanner:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60489,12 +60468,6 @@ msgstr ""
|
|||
msgid "Vulnerability|Select a severity"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Send code with prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Sending code to AI"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Sent request:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60507,9 +60480,6 @@ msgstr ""
|
|||
msgid "Vulnerability|Severity:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Show prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Something went wrong while trying to get the source file."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60558,21 +60528,12 @@ msgstr ""
|
|||
msgid "Vulnerability|Vulnerable method:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Warning: possible secrets detected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|What is code flow?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|You can also %{message}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|code_flow"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|use AI by asking GitLab Duo Chat to explain this vulnerability and suggest a solution"
|
||||
msgstr ""
|
||||
|
||||
msgid "WARNING:"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
"@gitlab/query-language": "^0.0.5-a-20240903",
|
||||
"@gitlab/svgs": "3.112.0",
|
||||
"@gitlab/ui": "91.1.2",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20240816130114",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20240909013227",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@rails/actioncable": "7.0.8-4",
|
||||
"@rails/ujs": "7.0.8-4",
|
||||
|
|
|
|||
|
|
@ -2950,5 +2950,5 @@ index 6a16dd1..99b1df4 100644
|
|||
- const parentOrigin = searchParams.get('parentOrigin') || window.origin;
|
||||
+ const parentOrigin = window.origin;
|
||||
const salt = searchParams.get('salt');
|
||||
|
||||
|
||||
(async function () {
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :instance_integration, class: 'Integrations::InstanceIntegration' do
|
||||
type { 'Integrations::InstanceIntegration' }
|
||||
end
|
||||
end
|
||||
|
|
@ -59,7 +59,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
|
|||
fill_in 'pin_code', with: '123'
|
||||
click_button 'Register with two-factor app'
|
||||
|
||||
expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication_troubleshooting'))
|
||||
expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -93,21 +93,8 @@ describe('Abuse Report Note', () => {
|
|||
});
|
||||
|
||||
describe('Editing', () => {
|
||||
it('should show edit button when resolveNote is true', () => {
|
||||
createComponent({
|
||||
note: { ...mockNote, userPermissions: { resolveNote: true } },
|
||||
});
|
||||
|
||||
expect(findNoteActions().props()).toMatchObject({
|
||||
showEditButton: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show edit button when resolveNote is false', () => {
|
||||
createComponent({
|
||||
note: { ...mockNote, userPermissions: { resolveNote: false } },
|
||||
});
|
||||
|
||||
// this should be changed: https://gitlab.com/gitlab-org/gitlab/-/issues/481897
|
||||
it('should not show edit button', () => {
|
||||
expect(findNoteActions().props()).toMatchObject({
|
||||
showEditButton: false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -318,6 +318,171 @@ export const mockDiscussionWithReplies = [
|
|||
},
|
||||
];
|
||||
|
||||
export const mockAbuseReportDiscussionWithNoReplies = [
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::Note/1',
|
||||
body: 'Comment 1',
|
||||
bodyHtml: '\u003cp data-sourcepos="1:1-1:9" dir="auto"\u003eComment 1\u003c/p\u003e',
|
||||
createdAt: '2023-10-19T06:11:13Z',
|
||||
lastEditedAt: null,
|
||||
url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_1',
|
||||
resolved: false,
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
webUrl: 'http://127.0.0.1:3000/root',
|
||||
webPath: '/root',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
lastEditedBy: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/055af96ab917175219aec8739c911277b18ea41d',
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::Note/1',
|
||||
__typename: 'Note',
|
||||
},
|
||||
],
|
||||
__typename: 'AbuseReportNoteConnection',
|
||||
},
|
||||
__typename: 'AbuseReportDiscussion',
|
||||
},
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
];
|
||||
export const mockAbuseReportDiscussionWithReplies = [
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
|
||||
body: 'Comment 2',
|
||||
bodyHtml: '\u003cp data-sourcepos="1:1-1:9" dir="auto"\u003eComment 2\u003c/p\u003e',
|
||||
createdAt: '2023-10-20T07:47:21Z',
|
||||
lastEditedAt: '2023-10-20T07:47:42Z',
|
||||
url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_2',
|
||||
resolved: false,
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
webUrl: 'http://127.0.0.1:3000/root',
|
||||
webPath: '/root',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
lastEditedBy: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
],
|
||||
__typename: 'AbuseReportNoteConnection',
|
||||
},
|
||||
__typename: 'AbuseReportDiscussion',
|
||||
},
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
|
||||
body: 'Reply comment 1',
|
||||
bodyHtml: '\u003cp data-sourcepos="1:1-1:15" dir="auto"\u003eReply comment 1\u003c/p\u003e',
|
||||
createdAt: '2023-10-20T07:47:42Z',
|
||||
lastEditedAt: '2023-10-20T07:47:42Z',
|
||||
url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_3',
|
||||
resolved: false,
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
webUrl: 'http://127.0.0.1:3000/root',
|
||||
webPath: '/root',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
lastEditedBy: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
],
|
||||
__typename: 'AbuseReportNoteConnection',
|
||||
},
|
||||
__typename: 'AbuseReportDiscussion',
|
||||
},
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
|
||||
body: 'Reply comment 2',
|
||||
bodyHtml: '\u003cp data-sourcepos="1:1-1:15" dir="auto"\u003eReply comment 2\u003c/p\u003e',
|
||||
createdAt: '2023-10-20T08:26:51Z',
|
||||
lastEditedAt: '2023-10-20T08:26:51Z',
|
||||
url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_4',
|
||||
resolved: false,
|
||||
author: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
webUrl: 'http://127.0.0.1:3000/root',
|
||||
webPath: '/root',
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
lastEditedBy: null,
|
||||
discussion: {
|
||||
id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
|
||||
notes: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
],
|
||||
__typename: 'AbuseReportNoteConnection',
|
||||
},
|
||||
__typename: 'AbuseReportDiscussion',
|
||||
},
|
||||
__typename: 'AbuseReportNote',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockNotesByIdResponse = {
|
||||
data: {
|
||||
abuseReport: {
|
||||
|
|
@ -327,22 +492,23 @@ export const mockNotesByIdResponse = {
|
|||
{
|
||||
id: 'gid://gitlab/Discussion/055af96ab917175219aec8739c911277b18ea41d',
|
||||
replyId:
|
||||
'gid://gitlab/IndividualNoteDiscussion/055af96ab917175219aec8739c911277b18ea41d',
|
||||
'gid://gitlab/AntiAbuse::Reports::IndividualNoteDiscussion/055af96ab917175219aec8739c911277b18ea41d',
|
||||
notes: {
|
||||
nodes: mockDiscussionWithNoReplies,
|
||||
__typename: 'NoteConnection',
|
||||
nodes: mockAbuseReportDiscussionWithNoReplies,
|
||||
__typename: 'AbuseReportNoteConnection',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
|
||||
replyId: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
|
||||
replyId:
|
||||
'gid://gitlab/AntiAbuse::Reports::Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
|
||||
notes: {
|
||||
nodes: mockDiscussionWithReplies,
|
||||
__typename: 'NoteConnection',
|
||||
nodes: mockAbuseReportDiscussionWithReplies,
|
||||
__typename: 'AbuseReportNoteConnection',
|
||||
},
|
||||
},
|
||||
],
|
||||
__typename: 'DiscussionConnection',
|
||||
__typename: 'AbuseReportDiscussionConnection',
|
||||
},
|
||||
__typename: 'AbuseReport',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { nextTick } from 'vue';
|
||||
|
|
@ -10,7 +10,9 @@ import ActionComponent from '~/ci/common/private/job_action_component.vue';
|
|||
describe('pipeline graph action component', () => {
|
||||
let wrapper;
|
||||
let mock;
|
||||
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
|
||||
const defaultProps = {
|
||||
tooltipText: 'bar',
|
||||
|
|
@ -69,11 +71,21 @@ describe('pipeline graph action component', () => {
|
|||
expect(wrapper.emitted().pipelineActionRequestComplete).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a loading icon while waiting for request', async () => {
|
||||
it('displays a loading icon/disabled button while waiting for request', async () => {
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
expect(findButton().props('disabled')).toBe(false);
|
||||
|
||||
findButton().trigger('click');
|
||||
|
||||
await nextTick();
|
||||
expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
expect(findButton().props('disabled')).toBe(true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
expect(findButton().props('disabled')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,24 @@ describe('asset patching in @gitlab/web-ide', () => {
|
|||
});
|
||||
const htmlChildren = allChildren.filter((x) => x.endsWith('.html'));
|
||||
|
||||
/**
|
||||
* ## What in the world is this test doing!?
|
||||
*
|
||||
* This test was introduced when we were fixing a [security vulnerability][1] related to GitLab self-hosting
|
||||
* problematic `.html` files. These files could be exploited through an `iframe` on an `evil.com` and will
|
||||
* assume the user's cookie authentication. Boom!
|
||||
*
|
||||
* ## How do I know if an `.html` file is vulnerable?
|
||||
*
|
||||
* - The `.html` file used the `postMessage` API and allowed any `origin` which enabled any external site to
|
||||
* open it in an `iframe` and communicate to it.
|
||||
* - The `iframe` exposed some internal VSCode message bus that could allow arbitrary requests. So watch out for
|
||||
* `fetch`.
|
||||
*
|
||||
* [1]: https://gitlab.com/gitlab-org/security/gitlab-web-ide-vscode-fork/-/issues/1#note_1905417620
|
||||
*
|
||||
* ========== If expectation fails and you can't see the full comment... LOOK UP! ==============
|
||||
*/
|
||||
expect(htmlChildren).toEqual([
|
||||
// This is the only HTML file we expect and it's protected by the other test.
|
||||
'out/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html',
|
||||
|
|
@ -33,6 +51,9 @@ describe('asset patching in @gitlab/web-ide', () => {
|
|||
'extensions/microsoft-authentication/media/index.html',
|
||||
'extensions/gitlab-vscode-extension/webviews/security_finding/index.html',
|
||||
'extensions/gitlab-vscode-extension/webviews/gitlab_duo_chat/index.html',
|
||||
'extensions/gitlab-vscode-extension/assets/language-server/webviews/duo-workflow/index.html',
|
||||
'extensions/gitlab-vscode-extension/assets/language-server/webviews/duo-chat/index.html',
|
||||
'extensions/gitlab-vscode-extension/assets/language-server/webviews/chat/index.html',
|
||||
'extensions/github-authentication/media/index.html',
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['AbuseReport'], feature_category: :insider_threat do
|
||||
let(:fields) { %w[id labels discussions notes] }
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('AbuseReport') }
|
||||
|
||||
specify { expect(described_class).to have_graphql_fields(fields) }
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['AbuseReportDiscussion'], feature_category: :team_planning do
|
||||
include GraphqlHelpers
|
||||
|
||||
it 'exposes the expected fields' do
|
||||
expected_fields = %i[
|
||||
abuse_report
|
||||
created_at
|
||||
id
|
||||
notes
|
||||
reply_id
|
||||
resolvable
|
||||
resolved
|
||||
resolved_at
|
||||
resolved_by
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('AbuseReportDiscussion') }
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['AbuseReportNote'], feature_category: :team_planning do
|
||||
include GraphqlHelpers
|
||||
|
||||
it 'exposes the expected fields' do
|
||||
expected_fields = %i[
|
||||
author
|
||||
body
|
||||
body_html
|
||||
body_first_line_html
|
||||
award_emoji
|
||||
created_at
|
||||
discussion
|
||||
id
|
||||
resolvable
|
||||
resolved
|
||||
resolved_at
|
||||
resolved_by
|
||||
updated_at
|
||||
url
|
||||
last_edited_at
|
||||
last_edited_by
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('AbuseReportNote') }
|
||||
end
|
||||
|
|
@ -5,6 +5,16 @@ require 'spec_helper'
|
|||
RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
|
||||
include GraphqlHelpers
|
||||
|
||||
# rubocop:disable RSpec/FactoryBot/AvoidCreate -- we need the project and author for the test, id needed
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:user) { build_stubbed(:user) }
|
||||
|
||||
let_it_be(:note_text) { 'note body content' }
|
||||
let_it_be(:note) { create(:note, note: note_text, project: project) }
|
||||
# rubocop:enable RSpec/FactoryBot/AvoidCreate
|
||||
|
||||
let(:batch_loader) { instance_double(Gitlab::Graphql::Loaders::BatchModelLoader) }
|
||||
|
||||
it 'exposes the expected fields' do
|
||||
expected_fields = %i[
|
||||
author
|
||||
|
|
@ -42,12 +52,10 @@ RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
|
|||
specify { expect(described_class).to require_graphql_authorizations(:read_note) }
|
||||
|
||||
context 'when system note with issue_email_participants action', feature_category: :service_desk do
|
||||
let_it_be(:user) { build_stubbed(:user) }
|
||||
let_it_be(:email) { 'user@example.com' }
|
||||
let_it_be(:note_text) { "added #{email}" }
|
||||
# Create project and issue separately because we need to public project.
|
||||
# rubocop:disable RSpec/FactoryBot/AvoidCreate -- Notes::RenderService updates #note and #cached_markdown_version
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:note) do
|
||||
create(:note, :system, project: project, noteable: issue, author: Users::Internal.support_bot, note: note_text)
|
||||
|
|
@ -72,9 +80,6 @@ RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
|
|||
end
|
||||
|
||||
describe '#body_first_line_html' do
|
||||
let_it_be(:user) { build_stubbed(:user) }
|
||||
let_it_be(:project) { build(:project, :public) }
|
||||
|
||||
let(:note_text) { 'note body content' }
|
||||
let(:note) { build(:note, note: note_text, project: project) }
|
||||
|
||||
|
|
@ -109,4 +114,28 @@ RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project' do
|
||||
subject(:note_project) { resolve_field(:project, note, current_user: user) }
|
||||
|
||||
it 'fetches the project' do
|
||||
expect(Gitlab::Graphql::Loaders::BatchModelLoader).to receive(:new).with(Project, project.id)
|
||||
.and_return(batch_loader)
|
||||
expect(batch_loader).to receive(:find)
|
||||
|
||||
note_project
|
||||
end
|
||||
end
|
||||
|
||||
describe '#author' do
|
||||
subject(:note_author) { resolve_field(:author, note, current_user: user) }
|
||||
|
||||
it 'fetches the author' do
|
||||
expect(Gitlab::Graphql::Loaders::BatchModelLoader).to receive(:new).with(User, note.author.id)
|
||||
.and_return(batch_loader)
|
||||
expect(batch_loader).to receive(:find)
|
||||
|
||||
note_author
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ RSpec.describe Types::Notes::NoteableInterface do
|
|||
expect(described_class.resolve_type(build(:merge_request), {})).to eq(Types::MergeRequestType)
|
||||
expect(described_class.resolve_type(build(:design), {})).to eq(Types::DesignManagement::DesignType)
|
||||
expect(described_class.resolve_type(build(:alert_management_alert), {})).to eq(Types::AlertManagement::AlertType)
|
||||
expect(described_class.resolve_type(build(:abuse_report), {})).to eq(Types::AbuseReportType)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue