Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9f5ac379c7
commit
5231344d99
|
@ -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'
|
||||
|
|
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -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)
|
||||
|
|
|
@ -1 +1 @@
|
|||
88ef3e7f64498ae3574f29b0705c29cf3b4e9311
|
||||
d0a79053ba4fef55b59543b99327fc89aed64876
|
||||
|
|
|
@ -1 +1 @@
|
|||
13.16.0
|
||||
13.16.1
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { initStaticSecurityConfiguration } from '~/security_configuration';
|
||||
|
||||
initStaticSecurityConfiguration(document.querySelector('#js-security-configuration-static'));
|
|
@ -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
|
||||
|
|
|
@ -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') },
|
||||
];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
},
|
||||
];
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
mutation configureSast($input: ConfigureSastInput!) {
|
||||
configureSast(input: $input) {
|
||||
successPath
|
||||
errors
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,6 +5,7 @@ fragment ReadyToMerge on Project {
|
|||
mergeRequest(iid: $iid) {
|
||||
autoMergeEnabled
|
||||
shouldRemoveSourceBranch
|
||||
forceRemoveSourceBranch
|
||||
defaultMergeCommitMessage
|
||||
defaultMergeCommitMessageWithDescription
|
||||
defaultSquashCommitMessage
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -16,6 +16,7 @@ module Ci
|
|||
include ShaAttribute
|
||||
include FromUnion
|
||||
include UpdatedAtFilterable
|
||||
include EachBatch
|
||||
|
||||
MAX_OPEN_MERGE_REQUESTS_REFS = 4
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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') }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 } ×
|
||||
.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 ⌘ 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 ⌘ 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 ⌘ p
|
||||
- else
|
||||
%kbd ctrl p
|
||||
%td= _('Go to file')
|
||||
%tr
|
||||
%td.shortcut
|
||||
- if browser.platform.mac?
|
||||
%kbd ⌘ enter
|
||||
- else
|
||||
%kbd ctrl enter
|
||||
%td= _('Commit (when editing commit message)')
|
|
@ -1,3 +0,0 @@
|
|||
:plain
|
||||
$("body").append("#{escape_javascript(render('shortcuts'))}");
|
||||
$("#modal-shortcuts").modal();
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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} }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support setting confidential note attribute in UI
|
||||
merge_request: 52949
|
||||
author: Lee Tickett @leetickett
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add bot to User GraphQL Type
|
||||
merge_request: 52933
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add sort to container registry list page
|
||||
merge_request: 53820
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove namespace_onboarding_actions table
|
||||
merge_request: 53488
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix charts sometimes being hidden on milestone page
|
||||
merge_request: 52552
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'BulkImports: Migrate Group Membership'
|
||||
merge_request: 53083
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: 'Designs: return error if uploading filenames with special chars'
|
||||
merge_request: 44136
|
||||
author: Sushil Khanchi @khanchi97
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show helper link on a new line in GPG status popover
|
||||
merge_request: 52894
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Reset CI minutes only for namespaces that used minutes.
|
||||
merge_request: 53740
|
||||
author:
|
||||
type: changed
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
1200747265d5095a86250020786d6f1e9e50bc75328a71de497046807afa89d7
|
|
@ -0,0 +1 @@
|
|||
febefead6f966960f6493d29add5f35fc4a1080b5118c5526502fa5fe1d29023
|
|
@ -0,0 +1 @@
|
|||
cdf55e9f2b1b9c375920198a438d29fe3c9ab7147f3c670b0d66b11d499573d9
|
|
@ -0,0 +1 @@
|
|||
d9cfb7515805e642c562b8be58b6cd482c24e62e76245db35a7d91b25c327d8d
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 Gitaly’s configuration 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.
|
||||
"""
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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**.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -23,6 +23,7 @@ module BulkImports
|
|||
[
|
||||
BulkImports::Groups::Pipelines::GroupPipeline,
|
||||
BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline,
|
||||
BulkImports::Groups::Pipelines::MembersPipeline,
|
||||
BulkImports::Groups::Pipelines::LabelsPipeline
|
||||
]
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue