Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-11 15:09:11 +00:00
parent 9f5ac379c7
commit 5231344d99
162 changed files with 4043 additions and 1357 deletions

View File

@ -109,7 +109,6 @@ linters:
- 'app/views/groups/runners/edit.html.haml'
- 'app/views/groups/settings/_advanced.html.haml'
- 'app/views/groups/settings/_lfs.html.haml'
- 'app/views/help/_shortcuts.html.haml'
- 'app/views/help/index.html.haml'
- 'app/views/help/instance_configuration.html.haml'
- 'app/views/help/instance_configuration/_gitlab_ci.html.haml'

View File

@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 13.8.4 (2021-02-11)
### Security (9 changes)
- Cancel running and pending jobs when a project is deleted. !1220
- Prevent Denial of Service Attack on gitlab-shell.
- Prevent exposure of confidential issue titles in file browser.
- Updates authorization for linting API.
- Check user access on API merge request read actions.
- Limit daily invitations to groups and projects.
- Enforce the analytics enabled project setting for project-level analytics features.
- Perform SSL verification for FortiTokenCloud Integration.
- Prevent Server-side Request Forgery for Prometheus when secured by Google IAP.
## 13.8.3 (2021-02-05)
### Fixed (2 changes)
@ -387,6 +402,21 @@ entry.
- Add verbiage + link sast to show it's in core. !51935
## 13.7.7 (2021-02-11)
### Security (9 changes)
- Cancel running and pending jobs when a project is deleted. !1220
- Prevent Denial of Service Attack on gitlab-shell.
- Prevent exposure of confidential issue titles in file browser.
- Updates authorization for linting API.
- Check user access on API merge request read actions.
- Limit daily invitations to groups and projects.
- Enforce the analytics enabled project setting for project-level analytics features.
- Perform SSL verification for FortiTokenCloud Integration.
- Prevent Server-side Request Forgery for Prometheus when secured by Google IAP.
## 13.7.6 (2021-02-01)
### Security (5 changes)
@ -908,6 +938,19 @@ entry.
- Update GitLab Workhorse to v8.57.0.
## 13.6.7 (2021-02-11)
### Security (7 changes)
- Cancel running and pending jobs when a project is deleted. !1220
- Updates authorization for linting API.
- Prevent exposure of confidential issue titles in file browser.
- Check user access on API merge request read actions.
- Prevent Denial of Service Attack on gitlab-shell.
- Limit daily invitations to groups and projects.
- Prevent Server-side Request Forgery for Prometheus when secured by Google IAP.
## 13.6.6 (2021-02-01)
### Security (5 changes)

View File

@ -1 +1 @@
88ef3e7f64498ae3574f29b0705c29cf3b4e9311
d0a79053ba4fef55b59543b99327fc89aed64876

View File

@ -1 +1 @@
13.16.0
13.16.1

View File

@ -3,12 +3,11 @@ import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import Vue from 'vue';
import { flatten } from 'lodash';
import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
import { refreshCurrentPage, visitUrl } from '~/lib/utils/url_utility';
import findAndFollowLink from '~/lib/utils/navigation_utility';
import { parseBoolean } from '~/lib/utils/common_utils';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
import ShortcutsToggle from './shortcuts_toggle.vue';
import { keysFor, TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY } from './keybindings';
const defaultStopCallback = Mousetrap.prototype.stopCallback;
@ -20,15 +19,6 @@ Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo
return defaultStopCallback.call(this, e, element, combo);
};
function initToggleButton() {
return new Vue({
el: document.querySelector('.js-toggle-shortcuts'),
render(createElement) {
return createElement(ShortcutsToggle);
},
});
}
/**
* The key used to save and fetch the local Mousetrap instance
* attached to a `<textarea>` element using `jQuery.data`
@ -65,7 +55,8 @@ function getToolbarBtnToShortcutsMap($textarea) {
export default class Shortcuts {
constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
this.enabledHelp = [];
this.helpModalElement = null;
this.helpModalVueInstance = null;
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
@ -107,11 +98,33 @@ export default class Shortcuts {
}
onToggleHelp(e) {
if (e.preventDefault) {
if (e?.preventDefault) {
e.preventDefault();
}
Shortcuts.toggleHelp(this.enabledHelp);
if (this.helpModalElement && this.helpModalVueInstance) {
this.helpModalVueInstance.$destroy();
this.helpModalElement.remove();
this.helpModalElement = null;
this.helpModalVueInstance = null;
} else {
this.helpModalElement = document.createElement('div');
document.body.append(this.helpModalElement);
this.helpModalVueInstance = new Vue({
el: this.helpModalElement,
components: {
ShortcutsHelp: () => import('./shortcuts_help.vue'),
},
render: (createElement) => {
return createElement('shortcuts-help', {
on: {
hidden: this.onToggleHelp,
},
});
},
});
}
}
static onTogglePerfBar(e) {
@ -144,34 +157,6 @@ export default class Shortcuts {
$(document).triggerHandler('markdown-preview:toggle', [e]);
}
static toggleHelp(location) {
const $modal = $('#modal-shortcuts');
if ($modal.length) {
$modal.modal('toggle');
return null;
}
return axios
.get(gon.shortcuts_path, {
responseType: 'text',
})
.then(({ data }) => {
$.globalEval(data, { nonce: getCspNonceValue() });
if (location && location.length > 0) {
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) {
results.push($(location[i]).show());
}
return results;
}
return $('.js-more-help-button').remove();
})
.then(initToggleButton);
}
focusFilter(e) {
if (!this.filterInput) {
this.filterInput = $('input[type=search]', '.nav-controls');

View File

@ -0,0 +1,525 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
import { GlIcon, GlModal } from '@gitlab/ui';
import ShortcutsToggle from './shortcuts_toggle.vue';
export default {
components: {
GlIcon,
GlModal,
ShortcutsToggle,
},
computed: {
ctrlCharacter() {
return window.gl.client.isMac ? '⌘' : 'ctrl';
},
onDotCom() {
return window.gon.dot_com;
},
},
};
</script>
<template>
<gl-modal
modal-id="keyboard-shortcut-modal"
size="lg"
data-testid="modal-shortcuts"
:visible="true"
:hide-footer="true"
@hidden="$emit('hidden')"
>
<template #modal-title>
<shortcuts-toggle />
</template>
<div class="row">
<div class="col-lg-4">
<table class="shortcut-mappings text-2">
<tbody>
<tr>
<th></th>
<th>{{ __('Global Shortcuts') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>?</kbd>
</td>
<td>{{ __('Toggle this dialog') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift p</kbd>
</td>
<td>{{ __('Go to your projects') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift g</kbd>
</td>
<td>{{ __('Go to your groups') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift a</kbd>
</td>
<td>{{ __('Go to the activity feed') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift l</kbd>
</td>
<td>{{ __('Go to the milestone list') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift s</kbd>
</td>
<td>{{ __('Go to your snippets') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>s</kbd>
/
<kbd>/</kbd>
</td>
<td>{{ __('Start search') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift i</kbd>
</td>
<td>{{ __('Go to your issues') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift m</kbd>
</td>
<td>{{ __('Go to your merge requests') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift t</kbd>
</td>
<td>{{ __('Go to your To-Do list') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>p</kbd>
<kbd>b</kbd>
</td>
<td>{{ __('Toggle the Performance Bar') }}</td>
</tr>
<tr v-if="onDotCom">
<td class="shortcut">
<kbd>g</kbd>
<kbd>x</kbd>
</td>
<td>{{ __('Toggle GitLab Next') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Editing') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} shift p</kbd>
</td>
<td>{{ __('Toggle Markdown preview') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-up" />
</kbd>
</td>
<td>
{{ __('Edit your most recent comment in a thread (from an empty textarea)') }}
</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Wiki') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>e</kbd>
</td>
<td>{{ __('Edit wiki page') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Repository Graph') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-left" />
</kbd>
/
<kbd>h</kbd>
</td>
<td>{{ __('Scroll left') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-right" />
</kbd>
/
<kbd>l</kbd>
</td>
<td>{{ __('Scroll right') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-up" />
</kbd>
/
<kbd>k</kbd>
</td>
<td>{{ __('Scroll up') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-down" />
</kbd>
/
<kbd>j</kbd>
</td>
<td>{{ __('Scroll down') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
shift
<gl-icon name="arrow-up" />
/ k
</kbd>
</td>
<td>{{ __('Scroll to top') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
shift
<gl-icon name="arrow-down" />
/ j
</kbd>
</td>
<td>{{ __('Scroll to bottom') }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-4">
<table class="shortcut-mappings text-2">
<tbody>
<tr>
<th></th>
<th>{{ __('Project') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>p</kbd>
</td>
<td>{{ __("Go to the project's overview page") }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>v</kbd>
</td>
<td>{{ __("Go to the project's activity feed") }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>r</kbd>
</td>
<td>{{ __('Go to releases') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>f</kbd>
</td>
<td>{{ __('Go to files') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>t</kbd>
</td>
<td>{{ __('Go to find file') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>c</kbd>
</td>
<td>{{ __('Go to commits') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>n</kbd>
</td>
<td>{{ __('Go to repository graph') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>d</kbd>
</td>
<td>{{ __('Go to repository charts') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>i</kbd>
</td>
<td>{{ __('Go to issues') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>i</kbd>
</td>
<td>{{ __('New issue') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>b</kbd>
</td>
<td>{{ __('Go to issue boards') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>m</kbd>
</td>
<td>{{ __('Go to merge requests') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>j</kbd>
</td>
<td>{{ __('Go to jobs') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>l</kbd>
</td>
<td>{{ __('Go to metrics') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>e</kbd>
</td>
<td>{{ __('Go to environments') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>k</kbd>
</td>
<td>{{ __('Go to kubernetes') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>s</kbd>
</td>
<td>{{ __('Go to snippets') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>w</kbd>
</td>
<td>{{ __('Go to wiki') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Project Files') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-up" />
</kbd>
</td>
<td>{{ __('Move selection up') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-down" />
</kbd>
</td>
<td>{{ __('Move selection down') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>enter</kbd>
</td>
<td>{{ __('Open Selection') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>esc</kbd>
</td>
<td>{{ __('Go back (while searching for files)') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>y</kbd>
</td>
<td>{{ __('Go to file permalink (while viewing a file)') }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-4">
<table class="shortcut-mappings text-2">
<tbody>
<tr>
<th></th>
<th>{{ __('Epics, Issues, and Merge Requests') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>r</kbd>
</td>
<td>{{ __('Comment/Reply (quoting selected text)') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>e</kbd>
</td>
<td>{{ __('Edit description') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>l</kbd>
</td>
<td>{{ __('Change label') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Issues and Merge Requests') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>a</kbd>
</td>
<td>{{ __('Change assignee') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>m</kbd>
</td>
<td>{{ __('Change milestone') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Merge Requests') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>]</kbd>
/
<kbd>j</kbd>
</td>
<td>{{ __('Next file in diff') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>[</kbd>
/
<kbd>k</kbd>
</td>
<td>{{ __('Previous file in diff') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} p</kbd>
</td>
<td>{{ __('Go to file') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>n</kbd>
</td>
<td>{{ __('Next unresolved discussion') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>p</kbd>
</td>
<td>{{ __('Previous unresolved discussion') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>b</kbd>
</td>
<td>{{ __('Copy source branch name') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Merge Request Commits') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>c</kbd>
</td>
<td>{{ __('Next commit') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>x</kbd>
</td>
<td>{{ __('Previous commit') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Web IDE') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} p</kbd>
</td>
<td>{{ __('Go to file') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} enter</kbd>
</td>
<td>{{ __('Commit (when editing commit message)') }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</gl-modal>
</template>

View File

@ -1,9 +1,8 @@
<script>
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
import { isEmpty } from 'lodash';
import Autosize from 'autosize';
import { GlButton, GlIcon } from '@gitlab/ui';
import { GlButton, GlIcon, GlFormCheckbox, GlTooltipDirective } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import { deprecatedCreateFlash as Flash } from '~/flash';
@ -34,6 +33,10 @@ export default {
TimelineEntryItem,
GlIcon,
CommentFieldLayout,
GlFormCheckbox,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin(), issuableStateMixin],
props: {
@ -46,8 +49,8 @@ export default {
return {
note: '',
noteType: constants.COMMENT,
noteIsConfidential: false,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
},
computed: {
@ -80,6 +83,9 @@ export default {
canCreateNote() {
return this.getNoteableData.current_user.can_create_note;
},
canSetConfidential() {
return this.getNoteableData.current_user.can_update;
},
issueActionButtonTitle() {
const openOrClose = this.isOpen ? 'close' : 'reopen';
@ -146,13 +152,11 @@ export default {
hasCloseAndCommentButton() {
return !this.glFeatures.removeCommentCloseReopen;
},
},
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
confidentialNotesEnabled() {
return Boolean(this.glFeatures.confidentialNotes);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
disableSubmitButton() {
return this.note.length === 0 || this.isSubmitting;
},
},
mounted() {
@ -173,13 +177,6 @@ export default {
'reopenIssuable',
'toggleIssueLocalState',
]),
setIsSubmitButtonDisabled(note, isSubmitting) {
if (!isEmpty(note) && !isSubmitting) {
this.isSubmitButtonDisabled = false;
} else {
this.isSubmitButtonDisabled = true;
}
},
handleSave(withIssueAction) {
if (this.note.length) {
const noteData = {
@ -189,6 +186,7 @@ export default {
note: {
noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id,
confidential: this.noteIsConfidential,
note: this.note,
},
merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
@ -252,6 +250,7 @@ export default {
if (shouldClear) {
this.note = '';
this.noteIsConfidential = false;
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
@ -340,11 +339,26 @@ export default {
</markdown-field>
</comment-field-layout>
<div class="note-form-actions">
<gl-form-checkbox
v-if="confidentialNotesEnabled && canSetConfidential"
v-model="noteIsConfidential"
class="gl-mb-6"
data-testid="confidential-note-checkbox"
>
{{ s__('Notes|Make this comment confidential') }}
<gl-icon
v-gl-tooltip:tooltipcontainer.bottom
name="question"
:size="16"
:title="s__('Notes|Confidential comments are only visible to project members')"
class="gl-text-gray-500"
/>
</gl-form-checkbox>
<div
class="btn-group gl-mr-3 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<gl-button
:disabled="isSubmitButtonDisabled"
:disabled="disableSubmitButton"
class="js-comment-button js-comment-submit-button"
data-qa-selector="comment_button"
data-testid="comment-button"
@ -357,7 +371,7 @@ export default {
>{{ commentButtonTitle }}</gl-button
>
<gl-button
:disabled="isSubmitButtonDisabled"
:disabled="disableSubmitButton"
name="button"
category="primary"
variant="success"

View File

@ -210,9 +210,9 @@ export default {
v-gl-tooltip:tooltipcontainer.bottom
data-testid="confidentialIndicator"
name="eye-slash"
:size="14"
:title="s__('Notes|Private comments are accessible by internal staff only')"
class="gl-ml-1 gl-text-gray-700 align-middle"
:size="16"
:title="s__('Notes|This comment is confidential and only visible to project members')"
class="gl-ml-1 gl-text-orange-700 align-middle"
/>
<slot name="extra-controls"></slot>
<gl-loading-icon

View File

@ -0,0 +1,3 @@
import { initStaticSecurityConfiguration } from '~/security_configuration';
initStaticSecurityConfiguration(document.querySelector('#js-security-configuration-static'));

View File

@ -1,7 +1,5 @@
import ServerlessBundle from '~/serverless/serverless_bundle';
import initServerlessSurveyBanner from '~/serverless/survey_banner';
document.addEventListener('DOMContentLoaded', () => {
initServerlessSurveyBanner();
new ServerlessBundle(); // eslint-disable-line no-new
});
initServerlessSurveyBanner();
new ServerlessBundle(); // eslint-disable-line no-new

View File

@ -1,4 +1,4 @@
import { s__ } from '~/locale';
import { s__, __ } from '~/locale';
// Translations strings
@ -35,8 +35,6 @@ export const ASYNC_DELETE_IMAGE_ERROR_MESSAGE = s__(
export const DELETE_IMAGE_SUCCESS_MESSAGE = s__(
'ContainerRegistry|%{title} was successfully scheduled for deletion',
);
export const IMAGE_REPOSITORY_LIST_LABEL = s__('ContainerRegistry|Image Repositories');
export const SEARCH_PLACEHOLDER_TEXT = s__('ContainerRegistry|Filter by name');
export const EMPTY_RESULT_TITLE = s__('ContainerRegistry|Sorry, your filter produced no results.');
export const EMPTY_RESULT_MESSAGE = s__(
'ContainerRegistry|To widen your search, change or remove the filters above.',
@ -47,3 +45,9 @@ export const EMPTY_RESULT_MESSAGE = s__(
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
export const IMAGE_FAILED_DELETED_STATUS = 'DELETE_FAILED';
export const GRAPHQL_PAGE_SIZE = 10;
export const SORT_FIELDS = [
{ orderBy: 'UPDATED', label: __('Updated') },
{ orderBy: 'CREATED', label: __('Created') },
{ orderBy: 'NAME', label: __('Name') },
];

View File

@ -6,9 +6,17 @@ query getContainerRepositoriesDetails(
$after: String
$before: String
$isGroupPage: Boolean!
$sort: ContainerRepositorySort
) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) {
containerRepositories(name: $name, after: $after, before: $before, first: $first, last: $last) {
containerRepositories(
name: $name
after: $after
before: $before
first: $first
last: $last
sort: $sort
) {
nodes {
id
tagsCount
@ -16,7 +24,14 @@ query getContainerRepositoriesDetails(
}
}
group(fullPath: $fullPath) @include(if: $isGroupPage) {
containerRepositories(name: $name, after: $after, before: $before, first: $first, last: $last) {
containerRepositories(
name: $name
after: $after
before: $before
first: $first
last: $last
sort: $sort
) {
nodes {
id
tagsCount

View File

@ -7,12 +7,12 @@ import {
GlLink,
GlAlert,
GlSkeletonLoader,
GlSearchBoxByClick,
} from '@gitlab/ui';
import { get } from 'lodash';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
import Tracking from '~/tracking';
import createFlash from '~/flash';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import RegistryHeader from '../components/list_page/registry_header.vue';
import DeleteImage from '../components/delete_image.vue';
@ -25,12 +25,11 @@ import {
CONNECTION_ERROR_MESSAGE,
REMOVE_REPOSITORY_MODAL_TEXT,
REMOVE_REPOSITORY_LABEL,
SEARCH_PLACEHOLDER_TEXT,
IMAGE_REPOSITORY_LIST_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
SORT_FIELDS,
} from '../constants/index';
export default {
@ -58,9 +57,9 @@ export default {
GlLink,
GlAlert,
GlSkeletonLoader,
GlSearchBoxByClick,
RegistryHeader,
DeleteImage,
RegistrySearch,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -77,11 +76,10 @@ export default {
CONNECTION_ERROR_MESSAGE,
REMOVE_REPOSITORY_MODAL_TEXT,
REMOVE_REPOSITORY_LABEL,
SEARCH_PLACEHOLDER_TEXT,
IMAGE_REPOSITORY_LIST_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
},
searchConfig: SORT_FIELDS,
apollo: {
baseImages: {
query: getContainerRepositoriesQuery,
@ -123,7 +121,8 @@ export default {
containerRepositoriesCount: 0,
itemToDelete: {},
deleteAlertType: null,
searchValue: null,
filter: [],
sorting: { orderBy: 'UPDATED', sort: 'desc' },
name: null,
mutationLoading: false,
fetchAdditionalDetails: false,
@ -142,6 +141,7 @@ export default {
queryVariables() {
return {
name: this.name,
sort: this.sortBy,
fullPath: this.config.isGroupPage ? this.config.groupPath : this.config.projectPath,
isGroupPage: this.config.isGroupPage,
first: GRAPHQL_PAGE_SIZE,
@ -166,6 +166,10 @@ export default {
? DELETE_IMAGE_SUCCESS_MESSAGE
: DELETE_IMAGE_ERROR_MESSAGE;
},
sortBy() {
const { orderBy, sort } = this.sorting;
return `${orderBy}_${sort}`.toUpperCase();
},
},
mounted() {
// If the two graphql calls - which are not batched - resolve togheter we will have a race
@ -231,6 +235,16 @@ export default {
this.track('confirm_delete');
this.mutationLoading = true;
},
updateSorting(value) {
this.sorting = {
...this.sorting,
...value,
};
},
doFilter() {
const search = this.filter.find((i) => i.type === 'filtered-search-term');
this.name = search?.value?.data;
},
},
};
</script>
@ -283,6 +297,16 @@ export default {
</template>
</registry-header>
<registry-search
:filter="filter"
:sorting="sorting"
:tokens="[]"
:sortable-fields="$options.searchConfig"
@sorting:changed="updateSorting"
@filter:changed="filter = $event"
@filter:submit="doFilter"
/>
<div v-if="isLoading" class="gl-mt-5">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
@ -298,20 +322,6 @@ export default {
</div>
<template v-else>
<template v-if="images.length > 0 || name">
<div class="gl-display-flex gl-p-1 gl-mt-3" data-testid="listHeader">
<div class="gl-flex-fill-1">
<h5>{{ $options.i18n.IMAGE_REPOSITORY_LIST_LABEL }}</h5>
</div>
<div>
<gl-search-box-by-click
v-model="searchValue"
:placeholder="$options.i18n.SEARCH_PLACEHOLDER_TEXT"
@clear="name = null"
@submit="name = $event"
/>
</div>
</div>
<image-list
v-if="images.length"
:images="images"

View File

@ -0,0 +1,23 @@
<script>
import ConfigurationTable from './configuration_table.vue';
export default {
components: {
ConfigurationTable,
},
};
</script>
<template>
<article>
<header>
<h4 class="gl-my-5">
{{ __('Security Configuration') }}
</h4>
<h5 class="gl-font-lg gl-mt-7">
{{ s__('SecurityConfiguration|Testing & Compliance') }}
</h5>
</header>
<configuration-table />
</article>
</template>

View File

@ -0,0 +1,97 @@
<script>
import { GlLink, GlSprintf, GlTable, GlAlert } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST,
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_CONTAINER_SCANNING,
REPORT_TYPE_COVERAGE_FUZZING,
REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants';
import ManageSast from './manage_sast.vue';
import Upgrade from './upgrade.vue';
import { features } from './features_constants';
const borderClasses = 'gl-border-b-1! gl-border-b-solid! gl-border-gray-100!';
const thClass = `gl-text-gray-900 gl-bg-transparent! ${borderClasses}`;
export default {
components: {
GlLink,
GlSprintf,
GlTable,
GlAlert,
},
data: () => ({
features,
errorMessage: '',
}),
methods: {
getFeatureDocumentationLinkLabel(item) {
return sprintf(s__('SecurityConfiguration|Feature documentation for %{featureName}'), {
featureName: item.name,
});
},
onError(value) {
this.errorMessage = value;
},
getComponentForItem(item) {
const COMPONENTS = {
[REPORT_TYPE_SAST]: ManageSast,
[REPORT_TYPE_DAST]: Upgrade,
[REPORT_TYPE_DEPENDENCY_SCANNING]: Upgrade,
[REPORT_TYPE_CONTAINER_SCANNING]: Upgrade,
[REPORT_TYPE_COVERAGE_FUZZING]: Upgrade,
[REPORT_TYPE_LICENSE_COMPLIANCE]: Upgrade,
};
return COMPONENTS[item.type];
},
},
table: {
fields: [
{
key: 'feature',
label: s__('SecurityConfiguration|Security Control'),
thClass,
},
{
key: 'manage',
label: s__('SecurityConfiguration|Manage'),
thClass,
},
],
items: features,
},
};
</script>
<template>
<div>
<gl-alert v-if="errorMessage" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
<gl-table :items="$options.table.items" :fields="$options.table.fields" stacked="md">
<template #cell(feature)="{ item }">
<div class="gl-text-gray-900">
{{ item.name }}
</div>
<div>
{{ item.description }}
<gl-link
target="_blank"
:href="item.link"
:aria-label="getFeatureDocumentationLinkLabel(item)"
>
{{ s__('SecurityConfiguration|More information') }}
</gl-link>
</div>
</template>
<template #cell(manage)="{ item }">
<component :is="getComponentForItem(item)" :data-testid="item.type" @error="onError" />
</template>
</gl-table>
</div>
</template>

View File

@ -0,0 +1,112 @@
import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST,
REPORT_TYPE_SECRET_DETECTION,
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_CONTAINER_SCANNING,
REPORT_TYPE_COVERAGE_FUZZING,
REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants';
/**
* Translations & helpPagePaths for Static Security Configuration Page
*/
export const SAST_NAME = s__('Static Application Security Testing (SAST)');
export const SAST_DESCRIPTION = s__('Analyze your source code for known vulnerabilities.');
export const SAST_HELP_PATH = helpPagePath('user/application_security/sast/index');
export const DAST_NAME = s__('Dynamic Application Security Testing (DAST)');
export const DAST_DESCRIPTION = s__('Analyze a review version of your web application.');
export const DAST_HELP_PATH = helpPagePath('user/application_security/dast/index');
export const SECRET_DETECTION_NAME = s__('Secret Detection');
export const SECRET_DETECTION_DESCRIPTION = s__(
'Analyze your source code and git history for secrets.',
);
export const SECRET_DETECTION_HELP_PATH = helpPagePath(
'user/application_security/secret_detection/index',
);
export const DEPENDENCY_SCANNING_NAME = s__('Dependency Scanning');
export const DEPENDENCY_SCANNING_DESCRIPTION = s__(
'Analyze your dependencies for known vulnerabilities.',
);
export const DEPENDENCY_SCANNING_HELP_PATH = helpPagePath(
'user/application_security/dependency_scanning/index',
);
export const CONTAINER_SCANNING_NAME = s__('Container Scanning');
export const CONTAINER_SCANNING_DESCRIPTION = s__(
'Check your Docker images for known vulnerabilities.',
);
export const CONTAINER_SCANNING_HELP_PATH = helpPagePath(
'user/application_security/container_scanning/index',
);
export const COVERAGE_FUZZING_NAME = s__('Coverage Fuzzing');
export const COVERAGE_FUZZING_DESCRIPTION = s__(
'Find bugs in your code with coverage-guided fuzzing.',
);
export const COVERAGE_FUZZING_HELP_PATH = helpPagePath(
'user/application_security/coverage_fuzzing/index',
);
export const LICENSE_COMPLIANCE_NAME = s__('License Compliance');
export const LICENSE_COMPLIANCE_DESCRIPTION = s__(
'Search your project dependencies for their licenses and apply policies.',
);
export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath(
'user/compliance/license_compliance/index',
);
export const UPGRADE_CTA = s__(
'SecurityConfiguration|Available with %{linkStart}upgrade or free trial%{linkEnd}',
);
export const features = [
{
name: SAST_NAME,
description: SAST_DESCRIPTION,
helpPath: SAST_HELP_PATH,
type: REPORT_TYPE_SAST,
},
{
name: DAST_NAME,
description: DAST_DESCRIPTION,
helpPath: DAST_HELP_PATH,
type: REPORT_TYPE_DAST,
},
{
name: SECRET_DETECTION_NAME,
description: SECRET_DETECTION_DESCRIPTION,
helpPath: SECRET_DETECTION_HELP_PATH,
type: REPORT_TYPE_SECRET_DETECTION,
},
{
name: DEPENDENCY_SCANNING_NAME,
description: DEPENDENCY_SCANNING_DESCRIPTION,
helpPath: DEPENDENCY_SCANNING_HELP_PATH,
type: REPORT_TYPE_DEPENDENCY_SCANNING,
},
{
name: CONTAINER_SCANNING_NAME,
description: CONTAINER_SCANNING_DESCRIPTION,
helpPath: CONTAINER_SCANNING_HELP_PATH,
type: REPORT_TYPE_CONTAINER_SCANNING,
},
{
name: COVERAGE_FUZZING_NAME,
description: COVERAGE_FUZZING_DESCRIPTION,
helpPath: COVERAGE_FUZZING_HELP_PATH,
type: REPORT_TYPE_COVERAGE_FUZZING,
},
{
name: LICENSE_COMPLIANCE_NAME,
description: LICENSE_COMPLIANCE_DESCRIPTION,
helpPath: LICENSE_COMPLIANCE_HELP_PATH,
type: REPORT_TYPE_LICENSE_COMPLIANCE,
},
];

View File

@ -0,0 +1,57 @@
<script>
import { GlButton } from '@gitlab/ui';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
export default {
components: {
GlButton,
},
inject: {
projectPath: {
from: 'projectPath',
default: '',
},
},
data: () => ({
isLoading: false,
}),
methods: {
async mutate() {
this.isLoading = true;
try {
const { data } = await this.$apollo.mutate({
mutation: configureSastMutation,
variables: {
input: {
projectPath: this.projectPath,
configuration: { global: [], pipeline: [], analyzers: [] },
},
},
});
const { errors, successPath } = data.configureSast;
if (errors.length > 0) {
throw new Error(errors[0]);
}
if (!successPath) {
throw new Error(s__('SecurityConfiguration|SAST merge request creation mutation failed'));
}
redirectTo(successPath);
} catch (e) {
this.$emit('error', e.message);
this.isLoading = false;
}
},
},
};
</script>
<template>
<gl-button :loading="isLoading" variant="success" category="secondary" @click="mutate">{{
s__('SecurityConfiguration|Configure via Merge Request')
}}</gl-button>
</template>

View File

@ -0,0 +1,26 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { UPGRADE_CTA } from './features_constants';
export default {
components: {
GlLink,
GlSprintf,
},
i18n: {
UPGRADE_CTA,
},
};
</script>
<template>
<span>
<gl-sprintf :message="$options.i18n.UPGRADE_CTA">
<template #link="{ content }">
<gl-link target="_blank" href="https://about.gitlab.com/pricing/">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</span>
</template>

View File

@ -0,0 +1,6 @@
mutation configureSast($input: ConfigureSastInput!) {
configureSast(input: $input) {
successPath
errors
}
}

View File

@ -0,0 +1,29 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import SecurityConfigurationApp from './components/app.vue';
export const initStaticSecurityConfiguration = (el) => {
if (!el) {
return null;
}
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const { projectPath } = el.dataset;
return new Vue({
el,
apolloProvider,
provide: {
projectPath,
},
render(createElement) {
return createElement(SecurityConfigurationApp);
},
});
};

View File

@ -1,11 +1,9 @@
<script>
// NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
const DEFAULT_RENDER_COUNT = 5;
const LOADING_STATE = 'loading';
const SUCCESS_STATE = 'success';
export default {
components: {
@ -34,35 +32,21 @@ export default {
data() {
return {
showLess: true,
loading: false,
requestedReviewSuccess: false,
loadingStates: {},
};
},
computed: {
firstUser() {
return this.users[0];
},
hasOneUser() {
return this.users.length === 1;
},
hiddenReviewersLabel() {
const { numberOfHiddenReviewers } = this;
return sprintf(__('+ %{numberOfHiddenReviewers} more'), { numberOfHiddenReviewers });
},
renderShowMoreSection() {
return this.users.length > DEFAULT_RENDER_COUNT;
},
numberOfHiddenReviewers() {
return this.users.length - DEFAULT_RENDER_COUNT;
},
uncollapsedUsers() {
const uncollapsedLength = this.showLess
? Math.min(this.users.length, DEFAULT_RENDER_COUNT)
: this.users.length;
return this.showLess ? this.users.slice(0, uncollapsedLength) : this.users;
},
username() {
return `@${this.firstUser.username}`;
watch: {
users: {
handler(users) {
this.loadingStates = users.reduce(
(acc, user) => ({
...acc,
[user.id]: acc[user.id] || null,
}),
this.loadingStates,
);
},
immediate: true,
},
},
methods: {
@ -70,21 +54,23 @@ export default {
this.showLess = !this.showLess;
},
reRequestReview(userId) {
this.loading = true;
this.loadingStates[userId] = LOADING_STATE;
this.$emit('request-review', { userId, callback: this.requestReviewComplete });
},
requestReviewComplete(success) {
requestReviewComplete(userId, success) {
if (success) {
this.requestedReviewSuccess = true;
this.loadingStates[userId] = SUCCESS_STATE;
setTimeout(() => {
this.requestedReviewSuccess = false;
this.loadingStates[userId] = null;
}, 1500);
} else {
this.loadingStates[userId] = null;
}
this.loading = false;
},
},
LOADING_STATE,
SUCCESS_STATE,
};
</script>
@ -100,20 +86,22 @@ export default {
<div class="gl-ml-3">@{{ user.username }}</div>
</reviewer-avatar-link>
<gl-icon
v-if="requestedReviewSuccess"
v-if="loadingStates[user.id] === $options.SUCCESS_STATE"
:size="24"
name="check"
class="float-right gl-text-green-500"
data-testid="re-request-success"
/>
<gl-button
v-else-if="user.can_update_merge_request && user.reviewed"
v-gl-tooltip.left
:title="__('Re-request review')"
:loading="loading"
:loading="loadingStates[user.id] === $options.LOADING_STATE"
class="float-right gl-text-gray-500!"
size="small"
icon="redo"
variant="link"
data-testid="re-request-button"
@click="reRequestReview(user.id)"
/>
</div>

View File

@ -58,9 +58,9 @@ export default class SidebarMediator {
.then(() => {
this.store.updateReviewer(userId);
toast(__('Requested review'));
callback(true);
callback(userId, true);
})
.catch(() => callback(false));
.catch(() => callback(userId, false));
}
setMoveToProjectId(projectId) {

View File

@ -56,7 +56,10 @@ export default {
mergeRequestsFfOnlyEnabled: data.project.mergeRequestsFfOnlyEnabled,
onlyAllowMergeIfPipelineSucceeds: data.project.onlyAllowMergeIfPipelineSucceeds,
};
this.removeSourceBranch = data.project.mergeRequest.shouldRemoveSourceBranch;
this.removeSourceBranch =
data.project.mergeRequest.shouldRemoveSourceBranch ||
data.project.mergeRequest.forceRemoveSourceBranch ||
false;
this.commitMessage = data.project.mergeRequest.defaultMergeCommitMessage;
this.squashBeforeMerge = data.project.mergeRequest.squashOnMerge;
this.isSquashReadOnly = data.project.squashReadOnly;

View File

@ -5,6 +5,7 @@ fragment ReadyToMerge on Project {
mergeRequest(iid: $iid) {
autoMergeEnabled
shouldRemoveSourceBranch
forceRemoveSourceBranch
defaultMergeCommitMessage
defaultMergeCommitMessageWithDescription
defaultSquashCommitMessage

View File

@ -17,7 +17,13 @@ export const REPORT_FILE_TYPES = {
* Security scan report types, as provided by the backend.
*/
export const REPORT_TYPE_SAST = 'sast';
export const REPORT_TYPE_DAST = 'dast';
export const REPORT_TYPE_SECRET_DETECTION = 'secret_detection';
export const REPORT_TYPE_DEPENDENCY_SCANNING = 'dependency_scanning';
export const REPORT_TYPE_CONTAINER_SCANNING = 'container_scanning';
export const REPORT_TYPE_COVERAGE_FUZZING = 'coverage_fuzzing';
export const REPORT_TYPE_LICENSE_COMPLIANCE = 'license_compliance';
export const REPORT_TYPE_API_FUZZING = 'api_fuzzing';
/**
* SecurityReportTypeEnum values for use with GraphQL.

View File

@ -243,7 +243,8 @@ module NotesActions
:type,
:note,
:line_code, # LegacyDiffNote
:position # DiffNote
:position, # DiffNote
:confidential
).tap do |create_params|
create_params.merge!(
params.permit(:merge_request_diff_head_sha, :in_reply_to_discussion_id)

View File

@ -7,30 +7,26 @@
#
# include RedisTracking
#
# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature
#
# if the feature flag is enabled by default you should use
# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature, feature_default_enabled: true
# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score'
#
# You can also pass custom conditions using `if:`, using the same format as with Rails callbacks.
module RedisTracking
extend ActiveSupport::Concern
class_methods do
def track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false, if: nil)
def track_redis_hll_event(*controller_actions, name:, if: nil)
custom_conditions = Array.wrap(binding.local_variable_get('if'))
conditions = [:trackable_request?, *custom_conditions]
after_action only: controller_actions, if: conditions do
track_unique_redis_hll_event(name, feature, feature_default_enabled)
track_unique_redis_hll_event(name)
end
end
end
private
def track_unique_redis_hll_event(event_name, feature, feature_default_enabled)
return unless metric_feature_enabled?(feature, feature_default_enabled)
def track_unique_redis_hll_event(event_name)
return unless visitor_id
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: visitor_id)
@ -40,10 +36,6 @@ module RedisTracking
request.format.html? && request.headers['DNT'] != '1'
end
def metric_feature_enabled?(feature, default_enabled)
Feature.enabled?(feature, default_enabled: default_enabled)
end
def visitor_id
return cookies[:visitor_id] if cookies[:visitor_id].present?
return unless current_user

View File

@ -15,7 +15,7 @@ module SnippetsActions
skip_before_action :verify_authenticity_token,
if: -> { action_name == 'show' && js_request? }
track_redis_hll_event :show, name: 'i_snippets_show', feature: :usage_data_i_snippets_show, feature_default_enabled: true
track_redis_hll_event :show, name: 'i_snippets_show'
respond_to :html
end

View File

@ -36,8 +36,7 @@ module WikiActions
# NOTE: We want to include wiki page views in the same counter as the other
# Event-based wiki actions tracked through TrackUniqueEvents, so we use the same event name.
track_redis_hll_event :show, name: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION.to_s,
feature: :track_unique_wiki_page_views, feature_default_enabled: true
track_redis_hll_event :show, name: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION.to_s
helper_method :view_file_button, :diff_file_html_data

View File

@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
record_experiment_user(:ci_syntax_templates, namespace_id: @project.namespace_id) if params[:file_name] == @project.ci_config_path_or_default
end
track_redis_hll_event :create, :update, name: 'g_edit_by_sfe', feature: :track_editor_edit_actions, feature_default_enabled: true
track_redis_hll_event :create, :update, name: 'g_edit_by_sfe'
feature_category :source_code_management

View File

@ -52,6 +52,7 @@ class Projects::IssuesController < Projects::ApplicationController
real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project)
push_to_gon_attributes(:features, real_time_feature_flag, real_time_enabled)
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)

View File

@ -9,6 +9,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :set_pipeline_path, only: [:show]
before_action :authorize_read_pipeline!
before_action :authorize_read_build!, only: [:index]
before_action :authorize_read_analytics!, only: [:charts]
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do

View File

@ -5,7 +5,7 @@ class SearchController < ApplicationController
include SearchHelper
include RedisTracking
track_redis_hll_event :show, name: 'i_search_total', feature: :search_track_unique_users, feature_default_enabled: true
track_redis_hll_event :show, name: 'i_search_total'
around_action :allow_gitaly_ref_name_caching

View File

@ -6,11 +6,19 @@ query getProjectContainerRepositories(
$after: String
$before: String
$isGroupPage: Boolean!
$sort: ContainerRepositorySort
) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) {
__typename
containerRepositoriesCount
containerRepositories(name: $name, after: $after, before: $before, first: $first, last: $last) {
containerRepositories(
name: $name
after: $after
before: $before
first: $first
last: $last
sort: $sort
) {
__typename
nodes {
id
@ -35,7 +43,14 @@ query getProjectContainerRepositories(
group(fullPath: $fullPath) @include(if: $isGroupPage) {
__typename
containerRepositoriesCount
containerRepositories(name: $name, after: $after, before: $before, first: $first, last: $last) {
containerRepositories(
name: $name
after: $after
before: $before
first: $first
last: $last
sort: $sort
) {
__typename
nodes {
id

View File

@ -12,6 +12,9 @@ module Types
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the user.'
field :bot, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates if the user is a bot.',
method: :bot?
field :username, GraphQL::STRING_TYPE, null: false,
description: 'Username of the user. Unique within this instance of GitLab.'
field :name, GraphQL::STRING_TYPE, null: false,

View File

@ -16,6 +16,7 @@ module Ci
include ShaAttribute
include FromUnion
include UpdatedAtFilterable
include EachBatch
MAX_OPEN_MERGE_REQUESTS_REFS = 4

View File

@ -55,6 +55,7 @@ class CommitStatus < ApplicationRecord
scope :for_ids, -> (ids) { where(id: ids) }
scope :for_ref, -> (ref) { where(ref: ref) }
scope :by_name, -> (name) { where(name: name) }
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
scope :for_project_paths, -> (paths) do
where(project: Project.where_full_path_in(Array(paths)))

View File

@ -47,6 +47,19 @@ class Member < ApplicationRecord
},
if: :project_bot?
scope :in_hierarchy, ->(source) do
groups = source.root_ancestor.self_and_descendants
group_members = Member.default_scoped.where(source: groups)
projects = source.root_ancestor.all_projects
project_members = Member.default_scoped.where(source: projects)
Member.default_scoped.from_union([
group_members,
project_members
]).merge(self)
end
# This scope encapsulates (most of) the conditions a row in the member table
# must satisfy if it is a valid permission. Of particular note:
#
@ -79,12 +92,18 @@ class Member < ApplicationRecord
scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
scope :not_accepted_invitations, -> { invite.where(invite_accepted_at: nil) }
scope :not_accepted_invitations_by_user, -> (user) { not_accepted_invitations.where(created_by: user) }
scope :not_expired, -> (today = Date.current) { where(arel_table[:expires_at].gt(today).or(arel_table[:expires_at].eq(nil))) }
scope :created_today, -> do
now = Date.current
where(created_at: now.beginning_of_day..now.end_of_day)
end
scope :last_ten_days_excluding_today, -> (today = Date.current) { where(created_at: (today - 10).beginning_of_day..(today - 1).end_of_day) }
scope :has_access, -> { active.where('access_level > 0') }

View File

@ -183,7 +183,17 @@ class PrometheusService < MonitoringService
manual_configuration? && google_iap_audience_client_id.present? && google_iap_service_account_json.present?
end
def clean_google_iap_service_account
return unless google_iap_service_account_json
google_iap_service_account_json
.then { |json| Gitlab::Json.parse(json) }
.except('token_credential_uri')
end
def iap_client
@iap_client ||= Google::Auth::Credentials.new(Gitlab::Json.parse(google_iap_service_account_json), target_audience: google_iap_audience_client_id).client
@iap_client ||= Google::Auth::Credentials
.new(clean_google_iap_service_account, target_audience: google_iap_audience_client_id)
.client
end
end

View File

@ -31,6 +31,7 @@ class ProjectStatistics < ApplicationRecord
scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
scope :for_namespaces, -> (namespaces) { where(namespace: namespaces) }
scope :with_any_ci_minutes_used, -> { where.not(shared_runners_seconds: 0) }
def total_repository_size
repository_size + lfs_objects_size

View File

@ -221,6 +221,7 @@ class ProjectPolicy < BasePolicy
enable :read_pages_content
enable :read_release
enable :read_analytics
enable :read_insights
end
# These abilities are not allowed to admins that are not members of the project,
@ -450,6 +451,9 @@ class ProjectPolicy < BasePolicy
rule { analytics_disabled }.policy do
prevent(:read_analytics)
prevent(:read_insights)
prevent(:read_cycle_analytics)
prevent(:read_repository_graphs)
end
rule { wiki_disabled }.policy do
@ -523,6 +527,7 @@ class ProjectPolicy < BasePolicy
enable :read_cycle_analytics
enable :read_pages_content
enable :read_analytics
enable :read_insights
# NOTE: may be overridden by IssuePolicy
enable :read_issue

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Ci
class AbortProjectPipelinesService
# Danger: Cancels in bulk without callbacks
# Only for pipeline abandonment scenarios (current example: project delete)
def execute(project)
return unless Feature.enabled?(:abort_deleted_project_pipelines, default_enabled: :yaml)
pipelines = project.all_pipelines.cancelable
bulk_abort!(pipelines, status: :canceled)
ServiceResponse.success(message: 'Pipelines canceled')
end
private
def bulk_abort!(pipelines, status:)
pipelines.each_batch do |pipeline_batch|
CommitStatus.in_pipelines(pipeline_batch).in_batches.update_all(status: status) # rubocop: disable Cop/InBatches
pipeline_batch.update_all(status: status)
end
end
end
end

View File

@ -6,6 +6,7 @@ module Ci
# This is a bug with CodeReuse/ActiveRecord cop
# https://gitlab.com/gitlab-org/gitlab/issues/32332
def execute(user)
# TODO: fix N+1 queries https://gitlab.com/gitlab-org/gitlab/-/issues/300685
user.pipelines.cancelable.find_each(&:cancel_running)
ServiceResponse.success(message: 'Pipeline canceled')

View File

@ -18,7 +18,6 @@ module DesignManagement
return error("Only #{MAX_FILES} files are allowed simultaneously") if files.size > MAX_FILES
return error("Duplicate filenames are not allowed!") if files.map(&:original_filename).uniq.length != files.length
return error("Design copy is in progress") if design_collection.copy_in_progress?
return error("Filenames contained invalid characters and could not be saved") if files.any?(&:filename_sanitized?)
uploaded_designs, version = upload_designs!
skipped_designs = designs - uploaded_designs

View File

@ -2,12 +2,12 @@
module Members
class CreateService < Members::BaseService
include Gitlab::Utils::StrongMemoize
DEFAULT_LIMIT = 100
def execute(source)
return error(s_('AddMember|No users specified.')) if params[:user_ids].blank?
user_ids = params[:user_ids].split(',').uniq.flatten
return error(s_('AddMember|No users specified.')) if user_ids.blank?
return error(s_("AddMember|Too many users specified (limit is %{user_limit})") % { user_limit: user_limit }) if
user_limit && user_ids.size > user_limit
@ -47,6 +47,13 @@ module Members
private
def user_ids
strong_memoize(:user_ids) do
ids = params[:user_ids] || ''
ids.split(',').uniq.flatten
end
end
def user_limit
limit = params.fetch(:limit, DEFAULT_LIMIT)

View File

@ -21,11 +21,14 @@ module Projects
def execute
return false unless can?(current_user, :remove_project, project)
project.update_attribute(:pending_delete, true)
# Flush the cache for both repositories. This has to be done _before_
# removing the physical repositories as some expiration code depends on
# Git data (e.g. a list of branch names).
flush_caches(project)
::Ci::AbortProjectPipelinesService.new.execute(project)
Projects::UnlinkForkService.new(project, current_user).execute
attempt_destroy(project)

View File

@ -1,6 +1,6 @@
- page_title _("Container Registry")
- @content_class = "limit-container-width" unless fluid_layout
- add_page_startup_graphql_call('container_registry/get_container_repositories', { fullPath: @group.full_path, first: 10, name: nil, isGroupPage: true} )
- add_page_startup_graphql_call('container_registry/get_container_repositories', { fullPath: @group.full_path, first: 10, name: nil, isGroupPage: true, sort: nil} )
%section
#js-container-registry{ data: { endpoint: group_container_registries_path(@group),

View File

@ -1,353 +0,0 @@
#modal-shortcuts.modal{ tabindex: -1 }
.modal-dialog.modal-lg.modal-1040
.modal-content
.modal-header
.js-toggle-shortcuts
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
.row
.col-lg-4
%table.shortcut-mappings.text-2
%tbody
%tr
%th
%th= _('Global Shortcuts')
%tr
%td.shortcut
%kbd ?
%td= _('Toggle this dialog')
%tr
%td.shortcut
%kbd shift p
%td= _('Go to your projects')
%tr
%td.shortcut
%kbd shift g
%td= _('Go to your groups')
%tr
%td.shortcut
%kbd shift a
%td= _('Go to the activity feed')
%tr
%td.shortcut
%kbd shift l
%td= _('Go to the milestone list')
%tr
%td.shortcut
%kbd shift s
%td= _('Go to your snippets')
%tr
%td.shortcut
%kbd s
\/
%kbd /
%td= _('Start search')
%tr
%td.shortcut
%kbd shift i
%td= _('Go to your issues')
%tr
%td.shortcut
%kbd shift m
%td= _('Go to your merge requests')
%tr
%td.shortcut
%kbd shift t
%td= _('Go to your To-Do list')
%tr
%td.shortcut
%kbd p
%kbd b
%td= _('Toggle the Performance Bar')
- if Gitlab.com?
%tr
%td.shortcut
%kbd g
%kbd x
%td= _('Toggle GitLab Next')
%tbody
%tr
%th
%th= _('Editing')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; shift p
- else
%kbd ctrl shift p
%td= _('Toggle Markdown preview')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-up', size: 12)
%td= _('Edit your most recent comment in a thread (from an empty textarea)')
%tbody
%tr
%th
%th= _('Wiki')
%tr
%td.shortcut
%kbd e
%td= _('Edit wiki page')
%tbody
%tr
%th
%th= _('Repository Graph')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-left', size: 12)
\/
%kbd h
%td= _('Scroll left')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-right', size: 12)
\/
%kbd l
%td= _('Scroll right')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-up', size: 12)
\/
%kbd k
%td= _('Scroll up')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-down', size: 12)
\/
%kbd j
%td= _('Scroll down')
%tr
%td.shortcut
%kbd
shift
= sprite_icon('arrow-up', size: 12)
\/ k
%td= _('Scroll to top')
%tr
%td.shortcut
%kbd
shift
= sprite_icon('arrow-down', size: 12)
\/ j
%td= _('Scroll to bottom')
.col-lg-4
%table.shortcut-mappings.text-2
%tbody
%tr
%th
%th= _('Project')
%tr
%td.shortcut
%kbd g
%kbd p
%td= _('Go to the project\'s overview page')
%tr
%td.shortcut
%kbd g
%kbd v
%td= _('Go to the project\'s activity feed')
%tr
%td.shortcut
%kbd g
%kbd r
%td= _('Go to releases')
%tr
%td.shortcut
%kbd g
%kbd f
%td= _('Go to files')
%tr
%td.shortcut
%kbd t
%td= _('Go to find file')
%tr
%td.shortcut
%kbd g
%kbd c
%td= _('Go to commits')
%tr
%td.shortcut
%kbd g
%kbd n
%td= _('Go to repository graph')
%tr
%td.shortcut
%kbd g
%kbd d
%td= _('Go to repository charts')
%tr
%td.shortcut
%kbd g
%kbd i
%td= _('Go to issues')
%tr
%td.shortcut
%kbd i
%td= _('New issue')
%tr
%td.shortcut
%kbd g
%kbd b
%td= _('Go to issue boards')
%tr
%td.shortcut
%kbd g
%kbd m
%td= _('Go to merge requests')
%tr
%td.shortcut
%kbd g
%kbd j
%td= _('Go to jobs')
%tr
%td.shortcut
%kbd g
%kbd l
%td= _('Go to metrics')
%tr
%td.shortcut
%kbd g
%kbd e
%td= _('Go to environments')
%tr
%td.shortcut
%kbd g
%kbd k
%td= _('Go to kubernetes')
%tr
%td.shortcut
%kbd g
%kbd s
%td= _('Go to snippets')
%tr
%td.shortcut
%kbd g
%kbd w
%td= _('Go to wiki')
%tbody
%tr
%th
%th= _('Project Files')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-up', size: 12)
%td= _('Move selection up')
%tr
%td.shortcut
%kbd
= sprite_icon('arrow-down', size: 12)
%td= _('Move selection down')
%tr
%td.shortcut
%kbd enter
%td= _('Open Selection')
%tr
%td.shortcut
%kbd esc
%td= _('Go back (while searching for files)')
%tr
%td.shortcut
%kbd y
%td= _('Go to file permalink (while viewing a file)')
.col-lg-4
%table.shortcut-mappings.text-2
%tbody
%tr
%th
%th= _('Epics, Issues, and Merge Requests')
%tr
%td.shortcut
%kbd r
%td= _('Comment/Reply (quoting selected text)')
%tr
%td.shortcut
%kbd e
%td= _('Edit description')
%tr
%td.shortcut
%kbd l
%td= _('Change label')
%tbody
%tr
%th
%th= _('Issues and Merge Requests')
%tr
%td.shortcut
%kbd a
%td= _('Change assignee')
%tr
%td.shortcut
%kbd m
%td= _('Change milestone')
%tbody
%tr
%th
%th= _('Merge Requests')
%tr
%td.shortcut
%kbd ]
\/
%kbd j
%td= _('Next file in diff')
%tr
%td.shortcut
%kbd [
\/
%kbd k
%td= _('Previous file in diff')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; p
- else
%kbd ctrl p
%td= _('Go to file')
%tr
%td.shortcut
%kbd n
%td= _('Next unresolved discussion')
%tr
%td.shortcut
%kbd p
%td= _('Previous unresolved discussion')
%tr
%td.shortcut
%kbd b
%td= _('Copy source branch name')
%tbody
%tr
%th
%th= _('Merge Request Commits')
%tr
%td.shortcut
%kbd c
%td= _('Next commit')
%tr
%td.shortcut
%kbd x
%td= _('Previous commit')
%tbody
%tr
%th
%th= _('Web IDE')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; p
- else
%kbd ctrl p
%td= _('Go to file')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; enter
- else
%kbd ctrl enter
%td= _('Commit (when editing commit message)')

View File

@ -1,3 +0,0 @@
:plain
$("body").append("#{escape_javascript(render('shortcuts'))}");
$("#modal-shortcuts").modal();

View File

@ -28,7 +28,7 @@
= _('GPG Key ID:')
%span.monospace= signature.gpg_key_primary_keyid
= link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
= link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link gl-display-block')
%a{ role: 'button', tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } }
= label

View File

@ -1,6 +1,6 @@
- page_title _("Container Registry")
- @content_class = "limit-container-width" unless fluid_layout
- add_page_startup_graphql_call('container_registry/get_container_repositories', { fullPath: @project.full_path, first: 10, name: nil, isGroupPage: false} )
- add_page_startup_graphql_call('container_registry/get_container_repositories', { fullPath: @project.full_path, first: 10, name: nil, isGroupPage: false, sort: nil} )
%section
#js-container-registry{ data: { endpoint: project_container_registry_index_path(@project),

View File

@ -1,4 +1,4 @@
- breadcrumb_title _("Security Configuration")
- page_title _("Security Configuration")
#js-security-configuration-static
#js-security-configuration-static{ data: {project_path: @project.full_path} }

View File

@ -0,0 +1,5 @@
---
title: Support setting confidential note attribute in UI
merge_request: 52949
author: Lee Tickett @leetickett
type: added

View File

@ -0,0 +1,5 @@
---
title: Add bot to User GraphQL Type
merge_request: 52933
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add sort to container registry list page
merge_request: 53820
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove namespace_onboarding_actions table
merge_request: 53488
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix charts sometimes being hidden on milestone page
merge_request: 52552
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: 'BulkImports: Migrate Group Membership'
merge_request: 53083
author:
type: added

View File

@ -1,5 +0,0 @@
---
title: 'Designs: return error if uploading filenames with special chars'
merge_request: 44136
author: Sushil Khanchi @khanchi97
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Show helper link on a new line in GPG status popover
merge_request: 52894
author: Yogi (@yo)
type: changed

View File

@ -0,0 +1,5 @@
---
title: Reset CI minutes only for namespaces that used minutes.
merge_request: 53740
author:
type: changed

View File

@ -0,0 +1,8 @@
---
name: abort_deleted_project_pipelines
introduced_by_url: https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1220
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/301106
milestone: '13.9'
type: development
group: group::continuous integration
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: confidential_notes
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52949
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/207474
milestone: '13.9'
type: development
group: group::product planning
default_enabled: false

View File

@ -3,3 +3,4 @@ filenames:
- ee/app/assets/javascripts/oncall_schedules/graphql/mutations/update_oncall_schedule_rotation.mutation.graphql
- ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/api_fuzzing_ci_configuration.query.graphql
- ee/app/assets/javascripts/on_demand_scans/graphql/dast_profile_update.mutation.graphql
- ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/create_api_fuzzing_configuration.mutation.graphql

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddDailyInvitesToPlanLimits < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column(:plan_limits, :daily_invites, :integer, default: 0, null: false)
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class InsertDailyInvitesPlanLimits < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
return unless Gitlab.com?
create_or_update_plan_limit('daily_invites', 'free', 20)
create_or_update_plan_limit('daily_invites', 'bronze', 0)
create_or_update_plan_limit('daily_invites', 'silver', 0)
create_or_update_plan_limit('daily_invites', 'gold', 0)
end
def down
return unless Gitlab.com?
create_or_update_plan_limit('daily_invites', 'free', 0)
create_or_update_plan_limit('daily_invites', 'bronze', 0)
create_or_update_plan_limit('daily_invites', 'silver', 0)
create_or_update_plan_limit('daily_invites', 'gold', 0)
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RemoveNamespaceIdForeignKeyOnNamespaceOnboardingActions < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
remove_foreign_key :namespace_onboarding_actions, :namespaces
end
end
def down
with_lock_retries do
add_foreign_key :namespace_onboarding_actions, :namespaces, on_delete: :cascade
end
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class RemoveNamespaceOnboardingActionsTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
drop_table :namespace_onboarding_actions
end
end
def down
with_lock_retries do
create_table :namespace_onboarding_actions do |t|
t.references :namespace, index: true, null: false
t.datetime_with_timezone :created_at, null: false
t.integer :action, limit: 2, null: false
end
end
end
end

View File

@ -0,0 +1 @@
1200747265d5095a86250020786d6f1e9e50bc75328a71de497046807afa89d7

View File

@ -0,0 +1 @@
febefead6f966960f6493d29add5f35fc4a1080b5118c5526502fa5fe1d29023

View File

@ -0,0 +1 @@
cdf55e9f2b1b9c375920198a438d29fe3c9ab7147f3c670b0d66b11d499573d9

View File

@ -0,0 +1 @@
d9cfb7515805e642c562b8be58b6cd482c24e62e76245db35a7d91b25c327d8d

View File

@ -14289,22 +14289,6 @@ CREATE TABLE namespace_limits (
temporary_storage_increase_ends_on date
);
CREATE TABLE namespace_onboarding_actions (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
action smallint NOT NULL
);
CREATE SEQUENCE namespace_onboarding_actions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE namespace_onboarding_actions_id_seq OWNED BY namespace_onboarding_actions.id;
CREATE TABLE namespace_package_settings (
namespace_id bigint NOT NULL,
maven_duplicates_allowed boolean DEFAULT true NOT NULL,
@ -15522,6 +15506,7 @@ CREATE TABLE plan_limits (
ci_max_artifact_size_api_fuzzing integer DEFAULT 0 NOT NULL,
ci_pipeline_deployments integer DEFAULT 500 NOT NULL,
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL,
daily_invites integer DEFAULT 0 NOT NULL,
rubygems_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL
);
@ -19096,8 +19081,6 @@ ALTER TABLE ONLY metrics_users_starred_dashboards ALTER COLUMN id SET DEFAULT ne
ALTER TABLE ONLY milestones ALTER COLUMN id SET DEFAULT nextval('milestones_id_seq'::regclass);
ALTER TABLE ONLY namespace_onboarding_actions ALTER COLUMN id SET DEFAULT nextval('namespace_onboarding_actions_id_seq'::regclass);
ALTER TABLE ONLY namespace_statistics ALTER COLUMN id SET DEFAULT nextval('namespace_statistics_id_seq'::regclass);
ALTER TABLE ONLY namespaces ALTER COLUMN id SET DEFAULT nextval('namespaces_id_seq'::regclass);
@ -20442,9 +20425,6 @@ ALTER TABLE ONLY namespace_aggregation_schedules
ALTER TABLE ONLY namespace_limits
ADD CONSTRAINT namespace_limits_pkey PRIMARY KEY (namespace_id);
ALTER TABLE ONLY namespace_onboarding_actions
ADD CONSTRAINT namespace_onboarding_actions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY namespace_package_settings
ADD CONSTRAINT namespace_package_settings_pkey PRIMARY KEY (namespace_id);
@ -22619,8 +22599,6 @@ CREATE INDEX index_mr_metrics_on_target_project_id_merged_at_time_to_merge ON me
CREATE UNIQUE INDEX index_namespace_aggregation_schedules_on_namespace_id ON namespace_aggregation_schedules USING btree (namespace_id);
CREATE INDEX index_namespace_onboarding_actions_on_namespace_id ON namespace_onboarding_actions USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON namespace_root_storage_statistics USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_statistics USING btree (namespace_id);
@ -25159,9 +25137,6 @@ ALTER TABLE ONLY merge_request_assignees
ALTER TABLE ONLY packages_dependency_links
ADD CONSTRAINT fk_rails_4437bf4070 FOREIGN KEY (dependency_id) REFERENCES packages_dependencies(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespace_onboarding_actions
ADD CONSTRAINT fk_rails_4504f6875a FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_auto_devops
ADD CONSTRAINT fk_rails_45436b12b2 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;

View File

@ -96,6 +96,13 @@ Read more on the [Rack Attack initializer](../security/rack_attack.md) method of
- **Default rate limit** - Disabled
### Member Invitations
Limit the maximum daily member invitations allowed per group hierarchy.
- GitLab.com: Free members may invite 20 members per day.
- Self-managed: Invites are not limited.
## Gitaly concurrency limit
Clone traffic can put a large strain on your Gitaly service. To prevent such workloads from overwhelming your Gitaly server, you can set concurrency limits in Gitalys configuration file.

View File

@ -27053,6 +27053,11 @@ type User {
"""
avatarUrl: String
"""
Indicates if the user is a bot.
"""
bot: Boolean!
"""
User email. Deprecated in 13.7: Use public_email.
"""

View File

@ -77878,6 +77878,24 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "bot",
"description": "Indicates if the user is a bot.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "email",
"description": "User email. Deprecated in 13.7: Use public_email.",

View File

@ -4031,6 +4031,7 @@ Autogenerated return type of UpdateSnippet.
| `assignedMergeRequests` | MergeRequestConnection | Merge Requests assigned to the user. |
| `authoredMergeRequests` | MergeRequestConnection | Merge Requests authored by the user. |
| `avatarUrl` | String | URL of the user's avatar. |
| `bot` | Boolean! | Indicates if the user is a bot. |
| `email` **{warning-solid}** | String | **Deprecated:** Use public_email. Deprecated in 13.7. |
| `groupCount` | Int | Group count for the user. Available only when feature flag `user_group_counts` is enabled. |
| `groupMemberships` | GroupMemberConnection | Group memberships of the user. |

View File

@ -60,8 +60,8 @@ GET /issues?state=opened
| `due_date` | string | no | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. _(Introduced in [GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/233420))_ |
| `iids[]` | integer array | no | Return only the issues having the given `iid` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `iteration_id` **(STARTER)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.6)_ |
| `iteration_title` **(STARTER)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.6)_ |
| `iteration_id` **(PREMIUM)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
| `iteration_title` **(PREMIUM)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |

View File

@ -6,6 +6,38 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# HTML style guide
## Semantic elements
[Semantic elements](https://developer.mozilla.org/en-US/docs/Glossary/Semantics) are HTML tags that
give semantic (rather than presentational) meaning to the data they contain. For example:
- [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
- [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
- [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
Prefer using semantic tags, but only if the intention is truly accurate with the semantic meaning
of the tag itself. View the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)
for a description on what each tag semantically means.
```html
<!-- bad - could use semantic tags instead of div's. -->
<div class="...">
<p>
<!-- bad - this isn't what "strong" is meant for. -->
Simply visit your <strong>Settings</strong> to say hello to the world.
</p>
<div class="...">...</div>
</div>
<!-- good - prefer semantic classes used accurately -->
<section class="...">
<p>
Simply visit your <span class="gl-font-weight-bold">Settings</span> to say hello to the world.
</p>
<footer class="...">...</footer>
</section>
```
## Buttons
### Button type

View File

@ -495,18 +495,17 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
aggregation.
- `aggregation`: may be set to a `:daily` or `:weekly` key. Defines how counting data is stored in Redis.
Aggregation on a `daily` basis does not pull more fine grained data.
- `feature_flag`: optional. For details, see our [GitLab internal Feature flags](feature_flags/) documentation. The feature flags are owned by the group adding the event tracking.
- `feature_flag`: optional `default_enabled: :yaml`. If no feature flag is set then the tracking is enabled. For details, see our [GitLab internal Feature flags](feature_flags/) documentation. The feature flags are owned by the group adding the event tracking.
Use one of the following methods to track events:
1. Track event in controller using `RedisTracking` module with `track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false)`.
1. Track event in controller using `RedisTracking` module with `track_redis_hll_event(*controller_actions, name:, if: nil)`.
Arguments:
- `controller_actions`: controller actions we want to track.
- `name`: event name.
- `feature`: feature name, all metrics we track should be under feature flag.
- `feature_default_enabled`: feature flag is disabled by default, set to `true` for it to be enabled by default.
- `if`: optional custom conditions, using the same format as with Rails callbacks.
Example usage:
@ -516,7 +515,7 @@ Use one of the following methods to track events:
include RedisTracking
skip_before_action :authenticate_user!, only: :show
track_redis_hll_event :index, :show, name: 'g_compliance_example_feature_visitors', feature: :compliance_example_feature, feature_default_enabled: true
track_redis_hll_event :index, :show, name: 'g_compliance_example_feature_visitors'
def index
render html: 'index'

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -7,12 +7,12 @@ type: reference, howto
# Threads **(FREE)**
The ability to contribute conversationally is offered throughout GitLab.
You can use words to communicate with other users all over GitLab.
You can leave a comment in the following places:
For example, you can leave a comment in the following places:
- Issues
- Epics **(ULTIMATE)**
- Epics
- Merge requests
- Snippets
- Commits
@ -281,6 +281,23 @@ edit existing comments. Non-team members are restricted from adding or editing c
Additionally, locked issues and merge requests can not be reopened.
## Confidential Comments
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207473) in GitLab 13.9.
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to enable it. **(FREE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
When creating a comment, you can decide to make it visible only to the project members (users with Reporter and higher permissions).
To create a confidential comment, select the **Make this comment confidential** checkbox before you submit it.
![Confidential comments](img/confidential_comments_v13_9.png)
## Merge Request Reviews
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4213) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.4.
@ -418,25 +435,6 @@ the thread will be automatically resolved, and GitLab will create a new commit
and push the suggested change directly into the codebase in the merge request's
branch. [Developer permission](../permissions.md) is required to do so.
### Enable or disable Custom commit messages for suggestions **(FREE SELF)**
Custom commit messages for suggestions is under development but ready for production use. It is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to disable it.
To disable custom commit messages for suggestions:
```ruby
Feature.disable(:suggestions_custom_commit)
```
To enable custom commit messages for suggestions:
```ruby
Feature.enable(:suggestions_custom_commit)
```
### Multi-line Suggestions
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53310) in GitLab 11.10.
@ -532,27 +530,6 @@ to your branch to address your reviewers' requests.
![A code change suggestion displayed, with the button to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
#### Enable or disable Batch Suggestions **(FREE SELF)**
Batch Suggestions is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to disable it for your instance.
To enable it:
```ruby
# Instance-wide
Feature.enable(:batch_suggestions)
```
To disable it:
```ruby
# Instance-wide
Feature.disable(:batch_suggestions)
```
## Start a thread by replying to a standard comment
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9
@ -585,3 +562,62 @@ In the comment, click the **More Actions** menu and click **Assign to commenting
Click the button again to unassign the commenter.
![Assign to commenting user](img/quickly_assign_commenter_v13_1.png)
## Enable or disable Confidential Comments **(FREE SELF)**
Confidential Comments is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:confidential_notes)
```
To disable it:
```ruby
Feature.disable(:confidential_notes)
```
## Enable or disable Custom commit messages for suggestions **(FREE SELF)**
Custom commit messages for suggestions is under development but ready for production use. It is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to disable it.
To disable custom commit messages for suggestions:
```ruby
Feature.disable(:suggestions_custom_commit)
```
To enable custom commit messages for suggestions:
```ruby
Feature.enable(:suggestions_custom_commit)
```
## Enable or disable Batch Suggestions **(FREE SELF)**
Batch Suggestions is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to disable it for your instance.
To enable it:
```ruby
# Instance-wide
Feature.enable(:batch_suggestions)
```
To disable it:
```ruby
# Instance-wide
Feature.disable(:batch_suggestions)
```

View File

@ -183,6 +183,7 @@ The following table depicts the various user permission levels in a project.
| Delete pipelines | | | | | ✓ |
| Delete merge request | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
| Administer project compliance frameworks | | | | | ✓ |
| Force push to protected branches (*4*) | | | | | |
| Remove protected branches (*4*) | | | | | |
@ -293,6 +294,7 @@ group.
| View Billing **(FREE SAAS)** | | | | | ✓ (4) |
| View Usage Quotas **(FREE SAAS)** | | | | | ✓ (4) |
| Filter members by 2FA status | | | | | ✓ |
| Administer project compliance frameworks | | | | | ✓ |
1. Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)

View File

@ -198,6 +198,8 @@ service account can be found at Google's documentation for
Prometheus OAuth Client secured with Google IAP.
1. (Optional) In **Google IAP Service Account JSON**, provide the contents of the
Service Account credentials file that is authorized to access the Prometheus resource.
The JSON key `token_credential_uri` is discarded to prevent
[Server-side Request Forgery (SSRF)](https://www.hackerone.com/blog-How-To-Server-Side-Request-Forgery-SSRF).
1. Click **Save changes**.
![Configure Prometheus Service](img/prometheus_manual_configuration_v13_2.png)

View File

@ -46,17 +46,17 @@ Compliance framework labels do not affect your project settings.
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-custom-compliance-frameworks). **(PREMIUM ONLY)**
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-custom-compliance-frameworks). **(PREMIUM)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
GitLab 13.8 introduces custom compliance frameworks at the group-level. A group owner can create a compliance framework label
GitLab 13.9 introduces custom compliance frameworks at the group-level. A group owner can create a compliance framework label
and assign it to any number of projects within that group or sub-groups. When this feature is enabled, projects can only
be assigned compliance framework labels that already exist within that group.
If existing [Compliance frameworks](#compliance-framework) are not sufficient, you can now create
your own.
If existing [Compliance frameworks](#compliance-framework) are not sufficient, project and group owners
can now create their own.
New compliance framework labels can be created and updated using GraphQL.
@ -320,7 +320,7 @@ Add the URL of a Jaeger server to allow your users to [easily access the Jaeger
[Add Storage credentials](../../../operations/incident_management/status_page.md#sync-incidents-to-the-status-page)
to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#create-a-status-page-project).
### Enable or disable custom compliance frameworks **(PREMIUM ONLY)**
### Enable or disable custom compliance frameworks **(PREMIUM)**
Enabling or disabling custom compliance frameworks is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.

View File

@ -11,6 +11,8 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end
post '/lint' do
unauthorized! unless Gitlab::CurrentSettings.signup_enabled? && current_user
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
status 200
@ -55,7 +57,7 @@ module API
optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
end
post ':id/ci/lint' do
authorize! :download_code, user_project
authorize! :create_pipeline, user_project
result = Gitlab::Ci::Lint
.new(project: user_project, current_user: current_user)

View File

@ -26,6 +26,8 @@ module API
# GET /projects/:id/merge_requests/:merge_request_iid/approvals
desc 'List approvals for merge request'
get 'approvals' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present_approval(merge_request)

View File

@ -23,6 +23,8 @@ module API
use :pagination
end
get ":id/merge_requests/:merge_request_iid/versions" do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.merge_request_diffs.order_id_desc), with: Entities::MergeRequestDiff
@ -39,6 +41,8 @@ module API
end
get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull

View File

@ -248,6 +248,8 @@ module API
success Entities::MergeRequest
end
get ':id/merge_requests/:merge_request_iid' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request,
@ -264,7 +266,10 @@ module API
success Entities::UserBasic
end
get ':id/merge_requests/:merge_request_iid/participants' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
participants = ::Kaminari.paginate_array(merge_request.participants)
present paginate(participants), with: Entities::UserBasic
@ -274,6 +279,8 @@ module API
success Entities::Commit
end
get ':id/merge_requests/:merge_request_iid/commits' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits =
@ -355,6 +362,8 @@ module API
success Entities::MergeRequestChanges
end
get ':id/merge_requests/:merge_request_iid/changes' do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request,
@ -370,6 +379,8 @@ module API
get ':id/merge_requests/:merge_request_iid/pipelines' do
pipelines = merge_request_pipelines_with_access
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
present paginate(pipelines), with: Entities::Ci::PipelineBasic
end

View File

@ -28,6 +28,11 @@ module API
end
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
unless can?(current_user, :read_merge_request, issuable.project)
not_found!(type.split("_").map(&:capitalize).join(" "))
end
todo = TodoService.new.mark_todo(issuable, current_user).first
if todo

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Graphql
module GetMembersQuery
extend self
def to_s
<<-'GRAPHQL'
query($full_path: ID!, $cursor: String) {
group(fullPath: $full_path) {
group_members: groupMembers(relations: DIRECT, first: 100, after: $cursor) {
page_info: pageInfo {
end_cursor: endCursor
has_next_page: hasNextPage
}
nodes {
created_at: createdAt
updated_at: updatedAt
expires_at: expiresAt
access_level: accessLevel {
integer_value: integerValue
}
user {
public_email: publicEmail
}
}
}
}
}
GRAPHQL
end
def variables(entity)
{
full_path: entity.source_full_path,
cursor: entity.next_page_for(:group_members)
}
end
def base_path
%w[data group group_members]
end
def data_path
base_path << 'nodes'
end
def page_info_path
base_path << 'page_info'
end
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Loaders
class MembersLoader
def initialize(*); end
def load(context, data)
return unless data
context.group.members.create!(data)
end
end
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Pipelines
class MembersPipeline
include Pipeline
extractor BulkImports::Common::Extractors::GraphqlExtractor,
query: BulkImports::Groups::Graphql::GetMembersQuery
transformer Common::Transformers::ProhibitedAttributesTransformer
transformer BulkImports::Groups::Transformers::MemberAttributesTransformer
loader BulkImports::Groups::Loaders::MembersLoader
def after_run(context, extracted_data)
context.entity.update_tracker_for(
relation: :group_members,
has_next_page: extracted_data.has_next_page?,
next_page: extracted_data.next_page
)
if extracted_data.has_next_page?
run(context)
end
end
end
end
end
end

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Transformers
class MemberAttributesTransformer
def initialize(*); end
def transform(context, data)
data
.then { |data| add_user(data) }
.then { |data| add_access_level(data) }
.then { |data| add_author(data, context) }
end
private
def add_user(data)
user = find_user(data&.dig('user', 'public_email'))
return unless user
data
.except('user')
.merge('user_id' => user.id)
end
def find_user(email)
return unless email
User.find_by_any_email(email, confirmed: true)
end
def add_access_level(data)
access_level = data&.dig('access_level', 'integer_value')
return unless valid_access_level?(access_level)
data.merge('access_level' => access_level)
end
def valid_access_level?(access_level)
Gitlab::Access
.options_with_owner
.value?(access_level)
end
def add_author(data, context)
return unless data
data.merge('created_by_id' => context.current_user.id)
end
end
end
end
end

View File

@ -23,6 +23,7 @@ module BulkImports
[
BulkImports::Groups::Pipelines::GroupPipeline,
BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline,
BulkImports::Groups::Pipelines::MembersPipeline,
BulkImports::Groups::Pipelines::LabelsPipeline
]
end

View File

@ -61,8 +61,7 @@ module Gitlab
headers: {
'Content-Type': 'application/json'
}.merge(headers),
body: body,
verify: false # FTC API Docs specifically mentions to turn off SSL Verification while making requests.
body: body
)
end
end

View File

@ -10,6 +10,10 @@ module Gitlab
include Chain::Helpers
def perform!
if project.pending_delete?
return error('Project is deleted!')
end
unless project.builds_enabled?
return error('Pipelines are disabled!')
end

View File

@ -13,7 +13,6 @@ module Gitlab
gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = Gitlab::Routing.url_helpers.help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if Gitlab.config.sentry.enabled

View File

@ -40,21 +40,17 @@ module Gitlab
# - An Array of the unique ::Commit objects in the first value
def summarize
summary = contents
.map { |content| build_entry(content) }
.tap { |summary| fill_last_commits!(summary) }
[summary, commits]
end
def fetch_logs
cache_key = ['projects', project.id, 'logs', commit.id, path, offset]
Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do
logs, _ = summarize
logs, _ = summarize
new_offset = next_offset if more?
new_offset = next_offset if more?
[logs.as_json, new_offset]
end
[logs.as_json, new_offset]
end
# Does the tree contain more entries after the given offset + limit?
@ -71,7 +67,7 @@ module Gitlab
private
def contents
all_contents[offset, limit]
all_contents[offset, limit] || []
end
def commits
@ -82,22 +78,17 @@ module Gitlab
project.repository
end
# Ensure the path is in "path/" format
def ensured_path
File.join(*[path, ""]) if path
end
def entry_path(entry)
File.join(*[path, entry[:file_name]].compact).force_encoding(Encoding::ASCII_8BIT)
end
def build_entry(entry)
{ file_name: entry.name, type: entry.type }
end
def fill_last_commits!(entries)
# Ensure the path is in "path/" format
ensured_path =
if path
File.join(*[path, ""])
end
commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit, literal_pathspec: true)
commits_hsh = fetch_last_cached_commits_list
prerender_commit_full_titles!(commits_hsh.values)
entries.each do |entry|
@ -112,6 +103,18 @@ module Gitlab
end
end
def fetch_last_cached_commits_list
cache_key = ['projects', project.id, 'last_commits_list', commit.id, ensured_path, offset, limit]
commits = Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do
repository
.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit, literal_pathspec: true)
.transform_values!(&:to_hash)
end
commits.transform_values! { |value| Commit.from_hash(value, project) }
end
def cache_commit(commit)
return unless commit.present?
@ -123,12 +126,18 @@ module Gitlab
end
def all_contents
strong_memoize(:all_contents) do
strong_memoize(:all_contents) { cached_contents }
end
def cached_contents
cache_key = ['projects', project.id, 'content', commit.id, path]
Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do
[
*tree.trees,
*tree.blobs,
*tree.submodules
]
].map { |entry| { file_name: entry.name, type: entry.type } }
end
end

View File

@ -148,7 +148,7 @@ module Gitlab
end
def load_yaml_from_path(path)
YAML.safe_load(File.read(path))&.map(&:with_indifferent_access)
YAML.safe_load(File.read(path), aliases: true)&.map(&:with_indifferent_access)
end
end
end

Some files were not shown because too many files have changed in this diff Show More