Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9f9dc2bc41
commit
842ac3526c
|
@ -805,29 +805,29 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
|
||||||
/doc/user/workspace/index.md @fneill
|
/doc/user/workspace/index.md @fneill
|
||||||
|
|
||||||
[Authentication and Authorization]
|
[Authentication and Authorization]
|
||||||
app/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/app/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/app/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/ee/app/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
config/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/config/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/config/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/ee/config/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
lib/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/lib/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/lib/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/ee/lib/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization
|
/ee/app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization
|
||||||
|
|
||||||
app/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/app/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/app/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/ee/app/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
config/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/config/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/config/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/ee/config/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
lib/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/lib/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/lib/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/ee/lib/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
/ee/app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization
|
||||||
|
|
||||||
app/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/app/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/app/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/ee/app/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
config/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/config/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/config/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/ee/config/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
lib/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/lib/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/lib/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/ee/lib/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
ee/app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization
|
/ee/app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
14.9.0
|
14.10.0-rc2
|
||||||
|
|
|
@ -23,6 +23,11 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
wrapTextNodes: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
|
@ -37,6 +42,7 @@ export default {
|
||||||
const initialData = {
|
const initialData = {
|
||||||
blobs: [{ path: this.blobPath, codeNavigationPath: this.codeNavigationPath }],
|
blobs: [{ path: this.blobPath, codeNavigationPath: this.codeNavigationPath }],
|
||||||
definitionPathPrefix: this.pathPrefix,
|
definitionPathPrefix: this.pathPrefix,
|
||||||
|
wrapTextNodes: this.wrapTextNodes,
|
||||||
};
|
};
|
||||||
this.setInitialData(initialData);
|
this.setInitialData(initialData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
...d,
|
...d,
|
||||||
definitionLineNumber: parseInt(d.definition_path?.split('#L').pop() || 0, 10),
|
definitionLineNumber: parseInt(d.definition_path?.split('#L').pop() || 0, 10),
|
||||||
};
|
};
|
||||||
addInteractionClass(path, d);
|
addInteractionClass({ path, d, wrapTextNodes: state.wrapTextNodes });
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
@ -34,7 +34,9 @@ export default {
|
||||||
},
|
},
|
||||||
showBlobInteractionZones({ state }, path) {
|
showBlobInteractionZones({ state }, path) {
|
||||||
if (state.data && state.data[path]) {
|
if (state.data && state.data[path]) {
|
||||||
Object.values(state.data[path]).forEach((d) => addInteractionClass(path, d));
|
Object.values(state.data[path]).forEach((d) =>
|
||||||
|
addInteractionClass({ path, d, wrapTextNodes: state.wrapTextNodes }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showDefinition({ commit, state }, { target: el }) {
|
showDefinition({ commit, state }, { target: el }) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
[types.SET_INITIAL_DATA](state, { blobs, definitionPathPrefix }) {
|
[types.SET_INITIAL_DATA](state, { blobs, definitionPathPrefix, wrapTextNodes }) {
|
||||||
state.blobs = blobs;
|
state.blobs = blobs;
|
||||||
state.definitionPathPrefix = definitionPathPrefix;
|
state.definitionPathPrefix = definitionPathPrefix;
|
||||||
|
state.wrapTextNodes = wrapTextNodes;
|
||||||
},
|
},
|
||||||
[types.REQUEST_DATA](state) {
|
[types.REQUEST_DATA](state) {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
|
|
|
@ -2,6 +2,7 @@ export default () => ({
|
||||||
blobs: [],
|
blobs: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
data: null,
|
data: null,
|
||||||
|
wrapTextNodes: false,
|
||||||
currentDefinition: null,
|
currentDefinition: null,
|
||||||
currentDefinitionPosition: null,
|
currentDefinitionPosition: null,
|
||||||
currentBlobPath: null,
|
currentBlobPath: null,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
const TEXT_NODE = 3;
|
||||||
|
|
||||||
|
const isTextNode = ({ nodeType }) => nodeType === TEXT_NODE;
|
||||||
|
|
||||||
|
const isBlank = (str) => !str || /^\s*$/.test(str);
|
||||||
|
|
||||||
|
const isMatch = (s1, s2) => !isBlank(s1) && s1.trim() === s2.trim();
|
||||||
|
|
||||||
|
const createSpan = (content) => {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.innerText = content;
|
||||||
|
return span;
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapSpacesWithSpans = (text) => text.replace(/ /g, createSpan(' ').outerHTML);
|
||||||
|
|
||||||
|
const wrapTextWithSpan = (el, text) => {
|
||||||
|
if (isTextNode(el) && isMatch(el.textContent, text)) {
|
||||||
|
const newEl = createSpan(text.trim());
|
||||||
|
el.replaceWith(newEl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapNodes = (text) => {
|
||||||
|
const wrapper = createSpan();
|
||||||
|
wrapper.innerHTML = wrapSpacesWithSpans(text);
|
||||||
|
wrapper.childNodes.forEach((el) => wrapTextWithSpan(el, text));
|
||||||
|
return wrapper.childNodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { wrapNodes, isTextNode };
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { wrapNodes, isTextNode } from './dom_utils';
|
||||||
|
|
||||||
export const cachedData = new Map();
|
export const cachedData = new Map();
|
||||||
|
|
||||||
export const getCurrentHoverElement = () => cachedData.get('current');
|
export const getCurrentHoverElement = () => cachedData.get('current');
|
||||||
export const setCurrentHoverElement = (el) => cachedData.set('current', el);
|
export const setCurrentHoverElement = (el) => cachedData.set('current', el);
|
||||||
|
|
||||||
export const addInteractionClass = (path, d) => {
|
export const addInteractionClass = ({ path, d, wrapTextNodes }) => {
|
||||||
const lineNumber = d.start_line + 1;
|
const lineNumber = d.start_line + 1;
|
||||||
const lines = document
|
const lines = document
|
||||||
.querySelector(`[data-path="${path}"]`)
|
.querySelector(`[data-path="${path}"]`)
|
||||||
|
@ -12,13 +14,24 @@ export const addInteractionClass = (path, d) => {
|
||||||
|
|
||||||
lines.forEach((line) => {
|
lines.forEach((line) => {
|
||||||
let charCount = 0;
|
let charCount = 0;
|
||||||
|
|
||||||
|
if (wrapTextNodes) {
|
||||||
|
line.childNodes.forEach((elm) => {
|
||||||
|
if (isTextNode(elm)) {
|
||||||
|
// Highlight.js does not wrap all text nodes by default
|
||||||
|
// We need all text nodes to be wrapped in order to append code nav attributes
|
||||||
|
elm.replaceWith(...wrapNodes(elm.textContent));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const el = [...line.childNodes].find(({ textContent }) => {
|
const el = [...line.childNodes].find(({ textContent }) => {
|
||||||
if (charCount === d.start_char) return true;
|
if (charCount === d.start_char) return true;
|
||||||
charCount += textContent.length;
|
charCount += textContent.length;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (el) {
|
if (el && !isTextNode(el)) {
|
||||||
el.setAttribute('data-char-index', d.start_char);
|
el.setAttribute('data-char-index', d.start_char);
|
||||||
el.setAttribute('data-line-index', d.start_line);
|
el.setAttribute('data-line-index', d.start_line);
|
||||||
el.classList.add('cursor-pointer', 'code-navigation', 'js-code-navigation');
|
el.classList.add('cursor-pointer', 'code-navigation', 'js-code-navigation');
|
||||||
|
|
|
@ -358,7 +358,7 @@ export default {
|
||||||
:loading="redirecting"
|
:loading="redirecting"
|
||||||
:disabled="redirecting"
|
:disabled="redirecting"
|
||||||
category="primary"
|
category="primary"
|
||||||
variant="success"
|
variant="confirm"
|
||||||
:href="newIncidentPath"
|
:href="newIncidentPath"
|
||||||
@click="navigateToCreateNewIncident"
|
@click="navigateToCreateNewIncident"
|
||||||
>
|
>
|
||||||
|
|
|
@ -171,6 +171,7 @@ export default {
|
||||||
data-testid="cancel-button"
|
data-testid="cancel-button"
|
||||||
icon="cancel"
|
icon="cancel"
|
||||||
:title="$options.CANCEL"
|
:title="$options.CANCEL"
|
||||||
|
:aria-label="$options.CANCEL"
|
||||||
:disabled="cancelBtnDisabled"
|
:disabled="cancelBtnDisabled"
|
||||||
@click="cancelJob()"
|
@click="cancelJob()"
|
||||||
/>
|
/>
|
||||||
|
@ -182,6 +183,7 @@ export default {
|
||||||
v-gl-modal-directive="$options.playJobModalId"
|
v-gl-modal-directive="$options.playJobModalId"
|
||||||
icon="play"
|
icon="play"
|
||||||
:title="$options.ACTIONS_START_NOW"
|
:title="$options.ACTIONS_START_NOW"
|
||||||
|
:aria-label="$options.ACTIONS_START_NOW"
|
||||||
data-testid="play-scheduled"
|
data-testid="play-scheduled"
|
||||||
/>
|
/>
|
||||||
<gl-modal
|
<gl-modal
|
||||||
|
@ -196,6 +198,7 @@ export default {
|
||||||
<gl-button
|
<gl-button
|
||||||
icon="time-out"
|
icon="time-out"
|
||||||
:title="$options.ACTIONS_UNSCHEDULE"
|
:title="$options.ACTIONS_UNSCHEDULE"
|
||||||
|
:aria-label="$options.ACTIONS_UNSCHEDULE"
|
||||||
:disabled="unscheduleBtnDisabled"
|
:disabled="unscheduleBtnDisabled"
|
||||||
data-testid="unschedule"
|
data-testid="unschedule"
|
||||||
@click="unscheduleJob()"
|
@click="unscheduleJob()"
|
||||||
|
@ -207,6 +210,7 @@ export default {
|
||||||
v-if="manualJobPlayable"
|
v-if="manualJobPlayable"
|
||||||
icon="play"
|
icon="play"
|
||||||
:title="$options.ACTIONS_PLAY"
|
:title="$options.ACTIONS_PLAY"
|
||||||
|
:aria-label="$options.ACTIONS_PLAY"
|
||||||
:disabled="playManualBtnDisabled"
|
:disabled="playManualBtnDisabled"
|
||||||
data-testid="play"
|
data-testid="play"
|
||||||
@click="playJob()"
|
@click="playJob()"
|
||||||
|
@ -215,6 +219,7 @@ export default {
|
||||||
v-else-if="isRetryable"
|
v-else-if="isRetryable"
|
||||||
icon="repeat"
|
icon="repeat"
|
||||||
:title="$options.ACTIONS_RETRY"
|
:title="$options.ACTIONS_RETRY"
|
||||||
|
:aria-label="$options.ACTIONS_RETRY"
|
||||||
:method="currentJobMethod"
|
:method="currentJobMethod"
|
||||||
:disabled="retryBtnDisabled"
|
:disabled="retryBtnDisabled"
|
||||||
data-testid="retry"
|
data-testid="retry"
|
||||||
|
@ -226,6 +231,7 @@ export default {
|
||||||
v-if="shouldDisplayArtifacts"
|
v-if="shouldDisplayArtifacts"
|
||||||
icon="download"
|
icon="download"
|
||||||
:title="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
|
:title="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
|
||||||
|
:aria-label="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
|
||||||
:href="artifactDownloadPath"
|
:href="artifactDownloadPath"
|
||||||
rel="nofollow"
|
rel="nofollow"
|
||||||
download
|
download
|
||||||
|
|
|
@ -66,6 +66,7 @@ export default {
|
||||||
text: this.secondaryText,
|
text: this.secondaryText,
|
||||||
attributes: {
|
attributes: {
|
||||||
variant: this.secondaryVariant,
|
variant: this.secondaryVariant,
|
||||||
|
category: 'secondary',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -357,7 +357,13 @@ export default {
|
||||||
}) {
|
}) {
|
||||||
if (shouldConfirm && isDirty) {
|
if (shouldConfirm && isDirty) {
|
||||||
const msg = __('Are you sure you want to cancel editing this comment?');
|
const msg = __('Are you sure you want to cancel editing this comment?');
|
||||||
const confirmed = await confirmAction(msg);
|
const confirmed = await confirmAction(msg, {
|
||||||
|
primaryBtnText: __('Cancel editing'),
|
||||||
|
primaryBtnVariant: 'danger',
|
||||||
|
secondaryBtnVariant: 'default',
|
||||||
|
secondaryBtnText: __('Continue editing'),
|
||||||
|
hideCancel: true,
|
||||||
|
});
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
this.$refs.noteBody.resetAutoSave();
|
this.$refs.noteBody.resetAutoSave();
|
||||||
|
|
|
@ -185,7 +185,7 @@ export default {
|
||||||
<gl-button
|
<gl-button
|
||||||
class="mr-auto js-no-auto-disable"
|
class="mr-auto js-no-auto-disable"
|
||||||
category="primary"
|
category="primary"
|
||||||
variant="success"
|
variant="confirm"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="isFormSubmissionDisabled"
|
:disabled="isFormSubmissionDisabled"
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
|
|
|
@ -238,7 +238,7 @@ export default {
|
||||||
:href="newReleasePath"
|
:href="newReleasePath"
|
||||||
:aria-describedby="shouldRenderEmptyState && 'releases-description'"
|
:aria-describedby="shouldRenderEmptyState && 'releases-description'"
|
||||||
category="primary"
|
category="primary"
|
||||||
variant="success"
|
variant="confirm"
|
||||||
>{{ $options.i18n.newRelease }}</gl-button
|
>{{ $options.i18n.newRelease }}</gl-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -301,6 +301,7 @@ export default {
|
||||||
:code-navigation-path="blobInfo.codeNavigationPath"
|
:code-navigation-path="blobInfo.codeNavigationPath"
|
||||||
:blob-path="blobInfo.path"
|
:blob-path="blobInfo.path"
|
||||||
:path-prefix="blobInfo.projectBlobPathRoot"
|
:path-prefix="blobInfo.projectBlobPathRoot"
|
||||||
|
:wrap-text-nodes="glFeatures.highlightJs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -84,7 +84,7 @@ export default {
|
||||||
<gl-form-input id="user-list-name" v-model="name" data-testid="user-list-name" required />
|
<gl-form-input id="user-list-name" v-model="name" data-testid="user-list-name" required />
|
||||||
</gl-form-group>
|
</gl-form-group>
|
||||||
<div :class="$options.classes.actionContainer">
|
<div :class="$options.classes.actionContainer">
|
||||||
<gl-button variant="success" data-testid="save-user-list" @click="submit">
|
<gl-button variant="confirm" data-testid="save-user-list" @click="submit">
|
||||||
{{ saveButtonLabel }}
|
{{ saveButtonLabel }}
|
||||||
</gl-button>
|
</gl-button>
|
||||||
<gl-button :href="cancelPath" data-testid="user-list-cancel">
|
<gl-button :href="cancelPath" data-testid="user-list-cancel">
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
|
import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
|
import eventHub from '~/notes/event_hub';
|
||||||
import { ROUGE_TO_HLJS_LANGUAGE_MAP, LINES_PER_CHUNK } from './constants';
|
import { ROUGE_TO_HLJS_LANGUAGE_MAP, LINES_PER_CHUNK } from './constants';
|
||||||
import Chunk from './components/chunk.vue';
|
import Chunk from './components/chunk.vue';
|
||||||
|
|
||||||
|
@ -102,6 +103,8 @@ export default {
|
||||||
Object.assign(chunk, { language, content: highlightedContent, isHighlighted: true });
|
Object.assign(chunk, { language, content: highlightedContent, isHighlighted: true });
|
||||||
|
|
||||||
this.selectLine();
|
this.selectLine();
|
||||||
|
|
||||||
|
this.$nextTick(() => eventHub.$emit('showBlobInteractionZones', this.blob.path));
|
||||||
},
|
},
|
||||||
highlight(content, language) {
|
highlight(content, language) {
|
||||||
let detectedLanguage = language;
|
let detectedLanguage = language;
|
||||||
|
@ -153,6 +156,7 @@ export default {
|
||||||
class="file-content code js-syntax-highlight blob-content gl-display-flex gl-flex-direction-column gl-overflow-auto"
|
class="file-content code js-syntax-highlight blob-content gl-display-flex gl-flex-direction-column gl-overflow-auto"
|
||||||
:class="$options.userColorScheme"
|
:class="$options.userColorScheme"
|
||||||
data-type="simple"
|
data-type="simple"
|
||||||
|
:data-path="blob.path"
|
||||||
data-qa-selector="blob_viewer_file_content"
|
data-qa-selector="blob_viewer_file_content"
|
||||||
>
|
>
|
||||||
<chunk
|
<chunk
|
||||||
|
|
|
@ -35,6 +35,8 @@ class Note < ApplicationRecord
|
||||||
contact: :read_crm_contact
|
contact: :read_crm_contact
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
NON_DIFF_NOTE_TYPES = ['Note', 'DiscussionNote', nil].freeze
|
||||||
|
|
||||||
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes.
|
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes.
|
||||||
# See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102
|
# See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102
|
||||||
alias_attribute :last_edited_by, :updated_by
|
alias_attribute :last_edited_by, :updated_by
|
||||||
|
@ -97,6 +99,9 @@ class Note < ApplicationRecord
|
||||||
validates :author, presence: true
|
validates :author, presence: true
|
||||||
validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
|
validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
|
||||||
|
|
||||||
|
validate :ensure_confidentiality_discussion_compliance
|
||||||
|
validate :ensure_noteable_can_have_confidential_note
|
||||||
|
validate :ensure_note_type_can_be_confidential
|
||||||
validate :ensure_confidentiality_not_changed, on: :update
|
validate :ensure_confidentiality_not_changed, on: :update
|
||||||
|
|
||||||
validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
|
validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
|
||||||
|
@ -143,7 +148,7 @@ class Note < ApplicationRecord
|
||||||
|
|
||||||
scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
|
scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
|
||||||
scope :new_diff_notes, -> { where(type: 'DiffNote') }
|
scope :new_diff_notes, -> { where(type: 'DiffNote') }
|
||||||
scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) }
|
scope :non_diff_notes, -> { where(type: NON_DIFF_NOTE_TYPES) }
|
||||||
|
|
||||||
scope :with_associations, -> do
|
scope :with_associations, -> do
|
||||||
# FYI noteable cannot be loaded for LegacyDiffNote for commits
|
# FYI noteable cannot be loaded for LegacyDiffNote for commits
|
||||||
|
@ -460,7 +465,7 @@ class Note < ApplicationRecord
|
||||||
# and all its notes and if we don't care about the discussion's resolvability status.
|
# and all its notes and if we don't care about the discussion's resolvability status.
|
||||||
def discussion
|
def discussion
|
||||||
strong_memoize(:discussion) do
|
strong_memoize(:discussion) do
|
||||||
full_discussion = self.noteable.notes.find_discussion(self.discussion_id) if part_of_discussion?
|
full_discussion = self.noteable.notes.find_discussion(self.discussion_id) if self.noteable && part_of_discussion?
|
||||||
full_discussion || to_discussion
|
full_discussion || to_discussion
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -727,6 +732,35 @@ class Note < ApplicationRecord
|
||||||
|
|
||||||
errors.add(:confidential, _('can not be changed for existing notes'))
|
errors.add(:confidential, _('can not be changed for existing notes'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_confidentiality_discussion_compliance
|
||||||
|
return if start_of_discussion?
|
||||||
|
|
||||||
|
if discussion.first_note.confidential? != confidential?
|
||||||
|
errors.add(:confidential, _('reply should have same confidentiality as top-level note'))
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure
|
||||||
|
clear_memoization(:discussion)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_noteable_can_have_confidential_note
|
||||||
|
return unless confidential?
|
||||||
|
return if noteable_can_have_confidential_note?
|
||||||
|
|
||||||
|
errors.add(:confidential, _('can not be set for this resource'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_note_type_can_be_confidential
|
||||||
|
return unless confidential?
|
||||||
|
return if NON_DIFF_NOTE_TYPES.include?(type)
|
||||||
|
|
||||||
|
errors.add(:confidential, _('can not be set for this type of note'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def noteable_can_have_confidential_note?
|
||||||
|
for_issue?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Note.prepend_mod_with('Note')
|
Note.prepend_mod_with('Note')
|
||||||
|
|
|
@ -7,8 +7,8 @@ class UserCustomAttribute < ApplicationRecord
|
||||||
validates :key, uniqueness: { scope: [:user_id] }
|
validates :key, uniqueness: { scope: [:user_id] }
|
||||||
|
|
||||||
def self.upsert_custom_attributes(custom_attributes)
|
def self.upsert_custom_attributes(custom_attributes)
|
||||||
created_at = Date.today
|
created_at = DateTime.now
|
||||||
updated_at = Date.today
|
updated_at = DateTime.now
|
||||||
|
|
||||||
custom_attributes.map! do |custom_attribute|
|
custom_attributes.map! do |custom_attribute|
|
||||||
custom_attribute.merge({ created_at: created_at, updated_at: updated_at })
|
custom_attribute.merge({ created_at: created_at, updated_at: updated_at })
|
||||||
|
|
|
@ -7,16 +7,14 @@ module Ci
|
||||||
include ::Gitlab::LoopHelpers
|
include ::Gitlab::LoopHelpers
|
||||||
|
|
||||||
BATCH_SIZE = 100
|
BATCH_SIZE = 100
|
||||||
|
LOOP_LIMIT = 500
|
||||||
LOOP_TIMEOUT = 5.minutes
|
LOOP_TIMEOUT = 5.minutes
|
||||||
SMALL_LOOP_LIMIT = 100
|
|
||||||
LARGE_LOOP_LIMIT = 500
|
|
||||||
EXCLUSIVE_LOCK_KEY = 'expired_job_artifacts:destroy:lock'
|
|
||||||
LOCK_TIMEOUT = 6.minutes
|
LOCK_TIMEOUT = 6.minutes
|
||||||
|
EXCLUSIVE_LOCK_KEY = 'expired_job_artifacts:destroy:lock'
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@removed_artifacts_count = 0
|
@removed_artifacts_count = 0
|
||||||
@start_at = Time.current
|
@start_at = Time.current
|
||||||
@loop_limit = ::Feature.enabled?(:ci_artifact_fast_removal_large_loop_limit, default_enabled: :yaml) ? LARGE_LOOP_LIMIT : SMALL_LOOP_LIMIT
|
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -42,7 +40,7 @@ module Ci
|
||||||
private
|
private
|
||||||
|
|
||||||
def destroy_unlocked_job_artifacts
|
def destroy_unlocked_job_artifacts
|
||||||
loop_until(timeout: LOOP_TIMEOUT, limit: @loop_limit) do
|
loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
|
||||||
artifacts = Ci::JobArtifact.expired_before(@start_at).artifact_unlocked.limit(BATCH_SIZE)
|
artifacts = Ci::JobArtifact.expired_before(@start_at).artifact_unlocked.limit(BATCH_SIZE)
|
||||||
service_response = destroy_batch(artifacts)
|
service_response = destroy_batch(artifacts)
|
||||||
@removed_artifacts_count += service_response[:destroyed_artifacts_count]
|
@removed_artifacts_count += service_response[:destroyed_artifacts_count]
|
||||||
|
@ -59,7 +57,7 @@ module Ci
|
||||||
@removed_artifacts_count += service_response[:destroyed_artifacts_count]
|
@removed_artifacts_count += service_response[:destroyed_artifacts_count]
|
||||||
|
|
||||||
break if loop_timeout?
|
break if loop_timeout?
|
||||||
break if index >= @loop_limit
|
break if index >= LOOP_LIMIT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
.settings-content
|
.settings-content
|
||||||
= form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' } do |f|
|
= gitlab_ui_form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-ci-cd-settings'), html: { class: 'fieldset-form' } do |f|
|
||||||
= form_errors(@application_setting)
|
= form_errors(@application_setting)
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
- devops_help_link_url = help_page_path('topics/autodevops/index.md')
|
||||||
= f.check_box :auto_devops_enabled, class: 'form-check-input'
|
- devops_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: devops_help_link_url }
|
||||||
= f.label :auto_devops_enabled, class: 'form-check-label' do
|
= f.gitlab_ui_checkbox_component :auto_devops_enabled, s_('CICD|Default to Auto DevOps pipeline for all projects'), help_text: s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file. %{link_start}What is Auto DevOps?%{link_end}').html_safe % { link_start: devops_help_link_start, link_end: '</a>'.html_safe }
|
||||||
= s_('CICD|Default to Auto DevOps pipeline for all projects')
|
|
||||||
.form-text.text-muted
|
|
||||||
= s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.')
|
|
||||||
= link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
|
= f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
|
||||||
= f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'example.com'
|
= f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'example.com'
|
||||||
|
@ -19,12 +15,7 @@
|
||||||
= link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank', rel: 'noopener noreferrer'
|
= link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :shared_runners_enabled, s_("AdminSettings|Enable shared runners for new projects"), help_text: s_("AdminSettings|All new projects can use the instance's shared runners by default.")
|
||||||
= f.check_box :shared_runners_enabled, class: 'form-check-input'
|
|
||||||
= f.label :shared_runners_enabled, class: 'form-check-label' do
|
|
||||||
= s_("AdminSettings|Enable shared runners for new projects")
|
|
||||||
.form-text.text-muted
|
|
||||||
= s_("AdminSettings|All new projects can use the instance's shared runners by default.")
|
|
||||||
|
|
||||||
= render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
|
= render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
|
||||||
|
|
||||||
|
@ -45,12 +36,7 @@
|
||||||
= html_escape(_("Set the default expiration time for job artifacts in all projects. Set to %{code_open}0%{code_close} to never expire artifacts by default. If no unit is written, it defaults to seconds. For example, these are all equivalent: %{code_open}3600%{code_close}, %{code_open}60 minutes%{code_close}, or %{code_open}one hour%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
= html_escape(_("Set the default expiration time for job artifacts in all projects. Set to %{code_open}0%{code_close} to never expire artifacts by default. If no unit is written, it defaults to seconds. For example, these are all equivalent: %{code_open}3600%{code_close}, %{code_open}60 minutes%{code_close}, or %{code_open}one hour%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||||
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
|
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :keep_latest_artifact, s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines'), help_text: s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.')
|
||||||
= f.check_box :keep_latest_artifact, class: 'form-check-input'
|
|
||||||
= f.label :keep_latest_artifact, class: 'form-check-label' do
|
|
||||||
= s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines')
|
|
||||||
.form-text.text-muted
|
|
||||||
= s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.')
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
|
= f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
|
||||||
= f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input'
|
= f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input'
|
||||||
|
@ -58,12 +44,7 @@
|
||||||
= html_escape(_("Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
= html_escape(_("Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||||
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'archive-jobs')
|
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'archive-jobs')
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :protected_ci_variables, s_('AdminSettings|Protect CI/CD variables by default'), help_text: s_('AdminSettings|New CI/CD variables in projects and groups default to protected.')
|
||||||
= f.check_box :protected_ci_variables, class: 'form-check-input'
|
|
||||||
= f.label :protected_ci_variables, class: 'form-check-label' do
|
|
||||||
= s_('AdminSettings|Protect CI/CD variables by default')
|
|
||||||
.form-text.text-muted
|
|
||||||
= s_('AdminSettings|New CI/CD variables in projects and groups default to protected.')
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :ci_config_path, _('Default CI/CD configuration file'), class: 'label-bold'
|
= f.label :ci_config_path, _('Default CI/CD configuration file'), class: 'label-bold'
|
||||||
= f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml'
|
= f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml'
|
||||||
|
@ -71,12 +52,7 @@
|
||||||
= _("The default CI/CD configuration file and path for new projects.").html_safe
|
= _("The default CI/CD configuration file and path for new projects.").html_safe
|
||||||
= link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
|
= link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :suggest_pipeline_enabled, s_('AdminSettings|Enable pipeline suggestion banner'), help_text: s_('AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file.')
|
||||||
= f.check_box :suggest_pipeline_enabled, class: 'form-check-input'
|
|
||||||
= f.label :suggest_pipeline_enabled, class: 'form-check-label' do
|
|
||||||
= s_('AdminSettings|Enable pipeline suggestion banner')
|
|
||||||
.form-text.text-muted
|
|
||||||
= s_('AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file.')
|
|
||||||
|
|
||||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||||
|
|
||||||
|
|
|
@ -93,18 +93,16 @@
|
||||||
%p
|
%p
|
||||||
= _('Manage Web IDE features.')
|
= _('Manage Web IDE features.')
|
||||||
.settings-content
|
.settings-content
|
||||||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: "#js-web-ide-settings"), html: { class: 'fieldset-form', id: 'web-ide-settings' } do |f|
|
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: "#js-web-ide-settings"), html: { class: 'fieldset-form', id: 'web-ide-settings' } do |f|
|
||||||
= form_errors(@application_setting)
|
= form_errors(@application_setting)
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
.form-check
|
||||||
= f.check_box :web_ide_clientside_preview_enabled, class: 'form-check-input'
|
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('user/project/web_ide/index', anchor: 'enable-live-preview') }
|
||||||
= f.label :web_ide_clientside_preview_enabled, class: 'form-check-label' do
|
= f.gitlab_ui_checkbox_component :web_ide_clientside_preview_enabled,
|
||||||
= s_('IDE|Live Preview')
|
s_('IDE|Live Preview'),
|
||||||
%span.form-text.text-muted
|
help_text: s_('Preview JavaScript projects in the Web IDE with CodeSandbox Live Preview. %{link_start}Learn more.%{link_end} ').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||||
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('user/project/web_ide/index', anchor: 'enable-live-preview') }
|
|
||||||
= s_('Preview JavaScript projects in the Web IDE with CodeSandbox Live Preview. %{link_start}Learn more.%{link_end} ').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
|
||||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||||
|
|
||||||
= render_if_exists 'admin/application_settings/maintenance_mode_settings_form'
|
= render_if_exists 'admin/application_settings/maintenance_mode_settings_form'
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
%span.light= _('Secondary email:')
|
%span.light= _('Secondary email:')
|
||||||
%strong
|
%strong
|
||||||
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
|
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
|
||||||
= link_to remove_email_admin_user_path(@user, email), data: { confirm: _("Are you sure you want to remove %{email}?") % { email: email.email } }, method: :delete, class: "btn btn-sm btn-danger gl-button btn-icon float-right", title: _('Remove secondary email'), id: "remove_email_#{email.id}" do
|
= link_to remove_email_admin_user_path(@user, email), data: { confirm: _("Are you sure you want to remove %{email}?") % { email: email.email }, 'confirm-btn-variant': 'danger' }, method: :delete, class: "btn btn-sm btn-danger gl-button btn-icon float-right", title: _('Remove secondary email'), id: "remove_email_#{email.id}" do
|
||||||
= sprite_icon('close', size: 16, css_class: 'gl-icon')
|
= sprite_icon('close', size: 16, css_class: 'gl-icon')
|
||||||
%li
|
%li
|
||||||
%span.light ID:
|
%span.light ID:
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
%strong{ class: @user.two_factor_enabled? ? 'cgreen' : 'cred' }
|
%strong{ class: @user.two_factor_enabled? ? 'cgreen' : 'cred' }
|
||||||
- if @user.two_factor_enabled?
|
- if @user.two_factor_enabled?
|
||||||
= _('Enabled')
|
= _('Enabled')
|
||||||
= link_to _('Disable'), disable_two_factor_admin_user_path(@user), data: { confirm: _('Are you sure?') }, method: :patch, class: 'btn gl-button btn-sm btn-danger float-right', title: _('Disable Two-factor Authentication')
|
= link_to _('Disable'), disable_two_factor_admin_user_path(@user), aria: { label: _('Disable') }, data: { confirm: _('Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :patch, class: 'btn gl-button btn-sm btn-danger float-right', title: _('Disable Two-factor Authentication')
|
||||||
- else
|
- else
|
||||||
= _('Disabled')
|
= _('Disabled')
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: ci_artifact_fast_removal_large_loop_limit
|
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76504
|
|
||||||
rollout_issue_url:
|
|
||||||
milestone: '14.6'
|
|
||||||
type: development
|
|
||||||
group: group::pipeline execution
|
|
||||||
default_enabled: false
|
|
|
@ -321,13 +321,13 @@ The following package managers use lockfiles that GitLab analyzers are capable o
|
||||||
|
|
||||||
| Package Manager | Supported File Format Versions | Tested Versions |
|
| Package Manager | Supported File Format Versions | Tested Versions |
|
||||||
| ------ | ------ | ------ |
|
| ------ | ------ | ------ |
|
||||||
| Bundler | N/A | [1.17.3](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/master/Gemfile.lock#L118), [2.1.4](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/bundler2-FREEZE/Gemfile.lock#L118) |
|
| Bundler | N/A | [1.17.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/ruby-bundler/main/Gemfile.lock#L118), [2.1.4](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/bundler2-FREEZE/Gemfile.lock#L118) |
|
||||||
| Composer | N/A | [1.x](https://gitlab.com/gitlab-org/security-products/tests/php-composer/-/blob/master/composer.lock) |
|
| Composer | N/A | [1.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/php-composer/main/composer.lock) |
|
||||||
| Conan | 0.4 | [1.x](https://gitlab.com/gitlab-org/security-products/tests/c-conan/-/blob/master/conan.lock) |
|
| Conan | 0.4 | [1.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/c-conan/main/conan.lock) |
|
||||||
| Go | N/A | [1.x](https://gitlab.com/gitlab-org/security-products/tests/go-modules/-/blob/master/go.mod) |
|
| Go | N/A | [1.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/go-modules/main/go.sum) |
|
||||||
| NuGet | v1 | [4.9](https://gitlab.com/gitlab-org/security-products/tests/csharp-nuget-dotnetcore/-/blob/master/src/web.api/packages.lock.json#L2) |
|
| NuGet | v1 | [4.9](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/csharp-nuget-dotnetcore/main/src/web.api/packages.lock.json#L2) |
|
||||||
| npm | v1, v2 | [6.x](https://gitlab.com/gitlab-org/security-products/tests/js-npm/-/blob/master/package-lock.json#L4), [7.x](https://gitlab.com/gitlab-org/security-products/tests/js-npm/-/blob/lockfile-v2-FREEZE/package-lock.json#L4) |
|
| npm | v1, v2 | [6.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/js-npm/main/package-lock.json#L4), [7.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/js-npm/lockfileVersion2/package-lock.json#L4) |
|
||||||
| yarn | v1 | [1.x](https://gitlab.com/gitlab-org/security-products/tests/js-yarn/-/blob/master/yarn.lock) |
|
| yarn | v1 | [1.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/master/qa/fixtures/js-yarn/main/yarn.lock#L2) |
|
||||||
|
|
||||||
#### Obtaining dependency information by running a package manager to generate a parsable file
|
#### Obtaining dependency information by running a package manager to generate a parsable file
|
||||||
|
|
||||||
|
@ -339,19 +339,19 @@ To support the following package managers, the GitLab analyzers proceed in two s
|
||||||
| Package Manager | Pre-installed Versions | Tested Versions |
|
| Package Manager | Pre-installed Versions | Tested Versions |
|
||||||
| ------ | ------ | ------ |
|
| ------ | ------ | ------ |
|
||||||
| Bundler | [2.1.4](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit/-/blob/v2.11.3/Dockerfile#L15)<sup><b><a href="#exported-dependency-information-notes-1">1</a></b></sup> | [1.17.3](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/master/Gemfile.lock#L118), [2.1.4](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/bundler2-FREEZE/Gemfile.lock#L118) |
|
| Bundler | [2.1.4](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit/-/blob/v2.11.3/Dockerfile#L15)<sup><b><a href="#exported-dependency-information-notes-1">1</a></b></sup> | [1.17.3](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/master/Gemfile.lock#L118), [2.1.4](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/bundler2-FREEZE/Gemfile.lock#L118) |
|
||||||
| sbt | [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/config/.tool-versions#L4) | [1.0.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L330), [1.1.4](https://gitlab.com/gitlab-org/security-products/tests/scala-sbt-multiproject/-/blob/main/project/build.properties#L1), [1.1.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L339), [1.2.8](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L348), [1.3.12](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L357), [1.4.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L366), [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L384) |
|
| sbt | [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/config/.tool-versions#L4) | [1.0.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L443-447), [1.1.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L449-453), [1.2.8](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L455-459), [1.3.12](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L461-465), [1.4.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L467-471), [1.5.8](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L473-477), [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L479-483) |
|
||||||
| Maven | [3.6.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L3) | [3.6.3](https://gitlab.com/gitlab-org/security-products/tests/java-maven/-/blob/master/pom.xml#L3) |
|
| Maven | [3.6.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L95-97) | [3.6.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L95-97) |
|
||||||
| Gradle | [6.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L5)<sup><b><a href="#exported-dependency-information-notes-2">2</a></b></sup>, [7.3.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.26.0/config/.tool-versions#L5)<sup><b><a href="#exported-dependency-information-notes-2">2</a></b></sup> | [5.6.4](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/master/gradle/wrapper/gradle-wrapper.properties#L3), [6.5](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14/gradle/wrapper/gradle-wrapper.properties#L3), [6.7-rc-1](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-15/gradle/wrapper/gradle-wrapper.properties#L3), [6.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.27.1/.gitlab-ci.yml#L289-297)<sup><b><a href="#exported-dependency-information-notes-3">3</a></b></sup>, [6.9](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14-gradle-6-9/gradle/wrapper/gradle-wrapper.properties#L3), [7.0-rc-2](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-16/gradle/wrapper/gradle-wrapper.properties#L3), [7.3](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14-gradle-7-3/gradle/wrapper/gradle-wrapper.properties#L3), [7.3.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.27.1/.gitlab-ci.yml#L299-317)<sup><b><a href="#exported-dependency-information-notes-3">3</a></b></sup> |
|
| Gradle | [6.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L5)<sup><b><a href="#exported-dependency-information-notes-2">2</a></b></sup>, [7.3.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.26.0/config/.tool-versions#L5)<sup><b><a href="#exported-dependency-information-notes-2">2</a></b></sup> | [5.6.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L319-323), [6.7](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L286-288)<sup><b><a href="#exported-dependency-information-notes-3">3</a></b></sup>, [6.9](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L331-335), [7.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.28.1/spec/image_spec.rb#L300-302)<sup><b><a href="#exported-dependency-information-notes-3">3</a></b></sup> |
|
||||||
| setuptools | [50.3.2](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L27) | [57.5.0](https://gitlab.com/gitlab-org/security-products/tests/python-setuptools/-/blob/main/setup.py) |
|
| setuptools | [50.3.2](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L27) | [57.5.0](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.22.0/spec/image_spec.rb#L224-247) |
|
||||||
| pip | [20.2.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L26) | [20.x](https://gitlab.com/gitlab-org/security-products/tests/python-pip/-/blob/master/requirements.txt) |
|
| pip | [20.2.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L26) | [20.x](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.22.0/spec/image_spec.rb#L77-91) |
|
||||||
| Pipenv | [2018.11.26](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.18.4/requirements.txt#L13) | [2018.11.26](https://gitlab.com/gitlab-org/security-products/tests/python-pipenv/-/blob/pipfile-lock-FREEZE/Pipfile.lock#L6)<sup><b><a href="#exported-dependency-information-notes-4">4</a></b></sup>, [2018.11.26](https://gitlab.com/gitlab-org/security-products/tests/python-pipenv/-/blob/master/Pipfile) |
|
| Pipenv | [2018.11.26](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.18.4/requirements.txt#L13) | [2018.11.26](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.22.0/spec/image_spec.rb#L168-191)<sup><b><a href="#exported-dependency-information-notes-4">4</a></b></sup>, [2018.11.26](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.22.0/spec/image_spec.rb#L143-166) |
|
||||||
|
|
||||||
<!-- markdownlint-disable MD044 -->
|
<!-- markdownlint-disable MD044 -->
|
||||||
<ol>
|
<ol>
|
||||||
<li>
|
<li>
|
||||||
<a id="exported-dependency-information-notes-1"></a>
|
<a id="exported-dependency-information-notes-1"></a>
|
||||||
<p>
|
<p>
|
||||||
The pre-installed version of <code>Bundler</code> is only used for the <a href="https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit">bundler-audit</a> analyzer, and is not used for <a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">gemnasium</a>.
|
The pre-installed and tested version of <code>Bundler</code> is only used for the <a href="https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit">bundler-audit</a> analyzer, and is not used for <a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">gemnasium</a>.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -326,18 +326,13 @@ run tests:
|
||||||
The following [`.gitlab-ci.yml`](../../../ci/yaml/index.md) example for Go uses:
|
The following [`.gitlab-ci.yml`](../../../ci/yaml/index.md) example for Go uses:
|
||||||
|
|
||||||
- [`go test`](https://go.dev/doc/tutorial/add-a-test) to run tests.
|
- [`go test`](https://go.dev/doc/tutorial/add-a-test) to run tests.
|
||||||
- [`gocover-cobertura`](https://github.com/t-yuki/gocover-cobertura) to convert Go's coverage profile into the Cobertura XML format.
|
- [`gocover-cobertura`](https://github.com/boumenot/gocover-cobertura) to convert Go's coverage profile into the Cobertura XML format.
|
||||||
|
|
||||||
This example assumes that [Go modules](https://go.dev/ref/mod) are being used.
|
This example assumes that [Go modules](https://go.dev/ref/mod)
|
||||||
Using Go modules causes paths within the coverage profile to be prefixed with your
|
are being used. Please note that the `-covermode count` option does not work with the `-race` flag.
|
||||||
project's module identifier, which can be found in the `go.mod` file. This
|
If you want to generate code coverage while also using the `-race` flag, you must switch to
|
||||||
prefix must be removed for GitLab to parse the Cobertura XML file correctly. You can use the following `sed` command to remove the prefix:
|
`-covermode atomic` which is slower than `-covermode count`. See [this blog post](https://go.dev/blog/cover)
|
||||||
|
for more details.
|
||||||
```shell
|
|
||||||
sed -i 's;filename=\"<YOUR_MODULE_ID>/;filename=\";g' coverage.xml
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the `gitlab.com/my-group/my-project` placeholder in the following example with your own module identifier to make it work.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
run tests:
|
run tests:
|
||||||
|
@ -345,9 +340,9 @@ run tests:
|
||||||
image: golang:1.17
|
image: golang:1.17
|
||||||
script:
|
script:
|
||||||
- go install
|
- go install
|
||||||
- go test . -coverprofile=coverage.txt -covermode count
|
- go test ./... -coverprofile=coverage.txt -covermode count
|
||||||
- go run github.com/t-yuki/gocover-cobertura < coverage.txt > coverage.xml
|
- go get github.com/boumenot/gocover-cobertura
|
||||||
- sed -i 's;filename=\"gitlab.com/my-group/my-project/;filename=\";g' coverage.xml
|
- go run github.com/boumenot/gocover-cobertura < coverage.txt > coverage.xml
|
||||||
artifacts:
|
artifacts:
|
||||||
reports:
|
reports:
|
||||||
cobertura: coverage.xml
|
cobertura: coverage.xml
|
||||||
|
|
|
@ -30,7 +30,7 @@ module Gitlab
|
||||||
user: deployment.deployed_by.hook_attrs,
|
user: deployment.deployed_by.hook_attrs,
|
||||||
user_url: Gitlab::UrlBuilder.build(deployment.deployed_by),
|
user_url: Gitlab::UrlBuilder.build(deployment.deployed_by),
|
||||||
commit_url: commit_url,
|
commit_url: commit_url,
|
||||||
commit_title: deployment.commit&.title,
|
commit_title: deployment.commit_title,
|
||||||
ref: deployment.ref
|
ref: deployment.ref
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -6630,7 +6630,7 @@ msgstr ""
|
||||||
msgid "CICD|Select projects that can be accessed by API requests authenticated with this project's CI_JOB_TOKEN CI/CD variable."
|
msgid "CICD|Select projects that can be accessed by API requests authenticated with this project's CI_JOB_TOKEN CI/CD variable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file."
|
msgid "CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file. %{link_start}What is Auto DevOps?%{link_end}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CICD|The Auto DevOps pipeline runs if no alternative CI configuration file is found."
|
msgid "CICD|The Auto DevOps pipeline runs if no alternative CI configuration file is found."
|
||||||
|
@ -6798,6 +6798,9 @@ msgstr ""
|
||||||
msgid "Cancel and close"
|
msgid "Cancel and close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cancel editing"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cancel index deletion"
|
msgid "Cancel index deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -9979,6 +9982,9 @@ msgstr ""
|
||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Continue editing"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Continue to the next step"
|
msgid "Continue to the next step"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -22131,6 +22137,9 @@ msgstr ""
|
||||||
msgid "Lead Time"
|
msgid "Lead Time"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Lead Time for Changes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Lead time"
|
msgid "Lead time"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -42376,9 +42385,6 @@ msgstr ""
|
||||||
msgid "What does this command do?"
|
msgid "What does this command do?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "What is Auto DevOps?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "What is Markdown?"
|
msgid "What is Markdown?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -43950,6 +43956,12 @@ msgstr ""
|
||||||
msgid "can not be changed for existing notes"
|
msgid "can not be changed for existing notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "can not be set for this resource"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "can not be set for this type of note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "can only be changed by a group admin."
|
msgid "can only be changed by a group admin."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -45432,6 +45444,9 @@ msgid_plural "replies"
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "reply should have same confidentiality as top-level note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "repositories"
|
msgid "repositories"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
RSpec.describe 'Release' do
|
RSpec.describe 'Verify' do
|
||||||
describe 'Multi-project pipelines' do
|
describe 'Multi-project pipelines' do
|
||||||
let(:downstream_job_name) { 'downstream_job' }
|
let(:downstream_job_name) { 'downstream_job' }
|
||||||
let(:executor) { "qa-runner-#{SecureRandom.hex(4)}" }
|
let(:executor) { "qa-runner-#{SecureRandom.hex(4)}" }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
RSpec.describe 'Release', :runner, :reliable do
|
RSpec.describe 'Verify', :runner, :reliable do
|
||||||
describe 'Parent-child pipelines dependent relationship' do
|
describe 'Parent-child pipelines dependent relationship' do
|
||||||
let!(:project) do
|
let!(:project) do
|
||||||
Resource::Project.fabricate_via_api! do |project|
|
Resource::Project.fabricate_via_api! do |project|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
RSpec.describe 'Release', :runner, :reliable do
|
RSpec.describe 'Verify', :runner, :reliable do
|
||||||
describe 'Parent-child pipelines independent relationship' do
|
describe 'Parent-child pipelines independent relationship' do
|
||||||
let!(:project) do
|
let!(:project) do
|
||||||
Resource::Project.fabricate_via_api! do |project|
|
Resource::Project.fabricate_via_api! do |project|
|
||||||
|
|
|
@ -5,7 +5,7 @@ module QA
|
||||||
# Static address variables declared for mapping environment to logging URLs
|
# Static address variables declared for mapping environment to logging URLs
|
||||||
STAGING_ADDRESS = 'https://staging.gitlab.com'
|
STAGING_ADDRESS = 'https://staging.gitlab.com'
|
||||||
STAGING_REF_ADDRESS = 'https://staging-ref.gitlab.com'
|
STAGING_REF_ADDRESS = 'https://staging-ref.gitlab.com'
|
||||||
PRODUCTION_ADDRESS = 'https://www.gitlab.com'
|
PRODUCTION_ADDRESS = 'https://gitlab.com'
|
||||||
PRE_PROD_ADDRESS = 'https://pre.gitlab.com'
|
PRE_PROD_ADDRESS = 'https://pre.gitlab.com'
|
||||||
SENTRY_ENVIRONMENTS = {
|
SENTRY_ENVIRONMENTS = {
|
||||||
staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
|
staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
|
||||||
|
|
|
@ -83,7 +83,7 @@ RSpec.describe QA::Support::Loglinking do
|
||||||
describe '.logging_environment' do
|
describe '.logging_environment' do
|
||||||
let(:staging_address) { 'https://staging.gitlab.com' }
|
let(:staging_address) { 'https://staging.gitlab.com' }
|
||||||
let(:staging_ref_address) { 'https://staging-ref.gitlab.com' }
|
let(:staging_ref_address) { 'https://staging-ref.gitlab.com' }
|
||||||
let(:production_address) { 'https://www.gitlab.com' }
|
let(:production_address) { 'https://gitlab.com' }
|
||||||
let(:pre_prod_address) { 'https://pre.gitlab.com' }
|
let(:pre_prod_address) { 'https://pre.gitlab.com' }
|
||||||
let(:logging_env_array) do
|
let(:logging_env_array) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -149,7 +149,7 @@ class MappingTest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
results = tests.map { |test| MappingTest.new(test) }
|
results = tests.map { |test| MappingTest.new(**test) }
|
||||||
|
|
||||||
failed_tests = results.select(&:failed?)
|
failed_tests = results.select(&:failed?)
|
||||||
if failed_tests.any?
|
if failed_tests.any?
|
||||||
|
|
|
@ -423,7 +423,21 @@ RSpec.describe Projects::NotesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when creating a confidential note' do
|
context 'when creating a confidential note' do
|
||||||
let(:extra_request_params) { { format: :json } }
|
let(:project) { create(:project) }
|
||||||
|
let(:note_params) do
|
||||||
|
{ note: note_text, noteable_id: issue.id, noteable_type: 'Issue' }.merge(extra_note_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:request_params) do
|
||||||
|
{
|
||||||
|
note: note_params,
|
||||||
|
namespace_id: project.namespace,
|
||||||
|
project_id: project,
|
||||||
|
target_type: 'issue',
|
||||||
|
target_id: issue.id,
|
||||||
|
format: :json
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
context 'when `confidential` parameter is not provided' do
|
context 'when `confidential` parameter is not provided' do
|
||||||
it 'sets `confidential` to `false` in JSON response' do
|
it 'sets `confidential` to `false` in JSON response' do
|
||||||
|
|
|
@ -302,6 +302,56 @@ FactoryBot.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Bandit reports are correctly de-duplicated when ran in the same pipeline
|
||||||
|
# as a corresponding semgrep report.
|
||||||
|
# This report does not include signature tracking.
|
||||||
|
trait :sast_bandit do
|
||||||
|
file_type { :sast }
|
||||||
|
file_format { :raw }
|
||||||
|
|
||||||
|
after(:build) do |artifact, _|
|
||||||
|
artifact.file = fixture_file_upload(
|
||||||
|
Rails.root.join('spec/fixtures/security_reports/master/gl-sast-report-bandit.json'), 'application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Equivalent Semgrep report for :sast_bandit report.
|
||||||
|
# This report includes signature tracking.
|
||||||
|
trait :sast_semgrep_for_bandit do
|
||||||
|
file_type { :sast }
|
||||||
|
file_format { :raw }
|
||||||
|
|
||||||
|
after(:build) do |artifact, _|
|
||||||
|
artifact.file = fixture_file_upload(
|
||||||
|
Rails.root.join('spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json'), 'application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gosec reports are not correctly de-duplicated when ran in the same pipeline
|
||||||
|
# as a corresponding semgrep report.
|
||||||
|
# This report includes signature tracking.
|
||||||
|
trait :sast_gosec do
|
||||||
|
file_type { :sast }
|
||||||
|
file_format { :raw }
|
||||||
|
|
||||||
|
after(:build) do |artifact, _|
|
||||||
|
artifact.file = fixture_file_upload(
|
||||||
|
Rails.root.join('spec/fixtures/security_reports/master/gl-sast-report-gosec.json'), 'application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Equivalent Semgrep report for :sast_gosec report.
|
||||||
|
# This report includes signature tracking.
|
||||||
|
trait :sast_semgrep_for_gosec do
|
||||||
|
file_type { :sast }
|
||||||
|
file_format { :raw }
|
||||||
|
|
||||||
|
after(:build) do |artifact, _|
|
||||||
|
artifact.file = fixture_file_upload(
|
||||||
|
Rails.root.join('spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-gosec.json'), 'application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
trait :common_security_report do
|
trait :common_security_report do
|
||||||
file_format { :raw }
|
file_format { :raw }
|
||||||
file_type { :dependency_scanning }
|
file_type { :dependency_scanning }
|
||||||
|
|
|
@ -169,7 +169,7 @@ RSpec.describe 'Merge request > User posts notes', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within('.modal') do
|
page.within('.modal') do
|
||||||
click_button('OK', match: :first)
|
click_button('Cancel editing', match: :first)
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(find('.js-note-text').text).to eq ''
|
expect(find('.js-note-text').text).to eq ''
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"version": "14.0.4",
|
||||||
|
"vulnerabilities": [
|
||||||
|
{
|
||||||
|
"id": "985a5666dcae22adef5ac12f8a8a2dacf9b9b481ae5d87cd0ac1712b0fd64864",
|
||||||
|
"category": "sast",
|
||||||
|
"message": "Deserialization of Untrusted Data",
|
||||||
|
"description": "Avoid using `load()`. `PyYAML.load` can create arbitrary Python\nobjects. A malicious actor could exploit this to run arbitrary\ncode. Use `safe_load()` instead.\n",
|
||||||
|
"cve": "",
|
||||||
|
"severity": "Critical",
|
||||||
|
"scanner": {
|
||||||
|
"id": "bandit",
|
||||||
|
"name": "Bandit"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"file": "app/app.py",
|
||||||
|
"start_line": 39
|
||||||
|
},
|
||||||
|
"identifiers": [
|
||||||
|
{
|
||||||
|
"type": "bandit_test_id",
|
||||||
|
"name": "Bandit Test ID B506",
|
||||||
|
"value": "B506"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scan": {
|
||||||
|
"scanner": {
|
||||||
|
"id": "bandit",
|
||||||
|
"name": "Bandit",
|
||||||
|
"url": "https://github.com/PyCQA/bandit",
|
||||||
|
"vendor": {
|
||||||
|
"name": "GitLab"
|
||||||
|
},
|
||||||
|
"version": "1.7.1"
|
||||||
|
},
|
||||||
|
"type": "sast",
|
||||||
|
"start_time": "2022-03-11T00:21:49",
|
||||||
|
"end_time": "2022-03-11T00:21:50",
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"version": "14.0.4",
|
||||||
|
"vulnerabilities": [
|
||||||
|
{
|
||||||
|
"id": "2e5656ff30e2e7cc93c36b4845c8a689ddc47fdbccf45d834c67442fbaa89be0",
|
||||||
|
"category": "sast",
|
||||||
|
"name": "Key Exchange without Entity Authentication",
|
||||||
|
"message": "Use of ssh InsecureIgnoreHostKey should be audited",
|
||||||
|
"description": "The software performs a key exchange with an actor without verifying the identity of that actor.",
|
||||||
|
"cve": "og.go:8:7: func foo() {\n8: \t_ = ssh.InsecureIgnoreHostKey()\n9: }\n:CWE-322",
|
||||||
|
"severity": "Medium",
|
||||||
|
"confidence": "High",
|
||||||
|
"raw_source_code_extract": "7: func foo() {\n8: \t_ = ssh.InsecureIgnoreHostKey()\n9: }\n",
|
||||||
|
"scanner": {
|
||||||
|
"id": "gosec",
|
||||||
|
"name": "Gosec"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"file": "og.go",
|
||||||
|
"start_line": 8
|
||||||
|
},
|
||||||
|
"identifiers": [
|
||||||
|
{
|
||||||
|
"type": "gosec_rule_id",
|
||||||
|
"name": "Gosec Rule ID G106",
|
||||||
|
"value": "G106"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CWE",
|
||||||
|
"name": "CWE-322",
|
||||||
|
"value": "322",
|
||||||
|
"url": "https://cwe.mitre.org/data/definitions/322.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tracking": {
|
||||||
|
"type": "source",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"file": "og.go",
|
||||||
|
"line_start": 8,
|
||||||
|
"line_end": 8,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"algorithm": "scope_offset",
|
||||||
|
"value": "og.go|foo[0]:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scan": {
|
||||||
|
"scanner": {
|
||||||
|
"id": "gosec",
|
||||||
|
"name": "Gosec",
|
||||||
|
"url": "https://github.com/securego/gosec",
|
||||||
|
"vendor": {
|
||||||
|
"name": "GitLab"
|
||||||
|
},
|
||||||
|
"version": "2.10.0"
|
||||||
|
},
|
||||||
|
"type": "sast",
|
||||||
|
"start_time": "2022-03-15T20:33:12",
|
||||||
|
"end_time": "2022-03-15T20:33:17",
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
}
|
71
spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json
vendored
Normal file
71
spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"version": "14.0.4",
|
||||||
|
"vulnerabilities": [
|
||||||
|
{
|
||||||
|
"id": "985a5666dcae22adef5ac12f8a8a2dacf9b9b481ae5d87cd0ac1712b0fd64864",
|
||||||
|
"category": "sast",
|
||||||
|
"message": "Deserialization of Untrusted Data",
|
||||||
|
"description": "Avoid using `load()`. `PyYAML.load` can create arbitrary Python\nobjects. A malicious actor could exploit this to run arbitrary\ncode. Use `safe_load()` instead.\n",
|
||||||
|
"cve": "",
|
||||||
|
"severity": "Critical",
|
||||||
|
"scanner": {
|
||||||
|
"id": "semgrep",
|
||||||
|
"name": "Semgrep"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"file": "app/app.py",
|
||||||
|
"start_line": 39
|
||||||
|
},
|
||||||
|
"identifiers": [
|
||||||
|
{
|
||||||
|
"type": "semgrep_id",
|
||||||
|
"name": "bandit.B506",
|
||||||
|
"value": "bandit.B506",
|
||||||
|
"url": "https://semgrep.dev/r/gitlab.bandit.B506"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cwe",
|
||||||
|
"name": "CWE-502",
|
||||||
|
"value": "502",
|
||||||
|
"url": "https://cwe.mitre.org/data/definitions/502.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "bandit_test_id",
|
||||||
|
"name": "Bandit Test ID B506",
|
||||||
|
"value": "B506"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tracking": {
|
||||||
|
"type": "source",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"file": "app/app.py",
|
||||||
|
"line_start": 39,
|
||||||
|
"line_end": 39,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"algorithm": "scope_offset",
|
||||||
|
"value": "app/app.py|yaml_hammer[0]:13"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scan": {
|
||||||
|
"scanner": {
|
||||||
|
"id": "semgrep",
|
||||||
|
"name": "Semgrep",
|
||||||
|
"url": "https://github.com/returntocorp/semgrep",
|
||||||
|
"vendor": {
|
||||||
|
"name": "GitLab"
|
||||||
|
},
|
||||||
|
"version": "0.82.0"
|
||||||
|
},
|
||||||
|
"type": "sast",
|
||||||
|
"start_time": "2022-03-11T18:48:16",
|
||||||
|
"end_time": "2022-03-11T18:48:22",
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"version": "14.0.4",
|
||||||
|
"vulnerabilities": [
|
||||||
|
{
|
||||||
|
"id": "79f6537b7ec83c7717f5bd1a4f12645916caafefe2e4359148d889855505aa67",
|
||||||
|
"category": "sast",
|
||||||
|
"message": "Key Exchange without Entity Authentication",
|
||||||
|
"description": "Audit the use of ssh.InsecureIgnoreHostKey\n",
|
||||||
|
"cve": "",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scanner": {
|
||||||
|
"id": "semgrep",
|
||||||
|
"name": "Semgrep"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"file": "og.go",
|
||||||
|
"start_line": 8
|
||||||
|
},
|
||||||
|
"identifiers": [
|
||||||
|
{
|
||||||
|
"type": "semgrep_id",
|
||||||
|
"name": "gosec.G106-1",
|
||||||
|
"value": "gosec.G106-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cwe",
|
||||||
|
"name": "CWE-322",
|
||||||
|
"value": "322",
|
||||||
|
"url": "https://cwe.mitre.org/data/definitions/322.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "gosec_rule_id",
|
||||||
|
"name": "Gosec Rule ID G106",
|
||||||
|
"value": "G106"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tracking": {
|
||||||
|
"type": "source",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"file": "og.go",
|
||||||
|
"line_start": 8,
|
||||||
|
"line_end": 8,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"algorithm": "scope_offset",
|
||||||
|
"value": "og.go|foo[0]:1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scan": {
|
||||||
|
"scanner": {
|
||||||
|
"id": "semgrep",
|
||||||
|
"name": "Semgrep",
|
||||||
|
"url": "https://github.com/returntocorp/semgrep",
|
||||||
|
"vendor": {
|
||||||
|
"name": "GitLab"
|
||||||
|
},
|
||||||
|
"version": "0.82.0"
|
||||||
|
},
|
||||||
|
"type": "sast",
|
||||||
|
"start_time": "2022-03-15T20:36:58",
|
||||||
|
"end_time": "2022-03-15T20:37:05",
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,12 +38,17 @@ describe('Code navigation app component', () => {
|
||||||
const codeNavigationPath = 'code/nav/path.js';
|
const codeNavigationPath = 'code/nav/path.js';
|
||||||
const path = 'blob/path.js';
|
const path = 'blob/path.js';
|
||||||
const definitionPathPrefix = 'path/prefix';
|
const definitionPathPrefix = 'path/prefix';
|
||||||
|
const wrapTextNodes = true;
|
||||||
|
|
||||||
factory({}, { codeNavigationPath, blobPath: path, pathPrefix: definitionPathPrefix });
|
factory(
|
||||||
|
{},
|
||||||
|
{ codeNavigationPath, blobPath: path, pathPrefix: definitionPathPrefix, wrapTextNodes },
|
||||||
|
);
|
||||||
|
|
||||||
expect(setInitialData).toHaveBeenCalledWith(expect.anything(), {
|
expect(setInitialData).toHaveBeenCalledWith(expect.anything(), {
|
||||||
blobs: [{ codeNavigationPath, path }],
|
blobs: [{ codeNavigationPath, path }],
|
||||||
definitionPathPrefix,
|
definitionPathPrefix,
|
||||||
|
wrapTextNodes,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,15 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
jest.mock('~/code_navigation/utils');
|
jest.mock('~/code_navigation/utils');
|
||||||
|
|
||||||
describe('Code navigation actions', () => {
|
describe('Code navigation actions', () => {
|
||||||
|
const wrapTextNodes = true;
|
||||||
|
|
||||||
describe('setInitialData', () => {
|
describe('setInitialData', () => {
|
||||||
it('commits SET_INITIAL_DATA', (done) => {
|
it('commits SET_INITIAL_DATA', (done) => {
|
||||||
testAction(
|
testAction(
|
||||||
actions.setInitialData,
|
actions.setInitialData,
|
||||||
{ projectPath: 'test' },
|
{ projectPath: 'test', wrapTextNodes },
|
||||||
{},
|
{},
|
||||||
[{ type: 'SET_INITIAL_DATA', payload: { projectPath: 'test' } }],
|
[{ type: 'SET_INITIAL_DATA', payload: { projectPath: 'test', wrapTextNodes } }],
|
||||||
[],
|
[],
|
||||||
done,
|
done,
|
||||||
);
|
);
|
||||||
|
@ -30,7 +32,7 @@ describe('Code navigation actions', () => {
|
||||||
|
|
||||||
const codeNavigationPath =
|
const codeNavigationPath =
|
||||||
'gitlab-org/gitlab-shell/-/jobs/1114/artifacts/raw/lsif/cmd/check/main.go.json';
|
'gitlab-org/gitlab-shell/-/jobs/1114/artifacts/raw/lsif/cmd/check/main.go.json';
|
||||||
const state = { blobs: [{ path: 'index.js', codeNavigationPath }] };
|
const state = { blobs: [{ path: 'index.js', codeNavigationPath }], wrapTextNodes };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.gon = { api_version: '1' };
|
window.gon = { api_version: '1' };
|
||||||
|
@ -109,10 +111,14 @@ describe('Code navigation actions', () => {
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(addInteractionClass).toHaveBeenCalledWith('index.js', {
|
expect(addInteractionClass).toHaveBeenCalledWith({
|
||||||
start_line: 0,
|
path: 'index.js',
|
||||||
start_char: 0,
|
d: {
|
||||||
hover: { value: '123' },
|
start_line: 0,
|
||||||
|
start_char: 0,
|
||||||
|
hover: { value: '123' },
|
||||||
|
},
|
||||||
|
wrapTextNodes,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(done)
|
.then(done)
|
||||||
|
@ -144,14 +150,19 @@ describe('Code navigation actions', () => {
|
||||||
data: {
|
data: {
|
||||||
'index.js': { '0:0': 'test', '1:1': 'console.log' },
|
'index.js': { '0:0': 'test', '1:1': 'console.log' },
|
||||||
},
|
},
|
||||||
|
wrapTextNodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions.showBlobInteractionZones({ state }, 'index.js');
|
actions.showBlobInteractionZones({ state }, 'index.js');
|
||||||
|
|
||||||
expect(addInteractionClass).toHaveBeenCalled();
|
expect(addInteractionClass).toHaveBeenCalled();
|
||||||
expect(addInteractionClass.mock.calls.length).toBe(2);
|
expect(addInteractionClass.mock.calls.length).toBe(2);
|
||||||
expect(addInteractionClass.mock.calls[0]).toEqual(['index.js', 'test']);
|
expect(addInteractionClass.mock.calls[0]).toEqual([
|
||||||
expect(addInteractionClass.mock.calls[1]).toEqual(['index.js', 'console.log']);
|
{ path: 'index.js', d: 'test', wrapTextNodes },
|
||||||
|
]);
|
||||||
|
expect(addInteractionClass.mock.calls[1]).toEqual([
|
||||||
|
{ path: 'index.js', d: 'console.log', wrapTextNodes },
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not call addInteractionClass when no data exists', () => {
|
it('does not call addInteractionClass when no data exists', () => {
|
||||||
|
|
|
@ -13,10 +13,12 @@ describe('Code navigation mutations', () => {
|
||||||
mutations.SET_INITIAL_DATA(state, {
|
mutations.SET_INITIAL_DATA(state, {
|
||||||
blobs: ['test'],
|
blobs: ['test'],
|
||||||
definitionPathPrefix: 'https://test.com/blob/main',
|
definitionPathPrefix: 'https://test.com/blob/main',
|
||||||
|
wrapTextNodes: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.blobs).toEqual(['test']);
|
expect(state.blobs).toEqual(['test']);
|
||||||
expect(state.definitionPathPrefix).toBe('https://test.com/blob/main');
|
expect(state.definitionPathPrefix).toBe('https://test.com/blob/main');
|
||||||
|
expect(state.wrapTextNodes).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -45,14 +45,42 @@ describe('addInteractionClass', () => {
|
||||||
${0} | ${0} | ${0}
|
${0} | ${0} | ${0}
|
||||||
${0} | ${8} | ${2}
|
${0} | ${8} | ${2}
|
||||||
${1} | ${0} | ${0}
|
${1} | ${0} | ${0}
|
||||||
|
${1} | ${0} | ${0}
|
||||||
`(
|
`(
|
||||||
'it sets code navigation attributes for line $line and character $char',
|
'it sets code navigation attributes for line $line and character $char',
|
||||||
({ line, char, index }) => {
|
({ line, char, index }) => {
|
||||||
addInteractionClass('index.js', { start_line: line, start_char: char });
|
addInteractionClass({ path: 'index.js', d: { start_line: line, start_char: char } });
|
||||||
|
|
||||||
expect(document.querySelectorAll(`#LC${line + 1} span`)[index].classList).toContain(
|
expect(document.querySelectorAll(`#LC${line + 1} span`)[index].classList).toContain(
|
||||||
'js-code-navigation',
|
'js-code-navigation',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
describe('wrapTextNodes', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setFixtures(
|
||||||
|
'<div data-path="index.js"><div class="blob-content"><div id="LC1" class="line"> Text </div></div></div>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = { path: 'index.js', d: { start_line: 0, start_char: 0 } };
|
||||||
|
const findAllSpans = () => document.querySelectorAll('#LC1 span');
|
||||||
|
|
||||||
|
it('does not wrap text nodes by default', () => {
|
||||||
|
addInteractionClass(params);
|
||||||
|
const spans = findAllSpans();
|
||||||
|
expect(spans.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps text nodes if wrapTextNodes is true', () => {
|
||||||
|
addInteractionClass({ ...params, wrapTextNodes: true });
|
||||||
|
const spans = findAllSpans();
|
||||||
|
|
||||||
|
expect(spans.length).toBe(3);
|
||||||
|
expect(spans[0].textContent).toBe(' ');
|
||||||
|
expect(spans[1].textContent).toBe('Text');
|
||||||
|
expect(spans[2].textContent).toBe(' ');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -258,6 +258,7 @@ describe('Blob content viewer component', () => {
|
||||||
codeNavigationPath: simpleViewerMock.codeNavigationPath,
|
codeNavigationPath: simpleViewerMock.codeNavigationPath,
|
||||||
blobPath: simpleViewerMock.path,
|
blobPath: simpleViewerMock.path,
|
||||||
pathPrefix: simpleViewerMock.projectBlobPathRoot,
|
pathPrefix: simpleViewerMock.projectBlobPathRoot,
|
||||||
|
wrapTextNodes: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
||||||
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
|
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
|
import eventHub from '~/notes/event_hub';
|
||||||
|
|
||||||
jest.mock('~/blob/line_highlighter');
|
jest.mock('~/blob/line_highlighter');
|
||||||
jest.mock('highlight.js/lib/core');
|
jest.mock('highlight.js/lib/core');
|
||||||
|
@ -30,7 +31,8 @@ describe('Source Viewer component', () => {
|
||||||
const chunk1 = generateContent('// Some source code 1', 70);
|
const chunk1 = generateContent('// Some source code 1', 70);
|
||||||
const chunk2 = generateContent('// Some source code 2', 70);
|
const chunk2 = generateContent('// Some source code 2', 70);
|
||||||
const content = chunk1 + chunk2;
|
const content = chunk1 + chunk2;
|
||||||
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content };
|
const path = 'some/path.js';
|
||||||
|
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path };
|
||||||
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
|
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
|
||||||
|
|
||||||
const createComponent = async (blob = {}) => {
|
const createComponent = async (blob = {}) => {
|
||||||
|
@ -47,6 +49,7 @@ describe('Source Viewer component', () => {
|
||||||
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
|
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
|
||||||
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
||||||
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
|
||||||
|
jest.spyOn(eventHub, '$emit');
|
||||||
|
|
||||||
return createComponent();
|
return createComponent();
|
||||||
});
|
});
|
||||||
|
@ -102,6 +105,11 @@ describe('Source Viewer component', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
|
||||||
|
findChunks().at(0).vm.$emit('appear');
|
||||||
|
expect(eventHub.$emit).toBeCalledWith('showBlobInteractionZones', path);
|
||||||
|
});
|
||||||
|
|
||||||
describe('LineHighlighter', () => {
|
describe('LineHighlighter', () => {
|
||||||
it('instantiates the lineHighlighter class', async () => {
|
it('instantiates the lineHighlighter class', async () => {
|
||||||
expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
|
expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
|
||||||
|
|
|
@ -134,6 +134,74 @@ RSpec.describe Note do
|
||||||
expect(existing_note.errors[:confidential]).to include('can not be changed for existing notes')
|
expect(existing_note.errors[:confidential]).to include('can not be changed for existing notes')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'for a new note' do
|
||||||
|
let_it_be(:noteable) { create(:issue) }
|
||||||
|
|
||||||
|
let(:note_params) { { confidential: true, noteable: noteable, project: noteable.project } }
|
||||||
|
|
||||||
|
subject { build(:note, **note_params) }
|
||||||
|
|
||||||
|
it 'allows to create a confidential note for an issue' do
|
||||||
|
expect(subject).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when noteable is not allowed to have confidential notes' do
|
||||||
|
let_it_be(:noteable) { create(:merge_request) }
|
||||||
|
|
||||||
|
it 'can not be set confidential' do
|
||||||
|
expect(subject).not_to be_valid
|
||||||
|
expect(subject.errors[:confidential]).to include('can not be set for this resource')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when note type is not allowed to be confidential' do
|
||||||
|
let(:note_params) { { type: 'DiffNote', confidential: true, noteable: noteable, project: noteable.project } }
|
||||||
|
|
||||||
|
it 'can not be set confidential' do
|
||||||
|
expect(subject).not_to be_valid
|
||||||
|
expect(subject.errors[:confidential]).to include('can not be set for this type of note')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the note is a discussion note' do
|
||||||
|
let(:note_params) { { type: 'DiscussionNote', confidential: true, noteable: noteable, project: noteable.project } }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when replying to a note' do
|
||||||
|
let(:note_params) { { confidential: true, noteable: noteable, project: noteable.project } }
|
||||||
|
|
||||||
|
subject { build(:discussion_note, discussion_id: original_note.discussion_id, **note_params) }
|
||||||
|
|
||||||
|
context 'when the note is reply to a confidential note' do
|
||||||
|
let_it_be(:original_note) { create(:note, confidential: true, noteable: noteable, project: noteable.project) }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the note is reply to a public note' do
|
||||||
|
let_it_be(:original_note) { create(:note, noteable: noteable, project: noteable.project) }
|
||||||
|
|
||||||
|
it 'can not be set confidential' do
|
||||||
|
expect(subject).not_to be_valid
|
||||||
|
expect(subject.errors[:confidential]).to include('reply should have same confidentiality as top-level note')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when reply note is public but discussion is confidential' do
|
||||||
|
let_it_be(:original_note) { create(:note, confidential: true, noteable: noteable, project: noteable.project) }
|
||||||
|
|
||||||
|
let(:note_params) { { noteable: noteable, project: noteable.project } }
|
||||||
|
|
||||||
|
it 'can not be set confidential' do
|
||||||
|
expect(subject).not_to be_valid
|
||||||
|
expect(subject.errors[:confidential]).to include('reply should have same confidentiality as top-level note')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1199,8 +1267,8 @@ RSpec.describe Note do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#discussion" do
|
describe "#discussion" do
|
||||||
let!(:note1) { create(:discussion_note_on_merge_request) }
|
let_it_be(:note1) { create(:discussion_note_on_merge_request) }
|
||||||
let!(:note2) { create(:diff_note_on_merge_request, project: note1.project, noteable: note1.noteable) }
|
let_it_be(:note2) { create(:diff_note_on_merge_request, project: note1.project, noteable: note1.noteable) }
|
||||||
|
|
||||||
context 'when the note is part of a discussion' do
|
context 'when the note is part of a discussion' do
|
||||||
subject { create(:discussion_note_on_merge_request, project: note1.project, noteable: note1.noteable, in_reply_to: note1) }
|
subject { create(:discussion_note_on_merge_request, project: note1.project, noteable: note1.noteable, in_reply_to: note1) }
|
||||||
|
|
|
@ -359,39 +359,6 @@ RSpec.describe NotePolicy do
|
||||||
expect(permissions(assignee, confidential_note)).to be_disallowed(:admin_note, :reposition_note, :resolve_note)
|
expect(permissions(assignee, confidential_note)).to be_disallowed(:admin_note, :reposition_note, :resolve_note)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'for merge requests' do
|
|
||||||
let(:merge_request) { create(:merge_request, source_project: project, author: author, assignees: [assignee]) }
|
|
||||||
let(:confidential_note) { create(:note, :confidential, project: project, noteable: merge_request) }
|
|
||||||
|
|
||||||
it_behaves_like 'confidential notes permissions'
|
|
||||||
|
|
||||||
it 'allows noteable assignees to read all notes' do
|
|
||||||
expect(permissions(assignee, confidential_note)).to be_allowed(:read_note, :award_emoji)
|
|
||||||
expect(permissions(assignee, confidential_note)).to be_disallowed(:admin_note, :reposition_note, :resolve_note)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for project snippets' do
|
|
||||||
let(:project_snippet) { create(:project_snippet, project: project, author: author) }
|
|
||||||
let(:confidential_note) { create(:note, :confidential, project: project, noteable: project_snippet) }
|
|
||||||
|
|
||||||
it_behaves_like 'confidential notes permissions'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for personal snippets' do
|
|
||||||
let(:personal_snippet) { create(:personal_snippet, author: author) }
|
|
||||||
let(:confidential_note) { create(:note, :confidential, project: nil, noteable: personal_snippet) }
|
|
||||||
|
|
||||||
it 'allows snippet author to read and resolve all notes' do
|
|
||||||
expect(permissions(author, confidential_note)).to be_allowed(:read_note, :resolve_note, :award_emoji)
|
|
||||||
expect(permissions(author, confidential_note)).to be_disallowed(:admin_note, :reposition_note)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not allow maintainers to read confidential notes and replies' do
|
|
||||||
expect(permissions(maintainer, confidential_note)).to be_disallowed(:read_note, :admin_note, :reposition_note, :resolve_note, :award_emoji)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,8 +17,7 @@ RSpec.describe 'Adding a Note' do
|
||||||
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
|
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
|
||||||
discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
|
discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
|
||||||
merge_request_diff_head_sha: head_sha.presence,
|
merge_request_diff_head_sha: head_sha.presence,
|
||||||
body: body,
|
body: body
|
||||||
confidential: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_mutation(:create_note, variables)
|
graphql_mutation(:create_note, variables)
|
||||||
|
@ -49,7 +48,6 @@ RSpec.describe 'Adding a Note' do
|
||||||
post_graphql_mutation(mutation, current_user: current_user)
|
post_graphql_mutation(mutation, current_user: current_user)
|
||||||
|
|
||||||
expect(mutation_response['note']['body']).to eq('Body text')
|
expect(mutation_response['note']['body']).to eq('Body text')
|
||||||
expect(mutation_response['note']['confidential']).to eq(true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'creating Notes in reply to a discussion' do
|
describe 'creating Notes in reply to a discussion' do
|
||||||
|
@ -79,6 +77,25 @@ RSpec.describe 'Adding a Note' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'for an issue' do
|
||||||
|
let(:noteable) { create(:issue, project: project) }
|
||||||
|
let(:mutation) do
|
||||||
|
variables = {
|
||||||
|
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
|
||||||
|
body: body,
|
||||||
|
confidential: true
|
||||||
|
}
|
||||||
|
|
||||||
|
graphql_mutation(:create_note, variables)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_developer(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'a Note mutation with confidential notes'
|
||||||
|
end
|
||||||
|
|
||||||
context 'when body only contains quick actions' do
|
context 'when body only contains quick actions' do
|
||||||
let(:head_sha) { noteable.diff_head_sha }
|
let(:head_sha) { noteable.diff_head_sha }
|
||||||
let(:body) { '/merge' }
|
let(:body) { '/merge' }
|
||||||
|
|
|
@ -877,5 +877,35 @@ RSpec.describe API::Issues do
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a confidential note' do
|
||||||
|
let!(:note) do
|
||||||
|
create(
|
||||||
|
:note,
|
||||||
|
:confidential,
|
||||||
|
project: project,
|
||||||
|
noteable: issue,
|
||||||
|
author: create(:user)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a full list of participants' do
|
||||||
|
get api("/projects/#{project.id}/issues/#{issue.iid}/participants", user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
participant_ids = json_response.map { |el| el['id'] }
|
||||||
|
expect(participant_ids).to match_array([issue.author_id, note.author_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user cannot see a confidential note' do
|
||||||
|
it 'returns a limited list of participants' do
|
||||||
|
get api("/projects/#{project.id}/issues/#{issue.iid}/participants", create(:user))
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
participant_ids = json_response.map { |el| el['id'] }
|
||||||
|
expect(participant_ids).to match_array([issue.author_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe API::Notes do
|
||||||
let!(:issue) { create(:issue, project: project, author: user) }
|
let!(:issue) { create(:issue, project: project, author: user) }
|
||||||
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
|
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
|
||||||
|
|
||||||
it_behaves_like "noteable API", 'projects', 'issues', 'iid' do
|
it_behaves_like "noteable API with confidential notes", 'projects', 'issues', 'iid' do
|
||||||
let(:parent) { project }
|
let(:parent) { project }
|
||||||
let(:noteable) { issue }
|
let(:noteable) { issue }
|
||||||
let(:note) { issue_note }
|
let(:note) { issue_note }
|
||||||
|
|
|
@ -31,7 +31,7 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_const("#{described_class}::LARGE_LOOP_LIMIT", 1)
|
stub_const("#{described_class}::LOOP_LIMIT", 1)
|
||||||
|
|
||||||
# This artifact-with-file is created before the control execution to ensure
|
# This artifact-with-file is created before the control execution to ensure
|
||||||
# that the DeletedObject operations are accounted for in the query count.
|
# that the DeletedObject operations are accounted for in the query count.
|
||||||
|
@ -130,7 +130,7 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s
|
||||||
let!(:artifact) { create(:ci_job_artifact, :expired, job: job, locked: job.pipeline.locked) }
|
let!(:artifact) { create(:ci_job_artifact, :expired, job: job, locked: job.pipeline.locked) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_const("#{described_class}::LARGE_LOOP_LIMIT", 10)
|
stub_const("#{described_class}::LOOP_LIMIT", 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the import fails' do
|
context 'when the import fails' do
|
||||||
|
@ -200,8 +200,7 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s
|
||||||
|
|
||||||
context 'when loop reached loop limit' do
|
context 'when loop reached loop limit' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(ci_artifact_fast_removal_large_loop_limit: false)
|
stub_const("#{described_class}::LOOP_LIMIT", 1)
|
||||||
stub_const("#{described_class}::SMALL_LOOP_LIMIT", 1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'destroys one artifact' do
|
it 'destroys one artifact' do
|
||||||
|
|
|
@ -106,7 +106,8 @@ RSpec.describe Notes::CreateService do
|
||||||
type: 'DiffNote',
|
type: 'DiffNote',
|
||||||
noteable_type: 'MergeRequest',
|
noteable_type: 'MergeRequest',
|
||||||
noteable_id: merge_request.id,
|
noteable_id: merge_request.id,
|
||||||
position: position.to_h)
|
position: position.to_h,
|
||||||
|
confidential: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -141,7 +142,8 @@ RSpec.describe Notes::CreateService do
|
||||||
type: 'DiffNote',
|
type: 'DiffNote',
|
||||||
noteable_type: 'MergeRequest',
|
noteable_type: 'MergeRequest',
|
||||||
noteable_id: merge_request.id,
|
noteable_id: merge_request.id,
|
||||||
position: position.to_h)
|
position: position.to_h,
|
||||||
|
confidential: false)
|
||||||
|
|
||||||
expect(merge_request).not_to receive(:diffs)
|
expect(merge_request).not_to receive(:diffs)
|
||||||
|
|
||||||
|
@ -173,7 +175,8 @@ RSpec.describe Notes::CreateService do
|
||||||
type: 'DiffNote',
|
type: 'DiffNote',
|
||||||
noteable_type: 'MergeRequest',
|
noteable_type: 'MergeRequest',
|
||||||
noteable_id: merge_request.id,
|
noteable_id: merge_request.id,
|
||||||
position: position.to_h)
|
position: position.to_h,
|
||||||
|
confidential: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'note is associated with a note diff file' do
|
it 'note is associated with a note diff file' do
|
||||||
|
@ -201,7 +204,8 @@ RSpec.describe Notes::CreateService do
|
||||||
type: 'DiffNote',
|
type: 'DiffNote',
|
||||||
noteable_type: 'MergeRequest',
|
noteable_type: 'MergeRequest',
|
||||||
noteable_id: merge_request.id,
|
noteable_id: merge_request.id,
|
||||||
position: position.to_h)
|
position: position.to_h,
|
||||||
|
confidential: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'note is not associated with a note diff file' do
|
it 'note is not associated with a note diff file' do
|
||||||
|
@ -230,7 +234,8 @@ RSpec.describe Notes::CreateService do
|
||||||
type: 'DiffNote',
|
type: 'DiffNote',
|
||||||
noteable_type: 'MergeRequest',
|
noteable_type: 'MergeRequest',
|
||||||
noteable_id: merge_request.id,
|
noteable_id: merge_request.id,
|
||||||
position: image_position.to_h)
|
position: image_position.to_h,
|
||||||
|
confidential: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'note is not associated with a note diff file' do
|
it 'note is not associated with a note diff file' do
|
||||||
|
@ -306,7 +311,7 @@ RSpec.describe Notes::CreateService do
|
||||||
let_it_be(:merge_request) { create(:merge_request, source_project: project, labels: [bug_label]) }
|
let_it_be(:merge_request) { create(:merge_request, source_project: project, labels: [bug_label]) }
|
||||||
|
|
||||||
let(:issuable) { merge_request }
|
let(:issuable) { merge_request }
|
||||||
let(:note_params) { opts.merge(noteable_type: 'MergeRequest', noteable_id: merge_request.id) }
|
let(:note_params) { opts.merge(noteable_type: 'MergeRequest', noteable_id: merge_request.id, confidential: false) }
|
||||||
let(:merge_request_quick_actions) do
|
let(:merge_request_quick_actions) do
|
||||||
[
|
[
|
||||||
QuickAction.new(
|
QuickAction.new(
|
||||||
|
|
|
@ -99,7 +99,7 @@ module LoginHelpers
|
||||||
fill_in "user_password", with: (password || "12345678")
|
fill_in "user_password", with: (password || "12345678")
|
||||||
check 'user_remember_me' if remember
|
check 'user_remember_me' if remember
|
||||||
|
|
||||||
click_button "Sign in"
|
find('[data-testid="sign-in-button"]:enabled').click
|
||||||
|
|
||||||
if two_factor_auth
|
if two_factor_auth
|
||||||
fill_in "user_otp_attempt", with: user.reload.current_otp
|
fill_in "user_otp_attempt", with: user.reload.current_otp
|
||||||
|
|
|
@ -85,3 +85,14 @@ RSpec.shared_examples 'a Note mutation when there are rate limit validation erro
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'a Note mutation with confidential notes' do
|
||||||
|
it_behaves_like 'a Note mutation that creates a Note'
|
||||||
|
|
||||||
|
it 'returns a Note with confidentiality enabled' do
|
||||||
|
post_graphql_mutation(mutation, current_user: current_user)
|
||||||
|
|
||||||
|
expect(mutation_response).to have_key('note')
|
||||||
|
expect(mutation_response['note']['confidential']).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -28,34 +28,4 @@ RSpec.shared_examples 'issuable participants endpoint' do
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a confidential note' do
|
|
||||||
let!(:note) do
|
|
||||||
create(
|
|
||||||
:note,
|
|
||||||
:confidential,
|
|
||||||
project: project,
|
|
||||||
noteable: entity,
|
|
||||||
author: create(:user)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a full list of participants' do
|
|
||||||
get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", user)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
participant_ids = json_response.map { |el| el['id'] }
|
|
||||||
expect(participant_ids).to match_array([entity.author_id, note.author_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user cannot see a confidential note' do
|
|
||||||
it 'returns a limited list of participants' do
|
|
||||||
get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", create(:user))
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
participant_ids = json_response.map { |el| el['id'] }
|
|
||||||
expect(participant_ids).to match_array([entity.author_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -142,15 +142,6 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
|
||||||
expect(json_response['author']['username']).to eq(user.username)
|
expect(json_response['author']['username']).to eq(user.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a confidential note if confidential is set to true" do
|
|
||||||
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!', confidential: true }
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
expect(json_response['body']).to eq('hi!')
|
|
||||||
expect(json_response['confidential']).to be_truthy
|
|
||||||
expect(json_response['author']['username']).to eq(user.username)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 400 bad request error if body not given" do
|
it "returns a 400 bad request error if body not given" do
|
||||||
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
|
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
|
||||||
|
|
||||||
|
@ -312,26 +303,22 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
|
||||||
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params
|
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when eveything is ok' do
|
context 'when only body param is present' do
|
||||||
before do
|
let(:params) { { body: 'Hello!' } }
|
||||||
subject
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns modified note' do
|
it 'updates the note text' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(note.reload.note).to eq('Hello!')
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(json_response['body']).to eq('Hello!')
|
expect(json_response['body']).to eq('Hello!')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the note' do
|
|
||||||
expect(note.reload.note).to eq('Hello!')
|
|
||||||
expect(note.confidential).to be_falsey
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when also confidential param is set' do
|
context 'when confidential param is present' do
|
||||||
let(:params) { { body: 'Hello!', confidential: true } }
|
let(:params) { { confidential: true } }
|
||||||
|
|
||||||
it 'fails to update the note' do
|
it 'does not allow to change confidentiality' do
|
||||||
expect { subject }.not_to change { note.reload.note }
|
expect { subject }.not_to change { note.reload.note }
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
@ -376,3 +363,24 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, noteable_type, id_name|
|
||||||
|
it_behaves_like 'noteable API', parent_type, noteable_type, id_name
|
||||||
|
|
||||||
|
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
|
||||||
|
let(:params) { { body: 'hi!' } }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a confidential note if confidential is set to true" do
|
||||||
|
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(json_response['body']).to eq('hi!')
|
||||||
|
expect(json_response['confidential']).to be_truthy
|
||||||
|
expect(json_response['author']['username']).to eq(user.username)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue