Merge branch 'master' into refactor-clusters
This commit is contained in:
commit
6ebe6792de
|
@ -104,8 +104,7 @@ the remaining issues on the GitHub issue tracker.
|
||||||
|
|
||||||
## I want to contribute!
|
## I want to contribute!
|
||||||
|
|
||||||
If you want to contribute to GitLab, but are not sure where to start,
|
If you want to contribute to GitLab, [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners.
|
||||||
look for [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight].
|
|
||||||
These issues will be of reasonable size and challenge, for anyone to start
|
These issues will be of reasonable size and challenge, for anyone to start
|
||||||
contributing to GitLab.
|
contributing to GitLab.
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import CILintEditor from './ci_lint_editor';
|
||||||
import groupsSelect from './groups_select';
|
import groupsSelect from './groups_select';
|
||||||
/* global Search */
|
/* global Search */
|
||||||
/* global Admin */
|
/* global Admin */
|
||||||
/* global NamespaceSelects */
|
import NamespaceSelect from './namespace_select';
|
||||||
/* global NewCommitForm */
|
/* global NewCommitForm */
|
||||||
/* global NewBranchForm */
|
/* global NewBranchForm */
|
||||||
/* global Project */
|
/* global Project */
|
||||||
|
@ -575,7 +575,8 @@ import Diff from './diff';
|
||||||
new UsersSelect();
|
new UsersSelect();
|
||||||
break;
|
break;
|
||||||
case 'projects':
|
case 'projects':
|
||||||
new NamespaceSelects();
|
document.querySelectorAll('.js-namespace-select')
|
||||||
|
.forEach(dropdown => new NamespaceSelect({ dropdown }));
|
||||||
break;
|
break;
|
||||||
case 'labels':
|
case 'labels':
|
||||||
switch (path[2]) {
|
switch (path[2]) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ const utils = {
|
||||||
},
|
},
|
||||||
|
|
||||||
isDropDownParts(target) {
|
isDropDownParts(target) {
|
||||||
if (!target || target.tagName === 'HTML') return false;
|
if (!target || !target.hasAttribute || target.tagName === 'HTML') return false;
|
||||||
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
|
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -119,11 +119,9 @@ export default function dropzoneInput(form) {
|
||||||
// removeAllFiles(true) stops uploading files (if any)
|
// removeAllFiles(true) stops uploading files (if any)
|
||||||
// and remove them from dropzone files queue.
|
// and remove them from dropzone files queue.
|
||||||
$cancelButton.on('click', (e) => {
|
$cancelButton.on('click', (e) => {
|
||||||
const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone');
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
Dropzone.forElement(target).removeAllFiles(true);
|
Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// If 'error' event is fired, we store a failed files,
|
// If 'error' event is fired, we store a failed files,
|
||||||
|
|
|
@ -421,7 +421,11 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{ 'js-child-row environment-child-row': model.isChildren, 'folder-row': model.isFolder, 'gl-responsive-table-row': !model.isFolder }"
|
class="gl-responsive-table-row"
|
||||||
|
:class="{
|
||||||
|
'js-child-row environment-child-row': model.isChildren,
|
||||||
|
'folder-row': model.isFolder,
|
||||||
|
}"
|
||||||
role="row">
|
role="row">
|
||||||
<div class="table-section section-10" role="gridcell">
|
<div class="table-section section-10" role="gridcell">
|
||||||
<div
|
<div
|
||||||
|
@ -495,15 +499,16 @@ export default {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-section section-25" role="gridcell">
|
<div
|
||||||
|
v-if="!model.isFolder"
|
||||||
|
class="table-section section-25" role="gridcell">
|
||||||
<div
|
<div
|
||||||
v-if="!model.isFolder"
|
|
||||||
role="rowheader"
|
role="rowheader"
|
||||||
class="table-mobile-header">
|
class="table-mobile-header">
|
||||||
Commit
|
Commit
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!model.isFolder && hasLastDeploymentKey"
|
v-if="hasLastDeploymentKey"
|
||||||
class="js-commit-component table-mobile-content">
|
class="js-commit-component table-mobile-content">
|
||||||
<commit-component
|
<commit-component
|
||||||
:tag="commitTag"
|
:tag="commitTag"
|
||||||
|
@ -514,21 +519,22 @@ export default {
|
||||||
:author="commitAuthor"/>
|
:author="commitAuthor"/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!model.isFolder && !hasLastDeploymentKey"
|
v-if="!hasLastDeploymentKey"
|
||||||
class="commit-title table-mobile-content">
|
class="commit-title table-mobile-content">
|
||||||
No deployments yet
|
No deployments yet
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-section section-10" role="gridcell">
|
<div
|
||||||
|
v-if="!model.isFolder"
|
||||||
|
class="table-section section-10" role="gridcell">
|
||||||
<div
|
<div
|
||||||
v-if="!model.isFolder"
|
|
||||||
role="rowheader"
|
role="rowheader"
|
||||||
class="table-mobile-header">
|
class="table-mobile-header">
|
||||||
Updated
|
Updated
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-if="!model.isFolder && canShowDate"
|
v-if="canShowDate"
|
||||||
class="environment-created-date-timeago table-mobile-content">
|
class="environment-created-date-timeago table-mobile-content">
|
||||||
{{createdDate}}
|
{{createdDate}}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import _ from 'underscore';
|
||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
|
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
|
||||||
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
|
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
|
||||||
|
import { n__ } from '../locale';
|
||||||
|
|
||||||
export default (function() {
|
export default (function() {
|
||||||
function ContributorsStatGraph() {}
|
function ContributorsStatGraph() {}
|
||||||
|
@ -44,7 +45,7 @@ export default (function() {
|
||||||
commits = $('<span/>', {
|
commits = $('<span/>', {
|
||||||
"class": 'graph-author-commits-count'
|
"class": 'graph-author-commits-count'
|
||||||
});
|
});
|
||||||
commits.text(author.commits + " commits");
|
commits.text(n__('%d commit', '%d commits', author.commits));
|
||||||
return $('<span/>').append(commits);
|
return $('<span/>').append(commits);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,85 +1,57 @@
|
||||||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */
|
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
|
||||||
import Api from './api';
|
import Api from './api';
|
||||||
|
import './lib/utils/url_utility';
|
||||||
|
|
||||||
(function() {
|
export default class NamespaceSelect {
|
||||||
window.NamespaceSelect = (function() {
|
constructor(opts) {
|
||||||
function NamespaceSelect(opts) {
|
const isFilter = opts.dropdown.dataset.isFilter === 'true';
|
||||||
this.onSelectItem = this.onSelectItem.bind(this);
|
const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id';
|
||||||
var fieldName, showAny;
|
|
||||||
this.dropdown = opts.dropdown;
|
$(opts.dropdown).glDropdown({
|
||||||
showAny = true;
|
filterable: true,
|
||||||
fieldName = 'namespace_id';
|
selectable: true,
|
||||||
if (this.dropdown.attr('data-field-name')) {
|
filterRemote: true,
|
||||||
fieldName = this.dropdown.data('fieldName');
|
search: {
|
||||||
}
|
fields: ['path']
|
||||||
if (this.dropdown.attr('data-show-any')) {
|
},
|
||||||
showAny = this.dropdown.data('showAny');
|
fieldName: fieldName,
|
||||||
}
|
toggleLabel: function(selected) {
|
||||||
this.dropdown.glDropdown({
|
if (selected.id == null) {
|
||||||
filterable: true,
|
return selected.text;
|
||||||
selectable: true,
|
} else {
|
||||||
filterRemote: true,
|
return selected.kind + ": " + selected.full_path;
|
||||||
search: {
|
}
|
||||||
fields: ['path']
|
},
|
||||||
},
|
data: function(term, dataCallback) {
|
||||||
fieldName: fieldName,
|
return Api.namespaces(term, function(namespaces) {
|
||||||
toggleLabel: function(selected) {
|
if (isFilter) {
|
||||||
if (selected.id == null) {
|
const anyNamespace = {
|
||||||
return selected.text;
|
text: 'Any namespace',
|
||||||
} else {
|
id: null
|
||||||
return selected.kind + ": " + selected.full_path;
|
};
|
||||||
|
namespaces.unshift(anyNamespace);
|
||||||
|
namespaces.splice(1, 0, 'divider');
|
||||||
}
|
}
|
||||||
},
|
return dataCallback(namespaces);
|
||||||
data: function(term, dataCallback) {
|
|
||||||
return Api.namespaces(term, function(namespaces) {
|
|
||||||
var anyNamespace;
|
|
||||||
if (showAny) {
|
|
||||||
anyNamespace = {
|
|
||||||
text: 'Any namespace',
|
|
||||||
id: null
|
|
||||||
};
|
|
||||||
namespaces.unshift(anyNamespace);
|
|
||||||
namespaces.splice(1, 0, 'divider');
|
|
||||||
}
|
|
||||||
return dataCallback(namespaces);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
text: function(namespace) {
|
|
||||||
if (namespace.id == null) {
|
|
||||||
return namespace.text;
|
|
||||||
} else {
|
|
||||||
return namespace.kind + ": " + namespace.full_path;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderRow: this.renderRow,
|
|
||||||
clicked: this.onSelectItem
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
NamespaceSelect.prototype.onSelectItem = function(options) {
|
|
||||||
const { e } = options;
|
|
||||||
return e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
return NamespaceSelect;
|
|
||||||
})();
|
|
||||||
|
|
||||||
window.NamespaceSelects = (function() {
|
|
||||||
function NamespaceSelects(opts) {
|
|
||||||
var ref;
|
|
||||||
if (opts == null) {
|
|
||||||
opts = {};
|
|
||||||
}
|
|
||||||
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select');
|
|
||||||
this.$dropdowns.each(function(i, dropdown) {
|
|
||||||
var $dropdown;
|
|
||||||
$dropdown = $(dropdown);
|
|
||||||
return new window.NamespaceSelect({
|
|
||||||
dropdown: $dropdown
|
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
}
|
text: function(namespace) {
|
||||||
|
if (namespace.id == null) {
|
||||||
return NamespaceSelects;
|
return namespace.text;
|
||||||
})();
|
} else {
|
||||||
}).call(window);
|
return namespace.kind + ": " + namespace.full_path;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderRow: this.renderRow,
|
||||||
|
clicked(options) {
|
||||||
|
if (!isFilter) {
|
||||||
|
const { e } = options;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url(namespace) {
|
||||||
|
return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -122,7 +122,9 @@
|
||||||
// we need to do this to prevent noteForm inconsistent content warning
|
// we need to do this to prevent noteForm inconsistent content warning
|
||||||
// this is something we intentionally do so we need to recover the content
|
// this is something we intentionally do so we need to recover the content
|
||||||
this.note.note = noteText;
|
this.note.note = noteText;
|
||||||
this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
|
if (this.$refs.noteBody) {
|
||||||
|
this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import getActionIcon from '../../../vue_shared/ci_action_icons';
|
|
||||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||||
|
import icon from '../../../vue_shared/components/icon.vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders either a cancel, retry or play icon pointing to the given path.
|
* Renders either a cancel, retry or play icon pointing to the given path.
|
||||||
|
@ -29,17 +29,18 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
|
||||||
directives: {
|
directives: {
|
||||||
tooltip,
|
tooltip,
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
actionIconSvg() {
|
|
||||||
return getActionIcon(this.actionIcon);
|
|
||||||
},
|
|
||||||
|
|
||||||
cssClass() {
|
cssClass() {
|
||||||
return `js-${gl.text.dasherize(this.actionIcon)}`;
|
const actionIconDash = gl.text.dasherize(this.actionIcon);
|
||||||
|
return `${actionIconDash} js-icon-${actionIconDash}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -50,14 +51,9 @@
|
||||||
:data-method="actionMethod"
|
:data-method="actionMethod"
|
||||||
:title="tooltipText"
|
:title="tooltipText"
|
||||||
:href="link"
|
:href="link"
|
||||||
class="ci-action-icon-container"
|
class="ci-action-icon-container ci-action-icon-wrapper"
|
||||||
|
:class="cssClass"
|
||||||
data-container="body">
|
data-container="body">
|
||||||
|
<icon :name="actionIcon"/>
|
||||||
<i
|
|
||||||
class="ci-action-icon-wrapper"
|
|
||||||
:class="cssClass"
|
|
||||||
v-html="actionIconSvg"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import getActionIcon from '../../../vue_shared/ci_action_icons';
|
import icon from '../../../vue_shared/components/icon.vue';
|
||||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,14 +29,12 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
directives: {
|
components: {
|
||||||
tooltip,
|
icon,
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
directives: {
|
||||||
actionIconSvg() {
|
tooltip,
|
||||||
return getActionIcon(this.actionIcon);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -49,7 +47,7 @@
|
||||||
rel="nofollow"
|
rel="nofollow"
|
||||||
class="ci-action-icon-wrapper js-ci-status-icon"
|
class="ci-action-icon-wrapper js-ci-status-icon"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
v-html="actionIconSvg"
|
|
||||||
aria-label="Job's action">
|
aria-label="Job's action">
|
||||||
|
<icon :name="actionIcon"/>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
* "group": "success",
|
* "group": "success",
|
||||||
* "details_path": "/root/ci-mock/builds/4256",
|
* "details_path": "/root/ci-mock/builds/4256",
|
||||||
* "action": {
|
* "action": {
|
||||||
* "icon": "icon_action_retry",
|
* "icon": "retry",
|
||||||
* "title": "Retry",
|
* "title": "Retry",
|
||||||
* "path": "/root/ci-mock/builds/4256/retry",
|
* "path": "/root/ci-mock/builds/4256/retry",
|
||||||
* "method": "post"
|
* "method": "post"
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
* "group": "success",
|
* "group": "success",
|
||||||
* "details_path": "/root/ci-mock/builds/4256",
|
* "details_path": "/root/ci-mock/builds/4256",
|
||||||
* "action": {
|
* "action": {
|
||||||
* "icon": "icon_action_retry",
|
* "icon": "retry",
|
||||||
* "title": "Retry",
|
* "title": "Retry",
|
||||||
* "path": "/root/ci-mock/builds/4256/retry",
|
* "path": "/root/ci-mock/builds/4256/retry",
|
||||||
* "method": "post"
|
* "method": "post"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Flash from '../../flash';
|
import Flash from '../../flash';
|
||||||
import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
|
import icon from '../../vue_shared/components/icon.vue';
|
||||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||||
import tooltip from '../../vue_shared/directives/tooltip';
|
import tooltip from '../../vue_shared/directives/tooltip';
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
loadingIcon,
|
loadingIcon,
|
||||||
|
icon,
|
||||||
},
|
},
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
|
@ -122,8 +123,8 @@ export default {
|
||||||
return `ci-status-icon-${this.stage.status.group}`;
|
return `ci-status-icon-${this.stage.status.group}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
svgIcon() {
|
borderlessIcon() {
|
||||||
return borderlessStatusIconEntityMap[this.stage.status.icon];
|
return `${this.stage.status.icon}_borderless`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -145,9 +146,10 @@ export default {
|
||||||
aria-expanded="false">
|
aria-expanded="false">
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-html="svgIcon"
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
:aria-label="stage.title">
|
:aria-label="stage.title">
|
||||||
|
<icon
|
||||||
|
:name="borderlessIcon"/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<i
|
<i
|
||||||
|
|
|
@ -98,7 +98,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
|
||||||
@toggle="toggleOpen"
|
@toggle="toggleOpen"
|
||||||
@submit="onSubmit">
|
@submit="onSubmit">
|
||||||
|
|
||||||
<template slot="body" scope="props">
|
<template slot="body" slot-scope="props">
|
||||||
<p v-html="props.text"></p>
|
<p v-html="props.text"></p>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
|
|
|
@ -27,6 +27,8 @@ export default {
|
||||||
'changeFileContent',
|
'changeFileContent',
|
||||||
]),
|
]),
|
||||||
initMonaco() {
|
initMonaco() {
|
||||||
|
if (this.shouldHideEditor) return;
|
||||||
|
|
||||||
if (this.monacoInstance) {
|
if (this.monacoInstance) {
|
||||||
this.monacoInstance.setModel(null);
|
this.monacoInstance.setModel(null);
|
||||||
}
|
}
|
||||||
|
@ -94,8 +96,12 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="ide"
|
id="ide"
|
||||||
v-if='!shouldHideEditor'
|
|
||||||
class="blob-viewer-container blob-editor-container"
|
class="blob-viewer-container blob-editor-container"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="shouldHideEditor"
|
||||||
|
v-html="activeFile.html"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,34 +1,26 @@
|
||||||
function expandSectionParent($section, $content) {
|
|
||||||
$section.addClass('expanded');
|
|
||||||
$content.off('animationend.expandSectionParent');
|
|
||||||
}
|
|
||||||
|
|
||||||
function expandSection($section) {
|
function expandSection($section) {
|
||||||
$section.find('.js-settings-toggle').text('Collapse');
|
$section.find('.js-settings-toggle').text('Collapse');
|
||||||
|
$section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
|
||||||
const $content = $section.find('.settings-content');
|
$section.addClass('expanded');
|
||||||
$content.addClass('expanded').off('scroll.expandSection').scrollTop(0);
|
if (!$section.hasClass('no-animate')) {
|
||||||
|
$section.addClass('animating')
|
||||||
if ($content.hasClass('no-animate')) {
|
.one('animationend.animateSection', () => $section.removeClass('animating'));
|
||||||
expandSectionParent($section, $content);
|
|
||||||
} else {
|
|
||||||
$content.on('animationend.expandSectionParent', () => expandSectionParent($section, $content));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeSection($section) {
|
function closeSection($section) {
|
||||||
$section.find('.js-settings-toggle').text('Expand');
|
$section.find('.js-settings-toggle').text('Expand');
|
||||||
|
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
|
||||||
const $content = $section.find('.settings-content');
|
|
||||||
$content.removeClass('expanded').on('scroll.expandSection', () => expandSection($section));
|
|
||||||
|
|
||||||
$section.removeClass('expanded');
|
$section.removeClass('expanded');
|
||||||
|
if (!$section.hasClass('no-animate')) {
|
||||||
|
$section.addClass('animating')
|
||||||
|
.one('animationend.animateSection', () => $section.removeClass('animating'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSection($section) {
|
function toggleSection($section) {
|
||||||
const $content = $section.find('.settings-content');
|
$section.removeClass('no-animate');
|
||||||
$content.removeClass('no-animate');
|
if ($section.hasClass('expanded')) {
|
||||||
if ($content.hasClass('expanded')) {
|
|
||||||
closeSection($section);
|
closeSection($section);
|
||||||
} else {
|
} else {
|
||||||
expandSection($section);
|
expandSection($section);
|
||||||
|
@ -39,10 +31,19 @@ export default function initSettingsPanels() {
|
||||||
$('.settings').each((i, elm) => {
|
$('.settings').each((i, elm) => {
|
||||||
const $section = $(elm);
|
const $section = $(elm);
|
||||||
$section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section));
|
$section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section));
|
||||||
$section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section));
|
|
||||||
|
if (!$section.hasClass('expanded')) {
|
||||||
|
$section.find('.settings-content').on('scroll.expandSection', () => {
|
||||||
|
$section.removeClass('no-animate');
|
||||||
|
expandSection($section);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (location.hash) {
|
if (location.hash) {
|
||||||
expandSection($(location.hash));
|
const $target = $(location.hash);
|
||||||
|
if ($target.length && $target.hasClass('.settings')) {
|
||||||
|
expandSection($target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import PipelineStage from '../../pipelines/components/stage.vue';
|
import PipelineStage from '../../pipelines/components/stage.vue';
|
||||||
import ciIcon from '../../vue_shared/components/ci_icon.vue';
|
import ciIcon from '../../vue_shared/components/ci_icon.vue';
|
||||||
import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
|
import icon from '../../vue_shared/components/icon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MRWidgetPipeline',
|
name: 'MRWidgetPipeline',
|
||||||
|
@ -10,6 +10,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
'pipeline-stage': PipelineStage,
|
'pipeline-stage': PipelineStage,
|
||||||
ciIcon,
|
ciIcon,
|
||||||
|
icon,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasPipeline() {
|
hasPipeline() {
|
||||||
|
@ -20,9 +21,6 @@ export default {
|
||||||
|
|
||||||
return hasCI && !ciStatus;
|
return hasCI && !ciStatus;
|
||||||
},
|
},
|
||||||
svg() {
|
|
||||||
return statusIconEntityMap.icon_status_failed;
|
|
||||||
},
|
|
||||||
stageText() {
|
stageText() {
|
||||||
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
|
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
|
||||||
},
|
},
|
||||||
|
@ -38,8 +36,10 @@ export default {
|
||||||
<template v-if="hasCIError">
|
<template v-if="hasCIError">
|
||||||
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
|
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
|
||||||
<span
|
<span
|
||||||
v-html="svg"
|
aria-hidden="true">
|
||||||
aria-hidden="true"></span>
|
<icon
|
||||||
|
name="status_failed"/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
Could not connect to the CI server. Please check your settings and try again
|
Could not connect to the CI server. Please check your settings and try again
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import cancelSVG from 'icons/_icon_action_cancel.svg';
|
|
||||||
import retrySVG from 'icons/_icon_action_retry.svg';
|
|
||||||
import playSVG from 'icons/_icon_action_play.svg';
|
|
||||||
import stopSVG from 'icons/_icon_action_stop.svg';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For the provided action returns the respective SVG
|
|
||||||
*
|
|
||||||
* @param {String} action
|
|
||||||
* @return {SVG|String}
|
|
||||||
*/
|
|
||||||
export default function getActionIcon(action) {
|
|
||||||
const icons = {
|
|
||||||
icon_action_cancel: cancelSVG,
|
|
||||||
icon_action_play: playSVG,
|
|
||||||
icon_action_retry: retrySVG,
|
|
||||||
icon_action_stop: stopSVG,
|
|
||||||
};
|
|
||||||
|
|
||||||
return icons[action] || '';
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
import BORDERLESS_CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
|
|
||||||
import BORDERLESS_CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
|
|
||||||
import BORDERLESS_FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
|
|
||||||
import BORDERLESS_MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
|
|
||||||
import BORDERLESS_PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
|
|
||||||
import BORDERLESS_RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
|
|
||||||
import BORDERLESS_SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
|
|
||||||
import BORDERLESS_SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
|
|
||||||
import BORDERLESS_WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
|
|
||||||
|
|
||||||
import CANCELED_SVG from 'icons/_icon_status_canceled.svg';
|
|
||||||
import CREATED_SVG from 'icons/_icon_status_created.svg';
|
|
||||||
import FAILED_SVG from 'icons/_icon_status_failed.svg';
|
|
||||||
import MANUAL_SVG from 'icons/_icon_status_manual.svg';
|
|
||||||
import PENDING_SVG from 'icons/_icon_status_pending.svg';
|
|
||||||
import RUNNING_SVG from 'icons/_icon_status_running.svg';
|
|
||||||
import SKIPPED_SVG from 'icons/_icon_status_skipped.svg';
|
|
||||||
import SUCCESS_SVG from 'icons/_icon_status_success.svg';
|
|
||||||
import WARNING_SVG from 'icons/_icon_status_warning.svg';
|
|
||||||
|
|
||||||
export const borderlessStatusIconEntityMap = {
|
|
||||||
icon_status_canceled: BORDERLESS_CANCELED_SVG,
|
|
||||||
icon_status_created: BORDERLESS_CREATED_SVG,
|
|
||||||
icon_status_failed: BORDERLESS_FAILED_SVG,
|
|
||||||
icon_status_manual: BORDERLESS_MANUAL_SVG,
|
|
||||||
icon_status_pending: BORDERLESS_PENDING_SVG,
|
|
||||||
icon_status_running: BORDERLESS_RUNNING_SVG,
|
|
||||||
icon_status_skipped: BORDERLESS_SKIPPED_SVG,
|
|
||||||
icon_status_success: BORDERLESS_SUCCESS_SVG,
|
|
||||||
icon_status_warning: BORDERLESS_WARNING_SVG,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const statusIconEntityMap = {
|
|
||||||
icon_status_canceled: CANCELED_SVG,
|
|
||||||
icon_status_created: CREATED_SVG,
|
|
||||||
icon_status_failed: FAILED_SVG,
|
|
||||||
icon_status_manual: MANUAL_SVG,
|
|
||||||
icon_status_pending: PENDING_SVG,
|
|
||||||
icon_status_running: RUNNING_SVG,
|
|
||||||
icon_status_skipped: SKIPPED_SVG,
|
|
||||||
icon_status_success: SUCCESS_SVG,
|
|
||||||
icon_status_warning: WARNING_SVG,
|
|
||||||
};
|
|
|
@ -43,7 +43,6 @@
|
||||||
computed: {
|
computed: {
|
||||||
cssClass() {
|
cssClass() {
|
||||||
const className = this.status.group;
|
const className = this.status.group;
|
||||||
|
|
||||||
return className ? `ci-status ci-${className}` : 'ci-status';
|
return className ? `ci-status ci-${className}` : 'ci-status';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { statusIconEntityMap } from '../ci_status_icons';
|
import icon from '../../vue_shared/components/icon.vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders CI icon based on API response shared between all places where it is used.
|
* Renders CI icon based on API response shared between all places where it is used.
|
||||||
|
@ -30,11 +30,11 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
components: {
|
||||||
statusIconSvg() {
|
icon,
|
||||||
return statusIconEntityMap[this.status.icon];
|
},
|
||||||
},
|
|
||||||
|
|
||||||
|
computed: {
|
||||||
cssClass() {
|
cssClass() {
|
||||||
const status = this.status.group;
|
const status = this.status.group;
|
||||||
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
|
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
|
||||||
|
@ -44,7 +44,8 @@
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
:class="cssClass"
|
:class="cssClass">
|
||||||
v-html="statusIconSvg">
|
<icon
|
||||||
|
:name="status.icon"/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
/* This is a re-usable vue component for rendering a svg sprite
|
||||||
|
icon
|
||||||
|
|
||||||
|
Sample configuration:
|
||||||
|
|
||||||
|
<icon
|
||||||
|
:img-src="userAvatarSrc"
|
||||||
|
:img-alt="tooltipText"
|
||||||
|
:tooltip-text="tooltipText"
|
||||||
|
tooltip-placement="top"
|
||||||
|
/>
|
||||||
|
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
cssClasses: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
spriteHref() {
|
||||||
|
return `${gon.sprite_icons}#${this.name}`;
|
||||||
|
},
|
||||||
|
iconSizeClass() {
|
||||||
|
return this.size ? `s${this.size}` : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
:class="[iconSizeClass, cssClasses]">
|
||||||
|
<use
|
||||||
|
v-bind="{'xlink:href':spriteHref}"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -56,4 +56,4 @@
|
||||||
@import "framework/icons";
|
@import "framework/icons";
|
||||||
@import "framework/snippets";
|
@import "framework/snippets";
|
||||||
@import "framework/memory_graph";
|
@import "framework/memory_graph";
|
||||||
@import "framework/responsive-tables";
|
@import "framework/responsive_tables";
|
||||||
|
|
|
@ -5,32 +5,6 @@
|
||||||
.cgreen { color: $common-green; }
|
.cgreen { color: $common-green; }
|
||||||
.cdark { color: $common-gray-dark; }
|
.cdark { color: $common-gray-dark; }
|
||||||
|
|
||||||
/** COMMON CLASSES **/
|
|
||||||
.prepend-top-0 { margin-top: 0; }
|
|
||||||
.prepend-top-5 { margin-top: 5px; }
|
|
||||||
.prepend-top-10 { margin-top: 10px; }
|
|
||||||
.prepend-top-default { margin-top: $gl-padding !important; }
|
|
||||||
.prepend-top-20 { margin-top: 20px; }
|
|
||||||
.prepend-left-4 { margin-left: 4px; }
|
|
||||||
.prepend-left-5 { margin-left: 5px; }
|
|
||||||
.prepend-left-10 { margin-left: 10px; }
|
|
||||||
.prepend-left-default { margin-left: $gl-padding; }
|
|
||||||
.prepend-left-20 { margin-left: 20px; }
|
|
||||||
.append-right-5 { margin-right: 5px; }
|
|
||||||
.append-right-8 { margin-right: 8px; }
|
|
||||||
.append-right-10 { margin-right: 10px; }
|
|
||||||
.append-right-default { margin-right: $gl-padding; }
|
|
||||||
.append-right-20 { margin-right: 20px; }
|
|
||||||
.append-bottom-0 { margin-bottom: 0; }
|
|
||||||
.append-bottom-5 { margin-bottom: 5px; }
|
|
||||||
.append-bottom-10 { margin-bottom: 10px; }
|
|
||||||
.append-bottom-15 { margin-bottom: 15px; }
|
|
||||||
.append-bottom-20 { margin-bottom: 20px; }
|
|
||||||
.append-bottom-default { margin-bottom: $gl-padding; }
|
|
||||||
.inline { display: inline-block; }
|
|
||||||
.center { text-align: center; }
|
|
||||||
.vertical-align-middle { vertical-align: middle; }
|
|
||||||
|
|
||||||
.underlined-link { text-decoration: underline; }
|
.underlined-link { text-decoration: underline; }
|
||||||
.hint { font-style: italic; color: $hint-color; }
|
.hint { font-style: italic; color: $hint-color; }
|
||||||
.light { color: $common-gray; }
|
.light { color: $common-gray; }
|
||||||
|
@ -448,3 +422,30 @@ table {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** COMMON CLASSES **/
|
||||||
|
.prepend-top-0 { margin-top: 0; }
|
||||||
|
.prepend-top-5 { margin-top: 5px; }
|
||||||
|
.prepend-top-10 { margin-top: 10px; }
|
||||||
|
.prepend-top-15 { margin-top: 15px; }
|
||||||
|
.prepend-top-default { margin-top: $gl-padding !important; }
|
||||||
|
.prepend-top-20 { margin-top: 20px; }
|
||||||
|
.prepend-left-4 { margin-left: 4px; }
|
||||||
|
.prepend-left-5 { margin-left: 5px; }
|
||||||
|
.prepend-left-10 { margin-left: 10px; }
|
||||||
|
.prepend-left-default { margin-left: $gl-padding; }
|
||||||
|
.prepend-left-20 { margin-left: 20px; }
|
||||||
|
.append-right-5 { margin-right: 5px; }
|
||||||
|
.append-right-8 { margin-right: 8px; }
|
||||||
|
.append-right-10 { margin-right: 10px; }
|
||||||
|
.append-right-default { margin-right: $gl-padding; }
|
||||||
|
.append-right-20 { margin-right: 20px; }
|
||||||
|
.append-bottom-0 { margin-bottom: 0; }
|
||||||
|
.append-bottom-5 { margin-bottom: 5px; }
|
||||||
|
.append-bottom-10 { margin-bottom: 10px; }
|
||||||
|
.append-bottom-15 { margin-bottom: 15px; }
|
||||||
|
.append-bottom-20 { margin-bottom: 20px; }
|
||||||
|
.append-bottom-default { margin-bottom: $gl-padding; }
|
||||||
|
.inline { display: inline-block; }
|
||||||
|
.center { text-align: center; }
|
||||||
|
.vertical-align-middle { vertical-align: middle; }
|
||||||
|
|
|
@ -3,57 +3,74 @@
|
||||||
max-width: #{$max + '%'};
|
max-width: #{$max + '%'};
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-responsive-table-row {
|
.gl-responsive-table-row-layout {
|
||||||
margin-top: 10px;
|
width: 100%;
|
||||||
border: 1px solid $border-color;
|
|
||||||
|
|
||||||
@media (min-width: $screen-md-min) {
|
@media (min-width: $screen-md-min) {
|
||||||
padding: 15px 0;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid $white-normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-section {
|
& > &:not(:first-child) {
|
||||||
white-space: nowrap;
|
margin-top: $gl-padding;
|
||||||
|
|
||||||
$section-widths: 10 15 20 25 30 40;
|
|
||||||
@each $width in $section-widths {
|
|
||||||
&.section-#{$width} {
|
|
||||||
flex: 0 0 #{$width + '%'};
|
|
||||||
|
|
||||||
@media (min-width: $screen-md-min) {
|
|
||||||
max-width: #{$width + '%'};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.table-button-footer) {
|
|
||||||
@media (max-width: $screen-sm-max) {
|
|
||||||
display: flex;
|
|
||||||
align-self: stretch;
|
|
||||||
padding: 10px;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 62px;
|
|
||||||
|
|
||||||
&:not(:first-of-type) {
|
|
||||||
border-top: 1px solid $white-normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.section-wrap {
|
|
||||||
white-space: normal;
|
|
||||||
|
|
||||||
@media (max-width: $screen-sm-max) {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gl-responsive-table-row {
|
||||||
|
@extend .gl-responsive-table-row-layout;
|
||||||
|
margin-top: 10px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
|
||||||
|
@media (min-width: $screen-md-min) {
|
||||||
|
margin: 0;
|
||||||
|
padding: $gl-padding 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid $white-normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gl-responsive-table-row-col-span {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-section {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
$section-widths: 10 15 20 25 30 40 100;
|
||||||
|
@each $width in $section-widths {
|
||||||
|
&.section-#{$width} {
|
||||||
|
flex: 0 0 #{$width + '%'};
|
||||||
|
|
||||||
|
@media (min-width: $screen-md-min) {
|
||||||
|
max-width: #{$width + '%'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $screen-sm-max) {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
padding: 10px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 62px;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top: 1px solid $white-normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.section-wrap {
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
@media (max-width: $screen-sm-max) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.section-align-top {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.table-button-footer {
|
.table-button-footer {
|
||||||
@media (min-width: $screen-md-min) {
|
@media (min-width: $screen-md-min) {
|
||||||
|
@ -61,12 +78,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $screen-sm-max) {
|
@media (max-width: $screen-sm-max) {
|
||||||
background-color: $gray-normal;
|
display: block;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
min-height: 0;
|
||||||
|
background-color: $gray-normal;
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
|
|
||||||
.table-action-buttons {
|
.table-action-buttons {
|
||||||
padding: 10px 5px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
|
@ -77,7 +95,14 @@
|
||||||
> .external-url,
|
> .external-url,
|
||||||
> .btn {
|
> .btn {
|
||||||
flex: 1 1 28px;
|
flex: 1 1 28px;
|
||||||
margin: 0 5px;
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-new {
|
.dropdown-new {
|
|
@ -333,8 +333,10 @@
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 3px;
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,9 +350,10 @@
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 3px;
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
height: 13px;
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -369,7 +372,7 @@
|
||||||
.build-job {
|
.build-job {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.fa-arrow-right {
|
.icon-arrow-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
|
@ -379,7 +382,7 @@
|
||||||
&.active {
|
&.active {
|
||||||
font-weight: $gl-font-weight-bold;
|
font-weight: $gl-font-weight-bold;
|
||||||
|
|
||||||
.fa-arrow-right {
|
.icon-arrow-right {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,8 +395,7 @@
|
||||||
background-color: $row-hover;
|
background-color: $row-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-refresh {
|
.icon-retry {
|
||||||
font-size: 13px;
|
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,4 @@
|
||||||
.clipboard-addon {
|
.clipboard-addon {
|
||||||
background-color: $white-light;
|
background-color: $white-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-block {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,12 +133,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-row {
|
.folder-row {
|
||||||
padding: 15px 0;
|
border-left: none;
|
||||||
border-bottom: 1px solid $white-normal;
|
border-right: none;
|
||||||
|
|
||||||
@media (max-width: $screen-sm-max) {
|
@media (min-width: $screen-sm-max) {
|
||||||
border-top: 1px solid $white-normal;
|
border-top: none;
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,8 +165,9 @@
|
||||||
z-index: 300;
|
z-index: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ci-action-icon-wrapper {
|
.ci-action-icon-wrapper svg {
|
||||||
line-height: 16px;
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.pipeline-actions {
|
.pipeline-actions {
|
||||||
padding-right: 0;
|
|
||||||
min-width: 170px; //Guarantees buttons don't break in several lines.
|
min-width: 170px; //Guarantees buttons don't break in several lines.
|
||||||
|
|
||||||
.btn-default {
|
.btn-default {
|
||||||
|
@ -452,7 +451,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action Icons in big pipeline-graph nodes
|
// Action Icons in big pipeline-graph nodes
|
||||||
.ci-action-icon-container .ci-action-icon-wrapper {
|
.ci-action-icon-container.ci-action-icon-wrapper {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
background: $white-light;
|
background: $white-light;
|
||||||
|
@ -468,8 +467,18 @@
|
||||||
svg {
|
svg {
|
||||||
fill: $gl-text-color-secondary;
|
fill: $gl-text-color-secondary;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -1px;
|
left: 5px;
|
||||||
top: -1px;
|
top: 2px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.play {
|
||||||
|
svg {
|
||||||
|
width: #{$ci-action-icon-size - 8};
|
||||||
|
height: #{$ci-action-icon-size - 8};
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover svg {
|
&:hover svg {
|
||||||
|
@ -721,17 +730,49 @@ button.mini-pipeline-graph-dropdown-toggle {
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $gl-text-color-secondary;
|
fill: $gl-text-color-secondary;
|
||||||
width: $ci-action-icon-size;
|
width: #{$ci-action-icon-size - 6};
|
||||||
height: $ci-action-icon-size;
|
height: #{$ci-action-icon-size - 6};
|
||||||
left: -6px;
|
left: -3px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -3px;
|
top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover svg,
|
&:hover svg,
|
||||||
&:focus svg {
|
&:focus svg {
|
||||||
fill: $gl-text-color;
|
fill: $gl-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.icon-action-retry,
|
||||||
|
&.icon-action-play {
|
||||||
|
svg {
|
||||||
|
width: #{$ci-action-icon-size - 6};
|
||||||
|
height: #{$ci-action-icon-size - 6};
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.icon-action-stop,
|
||||||
|
svg.icon-action-cancel {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
top: 1px;
|
||||||
|
left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.icon-action-play {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.icon-action-retry {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
top: 0;
|
||||||
|
left: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// link to the build
|
// link to the build
|
||||||
|
|
|
@ -23,15 +23,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings {
|
.settings {
|
||||||
overflow: hidden;
|
|
||||||
border-bottom: 1px solid $gray-darker;
|
border-bottom: 1px solid $gray-darker;
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.expanded {
|
&.animating {
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,14 +55,18 @@
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
padding-right: 110px;
|
padding-right: 110px;
|
||||||
animation: collapseMaxHeight 300ms ease-out;
|
animation: collapseMaxHeight 300ms ease-out;
|
||||||
|
// Keep the section from expanding when we scroll over it
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
&.expanded {
|
.settings.expanded & {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
animation: expandMaxHeight 300ms ease-in;
|
animation: expandMaxHeight 300ms ease-in;
|
||||||
|
// Reset and allow clicks again when expanded
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-animate {
|
.settings.no-animate & {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_index_vars
|
def set_index_vars
|
||||||
@scopes = Gitlab::Auth::API_SCOPES
|
@scopes = Gitlab::Auth.available_scopes(current_user)
|
||||||
|
|
||||||
@impersonation_token ||= finder.build
|
@impersonation_token ||= finder.build
|
||||||
@inactive_impersonation_tokens = finder(state: 'inactive').execute
|
@inactive_impersonation_tokens = finder(state: 'inactive').execute
|
||||||
|
|
|
@ -11,7 +11,7 @@ class ApplicationController < ActionController::Base
|
||||||
include EnforcesTwoFactorAuthentication
|
include EnforcesTwoFactorAuthentication
|
||||||
include WithPerformanceBar
|
include WithPerformanceBar
|
||||||
|
|
||||||
before_action :authenticate_user_from_private_token!
|
before_action :authenticate_user_from_personal_access_token!
|
||||||
before_action :authenticate_user_from_rss_token!
|
before_action :authenticate_user_from_rss_token!
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :validate_user_service_ticket!
|
before_action :validate_user_service_ticket!
|
||||||
|
@ -100,13 +100,12 @@ class ApplicationController < ActionController::Base
|
||||||
return try(:authenticated_user)
|
return try(:authenticated_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
# This filter handles both private tokens and personal access tokens
|
def authenticate_user_from_personal_access_token!
|
||||||
def authenticate_user_from_private_token!
|
|
||||||
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
|
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
|
||||||
|
|
||||||
return unless token.present?
|
return unless token.present?
|
||||||
|
|
||||||
user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
|
user = User.find_by_personal_access_token(token)
|
||||||
|
|
||||||
sessionless_sign_in(user)
|
sessionless_sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,54 @@ module IssuableActions
|
||||||
before_action :authorize_admin_issuable!, only: :bulk_update
|
before_action :authorize_admin_issuable!, only: :bulk_update
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
render show_view
|
||||||
|
end
|
||||||
|
format.json do
|
||||||
|
render json: serializer.represent(issuable, serializer: params[:serializer])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@issuable = update_service.execute(issuable)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
recaptcha_check_with_fallback { render :edit }
|
||||||
|
end
|
||||||
|
|
||||||
|
format.json do
|
||||||
|
render_entity_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue ActiveRecord::StaleObjectError
|
||||||
|
render_conflict_response
|
||||||
|
end
|
||||||
|
|
||||||
|
def realtime_changes
|
||||||
|
Gitlab::PollingInterval.set_header(response, interval: 3_000)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
title: view_context.markdown_field(issuable, :title),
|
||||||
|
title_text: issuable.title,
|
||||||
|
description: view_context.markdown_field(issuable, :description),
|
||||||
|
description_text: issuable.description,
|
||||||
|
task_status: issuable.task_status
|
||||||
|
}
|
||||||
|
|
||||||
|
if issuable.edited?
|
||||||
|
response[:updated_at] = issuable.updated_at
|
||||||
|
response[:updated_by_name] = issuable.last_edited_by.name
|
||||||
|
response[:updated_by_path] = user_path(issuable.last_edited_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: response
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
issuable.destroy
|
issuable.destroy
|
||||||
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
|
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
|
||||||
|
@ -68,6 +116,10 @@ module IssuableActions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorize_update_issuable!
|
||||||
|
render_404 unless can?(current_user, :"update_#{resource_name}", issuable)
|
||||||
|
end
|
||||||
|
|
||||||
def bulk_update_params
|
def bulk_update_params
|
||||||
permitted_keys = [
|
permitted_keys = [
|
||||||
:issuable_ids,
|
:issuable_ids,
|
||||||
|
@ -92,4 +144,24 @@ module IssuableActions
|
||||||
def resource_name
|
def resource_name
|
||||||
@resource_name ||= controller_name.singularize
|
@resource_name ||= controller_name.singularize
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_entity_json
|
||||||
|
if @issuable.valid?
|
||||||
|
render json: serializer.represent(@issuable)
|
||||||
|
else
|
||||||
|
render json: { errors: @issuable.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_view
|
||||||
|
'show'
|
||||||
|
end
|
||||||
|
|
||||||
|
def serializer
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_service
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,11 +30,11 @@ class JwtController < ApplicationController
|
||||||
render_unauthorized
|
render_unauthorized
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue Gitlab::Auth::MissingPersonalTokenError
|
rescue Gitlab::Auth::MissingPersonalAccessTokenError
|
||||||
render_missing_personal_token
|
render_missing_personal_access_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_missing_personal_token
|
def render_missing_personal_access_token
|
||||||
render json: {
|
render json: {
|
||||||
errors: [
|
errors: [
|
||||||
{ code: 'UNAUTHORIZED',
|
{ code: 'UNAUTHORIZED',
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_index_vars
|
def set_index_vars
|
||||||
@scopes = Gitlab::Auth.available_scopes
|
@scopes = Gitlab::Auth.available_scopes(current_user)
|
||||||
|
|
||||||
@inactive_personal_access_tokens = finder(state: 'inactive').execute
|
@inactive_personal_access_tokens = finder(state: 'inactive').execute
|
||||||
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
|
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
|
||||||
|
|
|
@ -24,16 +24,6 @@ class ProfilesController < Profiles::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_private_token
|
|
||||||
Users::UpdateService.new(current_user, user: @user).execute! do |user|
|
|
||||||
user.reset_authentication_token!
|
|
||||||
end
|
|
||||||
|
|
||||||
flash[:notice] = "Private token was successfully reset"
|
|
||||||
|
|
||||||
redirect_to profile_account_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_incoming_email_token
|
def reset_incoming_email_token
|
||||||
Users::UpdateService.new(current_user, user: @user).execute! do |user|
|
Users::UpdateService.new(current_user, user: @user).execute! do |user|
|
||||||
user.reset_incoming_email_token!
|
user.reset_incoming_email_token!
|
||||||
|
@ -41,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController
|
||||||
|
|
||||||
flash[:notice] = "Incoming email token was successfully reset"
|
flash[:notice] = "Incoming email token was successfully reset"
|
||||||
|
|
||||||
redirect_to profile_account_path
|
redirect_to profile_personal_access_tokens_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_rss_token
|
def reset_rss_token
|
||||||
|
@ -51,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController
|
||||||
|
|
||||||
flash[:notice] = "RSS token was successfully reset"
|
flash[:notice] = "RSS token was successfully reset"
|
||||||
|
|
||||||
redirect_to profile_account_path
|
redirect_to profile_personal_access_tokens_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def audit_log
|
def audit_log
|
||||||
|
|
|
@ -53,8 +53,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
|
||||||
|
|
||||||
send_challenges
|
send_challenges
|
||||||
render plain: "HTTP Basic: Access denied\n", status: 401
|
render plain: "HTTP Basic: Access denied\n", status: 401
|
||||||
rescue Gitlab::Auth::MissingPersonalTokenError
|
rescue Gitlab::Auth::MissingPersonalAccessTokenError
|
||||||
render_missing_personal_token
|
render_missing_personal_access_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def basic_auth_provided?
|
def basic_auth_provided?
|
||||||
|
@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
|
||||||
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
|
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_missing_personal_token
|
def render_missing_personal_access_token
|
||||||
render plain: "HTTP Basic: Access denied\n" \
|
render plain: "HTTP Basic: Access denied\n" \
|
||||||
"You must use a personal access token with 'api' scope for Git over HTTP.\n" \
|
"You must use a personal access token with 'api' scope for Git over HTTP.\n" \
|
||||||
"You can generate one at #{profile_personal_access_tokens_url}",
|
"You can generate one at #{profile_personal_access_tokens_url}",
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
before_action :authorize_create_issue!, only: [:new, :create]
|
before_action :authorize_create_issue!, only: [:new, :create]
|
||||||
|
|
||||||
# Allow modify issue
|
# Allow modify issue
|
||||||
before_action :authorize_update_issue!, only: [:edit, :update, :move]
|
before_action :authorize_update_issuable!, only: [:edit, :update, :move]
|
||||||
|
|
||||||
# Allow create a new branch and empty WIP merge request from current issue
|
# Allow create a new branch and empty WIP merge request from current issue
|
||||||
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
||||||
|
@ -67,18 +67,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
respond_with(@issue)
|
respond_with(@issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
|
||||||
@noteable = @issue
|
|
||||||
@note = @project.notes.new(noteable: @issue)
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
format.json do
|
|
||||||
render json: serializer.represent(@issue, serializer: params[:serializer])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def discussions
|
def discussions
|
||||||
notes = @issue.notes
|
notes = @issue.notes
|
||||||
.inc_relations_for_view
|
.inc_relations_for_view
|
||||||
|
@ -120,25 +108,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
|
||||||
update_params = issue_params.merge(spammable_params)
|
|
||||||
|
|
||||||
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html do
|
|
||||||
recaptcha_check_with_fallback { render :edit }
|
|
||||||
end
|
|
||||||
|
|
||||||
format.json do
|
|
||||||
render_issue_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
rescue ActiveRecord::StaleObjectError
|
|
||||||
render_conflict_response
|
|
||||||
end
|
|
||||||
|
|
||||||
def move
|
def move
|
||||||
params.require(:move_to_project_id)
|
params.require(:move_to_project_id)
|
||||||
|
|
||||||
|
@ -196,26 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def realtime_changes
|
|
||||||
Gitlab::PollingInterval.set_header(response, interval: 3_000)
|
|
||||||
|
|
||||||
response = {
|
|
||||||
title: view_context.markdown_field(@issue, :title),
|
|
||||||
title_text: @issue.title,
|
|
||||||
description: view_context.markdown_field(@issue, :description),
|
|
||||||
description_text: @issue.description,
|
|
||||||
task_status: @issue.task_status
|
|
||||||
}
|
|
||||||
|
|
||||||
if @issue.edited?
|
|
||||||
response[:updated_at] = @issue.updated_at
|
|
||||||
response[:updated_by_name] = @issue.last_edited_by.name
|
|
||||||
response[:updated_by_path] = user_path(@issue.last_edited_by)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: response
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_merge_request
|
def create_merge_request
|
||||||
result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
|
result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
|
||||||
|
|
||||||
|
@ -231,7 +180,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
def issue
|
def issue
|
||||||
return @issue if defined?(@issue)
|
return @issue if defined?(@issue)
|
||||||
# The Sortable default scope causes performance issues when used with find_by
|
# The Sortable default scope causes performance issues when used with find_by
|
||||||
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
|
@issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
|
||||||
|
@note = @project.notes.new(noteable: @issuable)
|
||||||
|
|
||||||
return render_404 unless can?(current_user, :read_issue, @issue)
|
return render_404 unless can?(current_user, :read_issue, @issue)
|
||||||
|
|
||||||
|
@ -246,14 +196,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
project_issue_path(@project, @issue)
|
project_issue_path(@project, @issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_update_issue!
|
|
||||||
render_404 unless can?(current_user, :update_issue, @issue)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_admin_issues!
|
|
||||||
render_404 unless can?(current_user, :admin_issue, @project)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_create_merge_request!
|
def authorize_create_merge_request!
|
||||||
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
|
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
|
||||||
end
|
end
|
||||||
|
@ -305,4 +247,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
def serializer
|
def serializer
|
||||||
IssueSerializer.new(current_user: current_user, project: issue.project)
|
IssueSerializer.new(current_user: current_user, project: issue.project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_service
|
||||||
|
update_params = issue_params.merge(spammable_params)
|
||||||
|
Issues::UpdateService.new(project, current_user, update_params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
skip_before_action :merge_request, only: [:index, :bulk_update]
|
skip_before_action :merge_request, only: [:index, :bulk_update]
|
||||||
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
|
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
|
||||||
|
|
||||||
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
|
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
|
||||||
|
|
||||||
before_action :authenticate_user!, only: [:assign_related_issues]
|
before_action :authenticate_user!, only: [:assign_related_issues]
|
||||||
|
|
||||||
|
@ -256,14 +256,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
alias_method :issuable, :merge_request
|
alias_method :issuable, :merge_request
|
||||||
alias_method :awardable, :merge_request
|
alias_method :awardable, :merge_request
|
||||||
|
|
||||||
def authorize_update_merge_request!
|
|
||||||
return render_404 unless can?(current_user, :update_merge_request, @merge_request)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_admin_merge_request!
|
|
||||||
return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validates_merge_request
|
def validates_merge_request
|
||||||
# Show git not found page
|
# Show git not found page
|
||||||
# if there is no saved commits between source & target branch
|
# if there is no saved commits between source & target branch
|
||||||
|
|
|
@ -63,34 +63,34 @@ module CiStatusHelper
|
||||||
|
|
||||||
def ci_icon_for_status(status)
|
def ci_icon_for_status(status)
|
||||||
if detailed_status?(status)
|
if detailed_status?(status)
|
||||||
return custom_icon(status.icon)
|
return sprite_icon(status.icon)
|
||||||
end
|
end
|
||||||
|
|
||||||
icon_name =
|
icon_name =
|
||||||
case status
|
case status
|
||||||
when 'success'
|
when 'success'
|
||||||
'icon_status_success'
|
'status_success'
|
||||||
when 'success_with_warnings'
|
when 'success_with_warnings'
|
||||||
'icon_status_warning'
|
'status_warning'
|
||||||
when 'failed'
|
when 'failed'
|
||||||
'icon_status_failed'
|
'status_failed'
|
||||||
when 'pending'
|
when 'pending'
|
||||||
'icon_status_pending'
|
'status_pending'
|
||||||
when 'running'
|
when 'running'
|
||||||
'icon_status_running'
|
'status_running'
|
||||||
when 'play'
|
when 'play'
|
||||||
'icon_play'
|
'play'
|
||||||
when 'created'
|
when 'created'
|
||||||
'icon_status_created'
|
'status_created'
|
||||||
when 'skipped'
|
when 'skipped'
|
||||||
'icon_status_skipped'
|
'status_skipped'
|
||||||
when 'manual'
|
when 'manual'
|
||||||
'icon_status_manual'
|
'status_manual'
|
||||||
else
|
else
|
||||||
'icon_status_canceled'
|
'status_canceled'
|
||||||
end
|
end
|
||||||
|
|
||||||
custom_icon(icon_name)
|
sprite_icon(icon_name, size: 16)
|
||||||
end
|
end
|
||||||
|
|
||||||
def pipeline_status_cache_key(pipeline_status)
|
def pipeline_status_cache_key(pipeline_status)
|
||||||
|
|
|
@ -71,11 +71,13 @@ module GitlabRoutingHelper
|
||||||
project_commit_url(entity.project, entity.sha, *args)
|
project_commit_url(entity.project, entity.sha, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def preview_markdown_path(project, *args)
|
def preview_markdown_path(parent, *args)
|
||||||
|
return group_preview_markdown_path(parent) if parent.is_a?(Group)
|
||||||
|
|
||||||
if @snippet.is_a?(PersonalSnippet)
|
if @snippet.is_a?(PersonalSnippet)
|
||||||
preview_markdown_snippets_path
|
preview_markdown_snippets_path
|
||||||
else
|
else
|
||||||
preview_markdown_project_path(project, *args)
|
preview_markdown_project_path(parent, *args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -211,15 +211,13 @@ module IssuablesHelper
|
||||||
|
|
||||||
def issuable_initial_data(issuable)
|
def issuable_initial_data(issuable)
|
||||||
data = {
|
data = {
|
||||||
endpoint: project_issue_path(@project, issuable),
|
endpoint: issuable_path(issuable),
|
||||||
canUpdate: can?(current_user, :update_issue, issuable),
|
canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
|
||||||
canDestroy: can?(current_user, :destroy_issue, issuable),
|
canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
|
||||||
issuableRef: issuable.to_reference,
|
issuableRef: issuable.to_reference,
|
||||||
markdownPreviewPath: preview_markdown_path(@project),
|
markdownPreviewPath: preview_markdown_path(parent),
|
||||||
markdownDocsPath: help_page_path('user/markdown'),
|
markdownDocsPath: help_page_path('user/markdown'),
|
||||||
issuableTemplates: issuable_templates(issuable),
|
issuableTemplates: issuable_templates(issuable),
|
||||||
projectPath: ref_project.path,
|
|
||||||
projectNamespace: ref_project.namespace.full_path,
|
|
||||||
initialTitleHtml: markdown_field(issuable, :title),
|
initialTitleHtml: markdown_field(issuable, :title),
|
||||||
initialTitleText: issuable.title,
|
initialTitleText: issuable.title,
|
||||||
initialDescriptionHtml: markdown_field(issuable, :description),
|
initialDescriptionHtml: markdown_field(issuable, :description),
|
||||||
|
@ -227,6 +225,12 @@ module IssuablesHelper
|
||||||
initialTaskStatus: issuable.task_status
|
initialTaskStatus: issuable.task_status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parent.is_a?(Group)
|
||||||
|
data[:groupPath] = parent.path
|
||||||
|
else
|
||||||
|
data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path)
|
||||||
|
end
|
||||||
|
|
||||||
data.merge!(updated_at_by(issuable))
|
data.merge!(updated_at_by(issuable))
|
||||||
|
|
||||||
data.to_json
|
data.to_json
|
||||||
|
@ -263,12 +267,7 @@ module IssuablesHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def issuable_path(issuable, *options)
|
def issuable_path(issuable, *options)
|
||||||
case issuable
|
polymorphic_path(issuable, *options)
|
||||||
when Issue
|
|
||||||
issue_path(issuable, *options)
|
|
||||||
when MergeRequest
|
|
||||||
merge_request_path(issuable, *options)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def issuable_url(issuable, *options)
|
def issuable_url(issuable, *options)
|
||||||
|
@ -369,4 +368,8 @@ module IssuablesHelper
|
||||||
fullPath: @project.full_path
|
fullPath: @project.full_path
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parent
|
||||||
|
@project || @group
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,7 +49,8 @@ module CacheMarkdownField
|
||||||
|
|
||||||
# Always include a project key, or Banzai complains
|
# Always include a project key, or Banzai complains
|
||||||
project = self.project if self.respond_to?(:project)
|
project = self.project if self.respond_to?(:project)
|
||||||
context = cached_markdown_fields[field].merge(project: project)
|
group = self.group if self.respond_to?(:group)
|
||||||
|
context = cached_markdown_fields[field].merge(project: project, group: group)
|
||||||
|
|
||||||
# Banzai is less strict about authors, so don't always have an author key
|
# Banzai is less strict about authors, so don't always have an author key
|
||||||
context[:author] = self.author if self.respond_to?(:author)
|
context[:author] = self.author if self.respond_to?(:author)
|
||||||
|
|
|
@ -14,7 +14,6 @@ module Issuable
|
||||||
include StripAttribute
|
include StripAttribute
|
||||||
include Awardable
|
include Awardable
|
||||||
include Taskable
|
include Taskable
|
||||||
include TimeTrackable
|
|
||||||
include Importable
|
include Importable
|
||||||
include Editable
|
include Editable
|
||||||
include AfterCommitQueue
|
include AfterCommitQueue
|
||||||
|
@ -95,8 +94,6 @@ module Issuable
|
||||||
|
|
||||||
strip_attributes :title
|
strip_attributes :title
|
||||||
|
|
||||||
acts_as_paranoid
|
|
||||||
|
|
||||||
after_save :record_metrics, unless: :imported?
|
after_save :record_metrics, unless: :imported?
|
||||||
|
|
||||||
# We want to use optimistic lock for cases when only title or description are involved
|
# We want to use optimistic lock for cases when only title or description are involved
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Placeholder class for model that is implemented in EE
|
||||||
|
# It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE
|
||||||
|
class Epic < ActiveRecord::Base
|
||||||
|
# TODO: this will be implemented as part of #3853
|
||||||
|
def to_reference
|
||||||
|
end
|
||||||
|
end
|
|
@ -180,6 +180,12 @@ class Group < Namespace
|
||||||
add_user(user, :owner, current_user: current_user)
|
add_user(user, :owner, current_user: current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def member?(user, min_access_level = Gitlab::Access::GUEST)
|
||||||
|
return false unless user
|
||||||
|
|
||||||
|
max_member_access_for_user(user) >= min_access_level
|
||||||
|
end
|
||||||
|
|
||||||
def has_owner?(user)
|
def has_owner?(user)
|
||||||
return false unless user
|
return false unless user
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base
|
||||||
include FasterCacheKeys
|
include FasterCacheKeys
|
||||||
include RelativePositioning
|
include RelativePositioning
|
||||||
include CreatedAtFilterable
|
include CreatedAtFilterable
|
||||||
|
include TimeTrackable
|
||||||
|
|
||||||
DueDateStruct = Struct.new(:title, :name).freeze
|
DueDateStruct = Struct.new(:title, :name).freeze
|
||||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||||
|
@ -74,6 +75,8 @@ class Issue < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
acts_as_paranoid
|
||||||
|
|
||||||
def self.reference_prefix
|
def self.reference_prefix
|
||||||
'#'
|
'#'
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base
|
||||||
include Sortable
|
include Sortable
|
||||||
include IgnorableColumn
|
include IgnorableColumn
|
||||||
include CreatedAtFilterable
|
include CreatedAtFilterable
|
||||||
|
include TimeTrackable
|
||||||
|
|
||||||
ignore_column :locked_at
|
ignore_column :locked_at
|
||||||
|
|
||||||
|
@ -119,6 +120,8 @@ class MergeRequest < ActiveRecord::Base
|
||||||
|
|
||||||
after_save :keep_around_commit
|
after_save :keep_around_commit
|
||||||
|
|
||||||
|
acts_as_paranoid
|
||||||
|
|
||||||
def self.reference_prefix
|
def self.reference_prefix
|
||||||
'!'
|
'!'
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||||
# Collect information about commits and diff from repository
|
# Collect information about commits and diff from repository
|
||||||
# and save it to the database as serialized data
|
# and save it to the database as serialized data
|
||||||
def save_git_content
|
def save_git_content
|
||||||
|
MergeRequest
|
||||||
|
.where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
|
||||||
|
.update_all(latest_merge_request_diff_id: self.id)
|
||||||
|
|
||||||
ensure_commit_shas
|
ensure_commit_shas
|
||||||
save_commits
|
save_commits
|
||||||
save_diffs
|
save_diffs
|
||||||
|
|
|
@ -69,7 +69,7 @@ class Note < ActiveRecord::Base
|
||||||
delegate :title, to: :noteable, allow_nil: true
|
delegate :title, to: :noteable, allow_nil: true
|
||||||
|
|
||||||
validates :note, presence: true
|
validates :note, presence: true
|
||||||
validates :project, presence: true, unless: :for_personal_snippet?
|
validates :project, presence: true, if: :for_project_noteable?
|
||||||
|
|
||||||
# Attachments are deprecated and are handled by Markdown uploader
|
# Attachments are deprecated and are handled by Markdown uploader
|
||||||
validates :attachment, file_size: { maximum: :max_attachment_size }
|
validates :attachment, file_size: { maximum: :max_attachment_size }
|
||||||
|
@ -114,7 +114,7 @@ class Note < ActiveRecord::Base
|
||||||
after_initialize :ensure_discussion_id
|
after_initialize :ensure_discussion_id
|
||||||
before_validation :nullify_blank_type, :nullify_blank_line_code
|
before_validation :nullify_blank_type, :nullify_blank_line_code
|
||||||
before_validation :set_discussion_id, on: :create
|
before_validation :set_discussion_id, on: :create
|
||||||
after_save :keep_around_commit, unless: :for_personal_snippet?
|
after_save :keep_around_commit, if: :for_project_noteable?
|
||||||
after_save :expire_etag_cache
|
after_save :expire_etag_cache
|
||||||
after_destroy :expire_etag_cache
|
after_destroy :expire_etag_cache
|
||||||
|
|
||||||
|
@ -208,6 +208,10 @@ class Note < ActiveRecord::Base
|
||||||
noteable.is_a?(PersonalSnippet)
|
noteable.is_a?(PersonalSnippet)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def for_project_noteable?
|
||||||
|
!for_personal_snippet?
|
||||||
|
end
|
||||||
|
|
||||||
def skip_project_check?
|
def skip_project_check?
|
||||||
for_personal_snippet?
|
for_personal_snippet?
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,5 +2,13 @@ class OauthAccessToken < Doorkeeper::AccessToken
|
||||||
belongs_to :resource_owner, class_name: 'User'
|
belongs_to :resource_owner, class_name: 'User'
|
||||||
belongs_to :application, class_name: 'Doorkeeper::Application'
|
belongs_to :application, class_name: 'Doorkeeper::Application'
|
||||||
|
|
||||||
alias_method :user, :resource_owner
|
alias_attribute :user, :resource_owner
|
||||||
|
|
||||||
|
def scopes=(value)
|
||||||
|
if value.is_a?(Array)
|
||||||
|
super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,8 +21,8 @@ class User < ActiveRecord::Base
|
||||||
|
|
||||||
ignore_column :external_email
|
ignore_column :external_email
|
||||||
ignore_column :email_provider
|
ignore_column :email_provider
|
||||||
|
ignore_column :authentication_token
|
||||||
|
|
||||||
add_authentication_token_field :authentication_token
|
|
||||||
add_authentication_token_field :incoming_email_token
|
add_authentication_token_field :incoming_email_token
|
||||||
add_authentication_token_field :rss_token
|
add_authentication_token_field :rss_token
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ class User < ActiveRecord::Base
|
||||||
before_validation :sanitize_attrs
|
before_validation :sanitize_attrs
|
||||||
before_validation :set_notification_email, if: :email_changed?
|
before_validation :set_notification_email, if: :email_changed?
|
||||||
before_validation :set_public_email, if: :public_email_changed?
|
before_validation :set_public_email, if: :public_email_changed?
|
||||||
before_save :ensure_authentication_token, :ensure_incoming_email_token
|
before_save :ensure_incoming_email_token
|
||||||
before_save :ensure_user_rights_and_limits, if: :external_changed?
|
before_save :ensure_user_rights_and_limits, if: :external_changed?
|
||||||
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
|
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
|
||||||
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
|
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
|
||||||
|
@ -185,8 +185,6 @@ class User < ActiveRecord::Base
|
||||||
# Note: When adding an option, it MUST go on the end of the array.
|
# Note: When adding an option, it MUST go on the end of the array.
|
||||||
enum project_view: [:readme, :activity, :files]
|
enum project_view: [:readme, :activity, :files]
|
||||||
|
|
||||||
alias_attribute :private_token, :authentication_token
|
|
||||||
|
|
||||||
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
||||||
|
|
||||||
state_machine :state, initial: :active do
|
state_machine :state, initial: :active do
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
class IssuableEntity < Grape::Entity
|
class IssuableEntity < Grape::Entity
|
||||||
|
include RequestAwareEntity
|
||||||
|
|
||||||
expose :id
|
expose :id
|
||||||
expose :iid
|
expose :iid
|
||||||
expose :author_id
|
expose :author_id
|
||||||
expose :description
|
expose :description
|
||||||
expose :lock_version
|
expose :lock_version
|
||||||
expose :milestone_id
|
expose :milestone_id
|
||||||
expose :state
|
|
||||||
expose :title
|
expose :title
|
||||||
expose :updated_by_id
|
expose :updated_by_id
|
||||||
expose :created_at
|
expose :created_at
|
||||||
expose :updated_at
|
expose :updated_at
|
||||||
expose :deleted_at
|
|
||||||
expose :time_estimate
|
|
||||||
expose :total_time_spent
|
|
||||||
expose :human_time_estimate
|
|
||||||
expose :human_total_time_spent
|
|
||||||
expose :milestone, using: API::Entities::Milestone
|
expose :milestone, using: API::Entities::Milestone
|
||||||
expose :labels, using: LabelEntity
|
expose :labels, using: LabelEntity
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
class IssueEntity < IssuableEntity
|
class IssueEntity < IssuableEntity
|
||||||
include RequestAwareEntity
|
include TimeTrackableEntity
|
||||||
|
|
||||||
|
expose :state
|
||||||
|
expose :deleted_at
|
||||||
expose :branch_name
|
expose :branch_name
|
||||||
expose :confidential
|
expose :confidential
|
||||||
expose :discussion_locked
|
expose :discussion_locked
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
class MergeRequestEntity < IssuableEntity
|
class MergeRequestEntity < IssuableEntity
|
||||||
include RequestAwareEntity
|
include TimeTrackableEntity
|
||||||
|
|
||||||
|
expose :state
|
||||||
|
expose :deleted_at
|
||||||
expose :in_progress_merge_commit_sha
|
expose :in_progress_merge_commit_sha
|
||||||
expose :merge_commit_sha
|
expose :merge_commit_sha
|
||||||
expose :merge_error
|
expose :merge_error
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
module TimeTrackableEntity
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
extend Grape
|
||||||
|
|
||||||
|
included do
|
||||||
|
expose :time_estimate
|
||||||
|
expose :total_time_spent
|
||||||
|
expose :human_time_estimate
|
||||||
|
expose :human_total_time_spent
|
||||||
|
end
|
||||||
|
end
|
|
@ -39,11 +39,8 @@ class AccessTokenValidationService
|
||||||
token_scopes = token.scopes.map(&:to_sym)
|
token_scopes = token.scopes.map(&:to_sym)
|
||||||
|
|
||||||
required_scopes.any? do |scope|
|
required_scopes.any? do |scope|
|
||||||
if scope.respond_to?(:sufficient?)
|
scope = API::Scope.new(scope) unless scope.is_a?(API::Scope)
|
||||||
scope.sufficient?(token_scopes, request)
|
scope.sufficient?(token_scopes, request)
|
||||||
else
|
|
||||||
API::Scope.new(scope).sufficient?(token_scopes, request)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
module Issuable
|
||||||
|
class CommonSystemNotesService < ::BaseService
|
||||||
|
attr_reader :issuable
|
||||||
|
|
||||||
|
def execute(issuable, old_labels)
|
||||||
|
@issuable = issuable
|
||||||
|
|
||||||
|
if issuable.previous_changes.include?('title')
|
||||||
|
create_title_change_note(issuable.previous_changes['title'].first)
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_description_change_note
|
||||||
|
|
||||||
|
handle_time_tracking_note if issuable.is_a?(TimeTrackable)
|
||||||
|
create_labels_note(old_labels) if issuable.labels != old_labels
|
||||||
|
create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
|
||||||
|
create_milestone_note if issuable.previous_changes.include?('milestone_id')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def handle_time_tracking_note
|
||||||
|
if issuable.previous_changes.include?('time_estimate')
|
||||||
|
create_time_estimate_note
|
||||||
|
end
|
||||||
|
|
||||||
|
if issuable.time_spent?
|
||||||
|
create_time_spent_note
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_description_change_note
|
||||||
|
if issuable.previous_changes.include?('description')
|
||||||
|
if issuable.tasks? && issuable.updated_tasks.any?
|
||||||
|
create_task_status_note
|
||||||
|
else
|
||||||
|
# TODO: Show this note if non-task content was modified.
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab-ce/issues/33577
|
||||||
|
create_description_change_note
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_labels_note(old_labels)
|
||||||
|
added_labels = issuable.labels - old_labels
|
||||||
|
removed_labels = old_labels - issuable.labels
|
||||||
|
|
||||||
|
SystemNoteService.change_label(issuable, issuable.project, current_user, added_labels, removed_labels)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_title_change_note(old_title)
|
||||||
|
SystemNoteService.change_title(issuable, issuable.project, current_user, old_title)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_description_change_note
|
||||||
|
SystemNoteService.change_description(issuable, issuable.project, current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_task_status_note
|
||||||
|
issuable.updated_tasks.each do |task|
|
||||||
|
SystemNoteService.change_task_status(issuable, issuable.project, current_user, task)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_time_estimate_note
|
||||||
|
SystemNoteService.change_time_estimate(issuable, issuable.project, current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_time_spent_note
|
||||||
|
SystemNoteService.change_time_spent(issuable, issuable.project, issuable.time_spent_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_milestone_note
|
||||||
|
SystemNoteService.change_milestone(issuable, issuable.project, current_user, issuable.milestone)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_discussion_lock_note
|
||||||
|
SystemNoteService.discussion_lock(issuable, current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,56 +1,10 @@
|
||||||
class IssuableBaseService < BaseService
|
class IssuableBaseService < BaseService
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_milestone_note(issuable)
|
|
||||||
SystemNoteService.change_milestone(
|
|
||||||
issuable, issuable.project, current_user, issuable.milestone)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_labels_note(issuable, old_labels)
|
|
||||||
added_labels = issuable.labels - old_labels
|
|
||||||
removed_labels = old_labels - issuable.labels
|
|
||||||
|
|
||||||
SystemNoteService.change_label(
|
|
||||||
issuable, issuable.project, current_user, added_labels, removed_labels)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_title_change_note(issuable, old_title)
|
|
||||||
SystemNoteService.change_title(
|
|
||||||
issuable, issuable.project, current_user, old_title)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_description_change_note(issuable)
|
|
||||||
SystemNoteService.change_description(issuable, issuable.project, current_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
|
|
||||||
SystemNoteService.change_branch(
|
|
||||||
issuable, issuable.project, current_user, branch_type,
|
|
||||||
old_branch, new_branch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_task_status_note(issuable)
|
|
||||||
issuable.updated_tasks.each do |task|
|
|
||||||
SystemNoteService.change_task_status(issuable, issuable.project, current_user, task)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_time_estimate_note(issuable)
|
|
||||||
SystemNoteService.change_time_estimate(issuable, issuable.project, current_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_time_spent_note(issuable)
|
|
||||||
SystemNoteService.change_time_spent(issuable, issuable.project, current_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_discussion_lock_note(issuable)
|
|
||||||
SystemNoteService.discussion_lock(issuable, current_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_params(issuable)
|
def filter_params(issuable)
|
||||||
ability_name = :"admin_#{issuable.to_ability_name}"
|
ability_name = :"admin_#{issuable.to_ability_name}"
|
||||||
|
|
||||||
unless can?(current_user, ability_name, project)
|
unless can?(current_user, ability_name, issuable)
|
||||||
params.delete(:milestone_id)
|
params.delete(:milestone_id)
|
||||||
params.delete(:labels)
|
params.delete(:labels)
|
||||||
params.delete(:add_label_ids)
|
params.delete(:add_label_ids)
|
||||||
|
@ -233,15 +187,14 @@ class IssuableBaseService < BaseService
|
||||||
|
|
||||||
# We have to perform this check before saving the issuable as Rails resets
|
# We have to perform this check before saving the issuable as Rails resets
|
||||||
# the changed fields upon calling #save.
|
# the changed fields upon calling #save.
|
||||||
update_project_counters = issuable.update_project_counter_caches?
|
update_project_counters = issuable.project && issuable.update_project_counter_caches?
|
||||||
|
|
||||||
if issuable.with_transaction_returning_status { issuable.save }
|
if issuable.with_transaction_returning_status { issuable.save }
|
||||||
# We do not touch as it will affect a update on updated_at field
|
# We do not touch as it will affect a update on updated_at field
|
||||||
ActiveRecord::Base.no_touching do
|
ActiveRecord::Base.no_touching do
|
||||||
handle_common_system_notes(issuable, old_labels: old_labels)
|
Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels)
|
||||||
end
|
end
|
||||||
|
|
||||||
change_discussion_lock(issuable)
|
|
||||||
handle_changes(
|
handle_changes(
|
||||||
issuable,
|
issuable,
|
||||||
old_labels: old_labels,
|
old_labels: old_labels,
|
||||||
|
@ -300,12 +253,6 @@ class IssuableBaseService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_discussion_lock(issuable)
|
|
||||||
if issuable.previous_changes.include?('discussion_locked')
|
|
||||||
create_discussion_lock_note(issuable)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def toggle_award(issuable)
|
def toggle_award(issuable)
|
||||||
award = params.delete(:emoji_award)
|
award = params.delete(:emoji_award)
|
||||||
if award
|
if award
|
||||||
|
@ -328,35 +275,17 @@ class IssuableBaseService < BaseService
|
||||||
attrs_changed || labels_changed || assignees_changed
|
attrs_changed || labels_changed || assignees_changed
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_common_system_notes(issuable, old_labels: [])
|
|
||||||
if issuable.previous_changes.include?('title')
|
|
||||||
create_title_change_note(issuable, issuable.previous_changes['title'].first)
|
|
||||||
end
|
|
||||||
|
|
||||||
if issuable.previous_changes.include?('description')
|
|
||||||
if issuable.tasks? && issuable.updated_tasks.any?
|
|
||||||
create_task_status_note(issuable)
|
|
||||||
else
|
|
||||||
# TODO: Show this note if non-task content was modified.
|
|
||||||
# https://gitlab.com/gitlab-org/gitlab-ce/issues/33577
|
|
||||||
create_description_change_note(issuable)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if issuable.previous_changes.include?('time_estimate')
|
|
||||||
create_time_estimate_note(issuable)
|
|
||||||
end
|
|
||||||
|
|
||||||
if issuable.time_spent?
|
|
||||||
create_time_spent_note(issuable)
|
|
||||||
end
|
|
||||||
|
|
||||||
create_labels_note(issuable, old_labels) if issuable.labels != old_labels
|
|
||||||
end
|
|
||||||
|
|
||||||
def invalidate_cache_counts(issuable, users: [])
|
def invalidate_cache_counts(issuable, users: [])
|
||||||
users.each do |user|
|
users.each do |user|
|
||||||
user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") # rubocop:disable GitlabSecurity/PublicSend
|
user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") # rubocop:disable GitlabSecurity/PublicSend
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# override if needed
|
||||||
|
def handle_changes(issuable, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
# override if needed
|
||||||
|
def execute_hooks(issuable, action = 'open', params = {})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,10 +27,6 @@ module Issues
|
||||||
todo_service.update_issue(issue, current_user, old_mentioned_users)
|
todo_service.update_issue(issue, current_user, old_mentioned_users)
|
||||||
end
|
end
|
||||||
|
|
||||||
if issue.previous_changes.include?('milestone_id')
|
|
||||||
create_milestone_note(issue)
|
|
||||||
end
|
|
||||||
|
|
||||||
if issue.assignees != old_assignees
|
if issue.assignees != old_assignees
|
||||||
create_assignee_note(issue, old_assignees)
|
create_assignee_note(issue, old_assignees)
|
||||||
notification_service.reassigned_issue(issue, current_user, old_assignees)
|
notification_service.reassigned_issue(issue, current_user, old_assignees)
|
||||||
|
|
|
@ -40,10 +40,6 @@ module MergeRequests
|
||||||
merge_request.target_branch)
|
merge_request.target_branch)
|
||||||
end
|
end
|
||||||
|
|
||||||
if merge_request.previous_changes.include?('milestone_id')
|
|
||||||
create_milestone_note(merge_request)
|
|
||||||
end
|
|
||||||
|
|
||||||
if merge_request.previous_changes.include?('assignee_id')
|
if merge_request.previous_changes.include?('assignee_id')
|
||||||
create_assignee_note(merge_request)
|
create_assignee_note(merge_request)
|
||||||
notification_service.reassigned_merge_request(merge_request, current_user)
|
notification_service.reassigned_merge_request(merge_request, current_user)
|
||||||
|
@ -111,5 +107,11 @@ module MergeRequests
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
|
||||||
|
SystemNoteService.change_branch(
|
||||||
|
issuable, issuable.project, current_user, branch_type,
|
||||||
|
old_branch, new_branch)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,7 @@ class MetricsService
|
||||||
Gitlab::HealthChecks::Redis::RedisCheck,
|
Gitlab::HealthChecks::Redis::RedisCheck,
|
||||||
Gitlab::HealthChecks::Redis::CacheCheck,
|
Gitlab::HealthChecks::Redis::CacheCheck,
|
||||||
Gitlab::HealthChecks::Redis::QueuesCheck,
|
Gitlab::HealthChecks::Redis::QueuesCheck,
|
||||||
Gitlab::HealthChecks::Redis::SharedStateCheck,
|
Gitlab::HealthChecks::Redis::SharedStateCheck
|
||||||
Gitlab::HealthChecks::FsShardsCheck
|
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
def prometheus_metrics_text
|
def prometheus_metrics_text
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
%td
|
%td
|
||||||
= truncate(hook_log.url, length: 50)
|
= truncate(hook_log.url, length: 50)
|
||||||
%td.light
|
%td.light
|
||||||
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
|
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
|
||||||
%td.light
|
%td.light
|
||||||
= time_ago_with_tooltip(hook_log.created_at)
|
= time_ago_with_tooltip(hook_log.created_at)
|
||||||
%td
|
%td
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
= hidden_field_tag :namespace_id, params[:namespace_id]
|
= hidden_field_tag :namespace_id, params[:namespace_id]
|
||||||
- namespace = Namespace.find(params[:namespace_id])
|
- namespace = Namespace.find(params[:namespace_id])
|
||||||
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
|
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
|
||||||
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
|
= dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' })
|
||||||
.dropdown-menu.dropdown-select.dropdown-menu-align-right
|
.dropdown-menu.dropdown-select.dropdown-menu-align-right
|
||||||
= dropdown_title('Namespaces')
|
= dropdown_title('Namespaces')
|
||||||
= dropdown_filter("Search for Namespace")
|
= dropdown_filter("Search for Namespace")
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
= f.label :new_namespace_id, "Namespace", class: 'control-label'
|
= f.label :new_namespace_id, "Namespace", class: 'control-label'
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
.dropdown
|
.dropdown
|
||||||
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' })
|
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
|
||||||
.dropdown-menu.dropdown-select
|
.dropdown-menu.dropdown-select
|
||||||
= dropdown_title('Namespaces')
|
= dropdown_title('Namespaces')
|
||||||
= dropdown_filter("Search for Namespace")
|
= dropdown_filter("Search for Namespace")
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
- if link && status.has_details?
|
- if link && status.has_details?
|
||||||
= link_to status.details_path, class: css_classes, title: title do
|
= link_to status.details_path, class: css_classes, title: title do
|
||||||
= custom_icon(status.icon)
|
= sprite_icon(status.icon)
|
||||||
= status.text
|
= status.text
|
||||||
- else
|
- else
|
||||||
%span{ class: css_classes, title: title }
|
%span{ class: css_classes, title: title }
|
||||||
= custom_icon(status.icon)
|
= sprite_icon(status.icon)
|
||||||
= status.text
|
= status.text
|
||||||
|
|
|
@ -7,13 +7,13 @@
|
||||||
|
|
||||||
- if status.has_details?
|
- if status.has_details?
|
||||||
= link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
|
= link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
|
||||||
%span{ class: klass }= custom_icon(status.icon)
|
%span{ class: klass }= sprite_icon(status.icon)
|
||||||
%span.ci-build-text= subject.name
|
%span.ci-build-text= subject.name
|
||||||
- else
|
- else
|
||||||
.menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
|
.menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
|
||||||
%span{ class: klass }= custom_icon(status.icon)
|
%span{ class: klass }= sprite_icon(status.icon)
|
||||||
%span.ci-build-text= subject.name
|
%span.ci-build-text= subject.name
|
||||||
|
|
||||||
- if status.has_action?
|
- if status.has_action?
|
||||||
= link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
|
= link_to status.action_path, class: "ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
|
||||||
= custom_icon(status.action_icon)
|
= sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}")
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
|
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
|
||||||
= link_to todos_filter_path(state: 'pending') do
|
= link_to todos_filter_path(state: 'pending') do
|
||||||
%span
|
%span
|
||||||
To do
|
Todos
|
||||||
%span.badge
|
%span.badge
|
||||||
= number_with_delimiter(todos_pending_count)
|
= number_with_delimiter(todos_pending_count)
|
||||||
%li.todos-done{ class: active_when(params[:state] == 'done') }>
|
%li.todos-done{ class: active_when(params[:state] == 'done') }>
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
- name = label.parameterize
|
|
||||||
- attribute = name.underscore
|
|
||||||
|
|
||||||
.reset-action
|
|
||||||
%p.cgray
|
|
||||||
= label_tag name, label, class: "label-light"
|
|
||||||
= text_field_tag name, current_user.send(attribute), class: 'form-control', readonly: true, onclick: 'this.select()'
|
|
||||||
%p.help-block
|
|
||||||
= help_text
|
|
||||||
.prepend-top-default
|
|
||||||
= link_to button_label, [:reset, attribute, :profile], method: :put, data: { confirm: 'Are you sure?' }, class: 'btn btn-default private-token'
|
|
|
@ -6,22 +6,6 @@
|
||||||
.alert.alert-info
|
.alert.alert-info
|
||||||
Some options are unavailable for LDAP accounts
|
Some options are unavailable for LDAP accounts
|
||||||
|
|
||||||
.row.prepend-top-default
|
|
||||||
.col-lg-4.profile-settings-sidebar
|
|
||||||
%h4.prepend-top-0
|
|
||||||
Private Tokens
|
|
||||||
%p
|
|
||||||
Keep these tokens secret, anyone with access to them can interact with
|
|
||||||
GitLab as if they were you.
|
|
||||||
.col-lg-8.private-tokens-reset
|
|
||||||
= render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' }
|
|
||||||
|
|
||||||
= render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' }
|
|
||||||
|
|
||||||
- if incoming_email_token_enabled?
|
|
||||||
= render partial: 'reset_token', locals: { label: 'Incoming email token', button_label: 'Reset incoming email token', help_text: 'Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.' }
|
|
||||||
|
|
||||||
%hr
|
|
||||||
.row.prepend-top-default
|
.row.prepend-top-default
|
||||||
.col-lg-4.profile-settings-sidebar
|
.col-lg-4.profile-settings-sidebar
|
||||||
%h4.prepend-top-0
|
%h4.prepend-top-0
|
||||||
|
|
|
@ -30,3 +30,40 @@
|
||||||
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
|
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
|
||||||
|
|
||||||
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
|
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
|
||||||
|
|
||||||
|
%hr
|
||||||
|
.row.prepend-top-default
|
||||||
|
.col-lg-4.profile-settings-sidebar
|
||||||
|
%h4.prepend-top-0
|
||||||
|
RSS token
|
||||||
|
%p
|
||||||
|
Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs.
|
||||||
|
%p
|
||||||
|
It cannot be used to access any other data.
|
||||||
|
.col-lg-8.rss-token-reset
|
||||||
|
= label_tag :rss_token, 'RSS token', class: "label-light"
|
||||||
|
= text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()'
|
||||||
|
%p.help-block
|
||||||
|
Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you.
|
||||||
|
You should
|
||||||
|
= link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' }
|
||||||
|
if that ever happens.
|
||||||
|
|
||||||
|
- if incoming_email_token_enabled?
|
||||||
|
%hr
|
||||||
|
.row.prepend-top-default
|
||||||
|
.col-lg-4.profile-settings-sidebar
|
||||||
|
%h4.prepend-top-0
|
||||||
|
Incoming email token
|
||||||
|
%p
|
||||||
|
Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses.
|
||||||
|
%p
|
||||||
|
It cannot be used to access any other data.
|
||||||
|
.col-lg-8.incoming-email-token-reset
|
||||||
|
= label_tag :incoming_email_token, 'Incoming email token', class: "label-light"
|
||||||
|
= text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()'
|
||||||
|
%p.help-block
|
||||||
|
Keep this token secret. Anyone who gets ahold of it can create issues as if they were you.
|
||||||
|
You should
|
||||||
|
= link_to 'reset it', [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: 'Are you sure? Any issue email addresses currently in use will stop working.' }
|
||||||
|
if that ever happens.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
- project = local_assigns.fetch(:project)
|
- project = local_assigns.fetch(:project)
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Export project
|
Export project
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
|
Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
.bs-callout.bs-callout-info
|
.bs-callout.bs-callout-info
|
||||||
%p.append-bottom-0
|
%p.append-bottom-0
|
||||||
%p
|
%p
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Deploy Keys
|
Deploy Keys
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
|
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
%h5.prepend-top-0
|
%h5.prepend-top-0
|
||||||
Create a new deploy key for this project
|
Create a new deploy key for this project
|
||||||
= render @deploy_keys.form_partial_path
|
= render @deploy_keys.form_partial_path
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
.project-edit-container
|
.project-edit-container
|
||||||
%section.settings.general-settings
|
%section.settings.general-settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
General project settings
|
General project settings
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Update your project name, description, avatar, and other general settings.
|
Update your project name, description, avatar, and other general settings.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
.project-edit-errors
|
.project-edit-errors
|
||||||
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
|
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
|
||||||
%fieldset
|
%fieldset
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
= link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
|
= link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
|
||||||
= f.submit 'Save changes', class: "btn btn-save"
|
= f.submit 'Save changes', class: "btn btn-save"
|
||||||
|
|
||||||
%section.settings.sharing-permissions
|
%section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Permissions
|
Permissions
|
||||||
|
@ -69,13 +69,13 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Enable or disable certain project features and choose access levels.
|
Enable or disable certain project features and choose access levels.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
|
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
|
||||||
%script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project)
|
%script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project)
|
||||||
.js-project-permissions-form
|
.js-project-permissions-form
|
||||||
= f.submit 'Save changes', class: "btn btn-save"
|
= f.submit 'Save changes', class: "btn btn-save"
|
||||||
|
|
||||||
%section.settings.merge-requests-feature{ class: ("hidden" if @project.project_feature.send(:merge_requests_access_level) == 0) }
|
%section.settings.merge-requests-feature.no-animate{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Merge request settings
|
Merge request settings
|
||||||
|
@ -83,14 +83,14 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Customize your merge request restrictions.
|
Customize your merge request restrictions.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
|
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
|
||||||
= render 'merge_request_settings', form: f
|
= render 'merge_request_settings', form: f
|
||||||
= f.submit 'Save changes', class: "btn btn-save"
|
= f.submit 'Save changes', class: "btn btn-save"
|
||||||
|
|
||||||
= render 'export', project: @project
|
= render 'export', project: @project
|
||||||
|
|
||||||
%section.settings.advanced-settings
|
%section.settings.advanced-settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Advanced settings
|
Advanced settings
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project.
|
Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
.sub-section
|
.sub-section
|
||||||
%h4 Housekeeping
|
%h4 Housekeeping
|
||||||
%p
|
%p
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- @no_container = true
|
- @no_container = true
|
||||||
- page_title "Contributors"
|
- page_title _('Contributors')
|
||||||
- content_for :page_specific_javascripts do
|
- content_for :page_specific_javascripts do
|
||||||
= webpack_bundle_tag('common_d3')
|
= webpack_bundle_tag('common_d3')
|
||||||
= webpack_bundle_tag('graphs')
|
= webpack_bundle_tag('graphs')
|
||||||
|
@ -7,23 +7,23 @@
|
||||||
|
|
||||||
.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
|
.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
|
||||||
.sub-header-block
|
.sub-header-block
|
||||||
.tree-ref-holder
|
.tree-ref-holder.inline.vertical-align-middle
|
||||||
= render 'shared/ref_switcher', destination: 'graphs'
|
= render 'shared/ref_switcher', destination: 'graphs'
|
||||||
%ul.breadcrumb.repo-breadcrumb
|
= link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn'
|
||||||
= commits_breadcrumbs
|
|
||||||
|
|
||||||
.loading-graph
|
.loading-graph
|
||||||
.center
|
.center
|
||||||
%h3.page-title
|
%h3.page-title
|
||||||
%i.fa.fa-spinner.fa-spin
|
%i.fa.fa-spinner.fa-spin
|
||||||
Building repository graph.
|
= s_('ContributorsPage|Building repository graph.')
|
||||||
%p.slead Please wait a moment, this page will automatically refresh when ready.
|
%p.slead
|
||||||
|
= s_('ContributorsPage|Please wait a moment, this page will automatically refresh when ready.')
|
||||||
|
|
||||||
.stat-graph.hide
|
.stat-graph.hide
|
||||||
.header.clearfix
|
.header.clearfix
|
||||||
%h3#date_header.page-title
|
%h3#date_header.page-title
|
||||||
%p.light
|
%p.light
|
||||||
Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
|
= s_('ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits.') % { branch_name: @ref }
|
||||||
%input#brush_change{ :type => "hidden" }
|
%input#brush_change{ :type => "hidden" }
|
||||||
.graphs.row
|
.graphs.row
|
||||||
#contributors-master
|
#contributors-master
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
%td
|
%td
|
||||||
= truncate(hook_log.url, length: 50)
|
= truncate(hook_log.url, length: 50)
|
||||||
%td.light
|
%td.light
|
||||||
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
|
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
|
||||||
%td.light
|
%td.light
|
||||||
= time_ago_with_tooltip(hook_log.created_at)
|
= time_ago_with_tooltip(hook_log.created_at)
|
||||||
%td
|
%td
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
- builds.select{|build| build.status == build_status}.each do |build|
|
- builds.select{|build| build.status == build_status}.each do |build|
|
||||||
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
|
||||||
= link_to project_job_path(@project, build) do
|
= link_to project_job_path(@project, build) do
|
||||||
= icon('arrow-right')
|
= sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
|
||||||
%span{ class: "ci-status-icon-#{build.status}" }
|
%span{ class: "ci-status-icon-#{build.status}" }
|
||||||
= ci_icon_for_status(build.status)
|
= ci_icon_for_status(build.status)
|
||||||
%span
|
%span
|
||||||
|
@ -100,4 +100,5 @@
|
||||||
- else
|
- else
|
||||||
= build.id
|
= build.id
|
||||||
- if build.retried?
|
- if build.retried?
|
||||||
%i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
|
%span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
|
||||||
|
= sprite_icon('retry', size:16, css_class: 'icon-retry')
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
|
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
|
||||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
|
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
|
||||||
%li{ class: merge_request_button_visibility(@merge_request, false) }
|
%li{ class: merge_request_button_visibility(@merge_request, false) }
|
||||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
|
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
|
||||||
|
|
||||||
- if can_update_merge_request
|
- if can_update_merge_request
|
||||||
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit"
|
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Protected Branches
|
Protected Branches
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Keep stable branches secure and force developers to use merge requests.
|
Keep stable branches secure and force developers to use merge requests.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
%p
|
%p
|
||||||
By default, protected branches are designed to:
|
By default, protected branches are designed to:
|
||||||
%ul
|
%ul
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Protected Tags
|
Protected Tags
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Limit access to creating and updating tags.
|
Limit access to creating and updating tags.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
%p
|
%p
|
||||||
By default, protected tags are designed to:
|
By default, protected tags are designed to:
|
||||||
%ul
|
%ul
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
- expanded = Rails.env.test?
|
- expanded = Rails.env.test?
|
||||||
|
|
||||||
%section.settings#js-general-pipeline-settings
|
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
General pipelines settings
|
General pipelines settings
|
||||||
|
@ -12,10 +12,10 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Update your CI/CD configuration, like job timeout or Auto DevOps.
|
Update your CI/CD configuration, like job timeout or Auto DevOps.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
= render 'projects/pipelines_settings/show'
|
= render 'projects/pipelines_settings/show'
|
||||||
|
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Runners settings
|
Runners settings
|
||||||
|
@ -23,10 +23,10 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
Register and see your runners for this project.
|
Register and see your runners for this project.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
= render 'projects/runners/index'
|
= render 'projects/runners/index'
|
||||||
|
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Secret variables
|
Secret variables
|
||||||
|
@ -35,10 +35,10 @@
|
||||||
= expanded ? 'Collapse' : 'Expand'
|
= expanded ? 'Collapse' : 'Expand'
|
||||||
%p
|
%p
|
||||||
= render "ci/variables/content"
|
= render "ci/variables/content"
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
= render 'ci/variables/index'
|
= render 'ci/variables/index'
|
||||||
|
|
||||||
%section.settings
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4
|
||||||
Pipeline triggers
|
Pipeline triggers
|
||||||
|
@ -48,5 +48,5 @@
|
||||||
Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will
|
Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will
|
||||||
impersonate their associated user including their access to projects and their project
|
impersonate their associated user including their access to projects and their project
|
||||||
permissions.
|
permissions.
|
||||||
.settings-content.no-animate{ class: ('expanded' if expanded) }
|
.settings-content
|
||||||
= render 'projects/triggers/index'
|
= render 'projects/triggers/index'
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
.stage-container.dropdown{ class: klass }
|
.stage-container.dropdown{ class: klass }
|
||||||
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
|
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
|
||||||
= custom_icon(icon_status)
|
= sprite_icon(icon_status)
|
||||||
= icon('caret-down')
|
= icon('caret-down')
|
||||||
|
|
||||||
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
|
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
= hook_log.trigger.singularize.titleize
|
= hook_log.trigger.singularize.titleize
|
||||||
%p
|
%p
|
||||||
%strong Elapsed time:
|
%strong Elapsed time:
|
||||||
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
|
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
|
||||||
%p
|
%p
|
||||||
%strong Request time:
|
%strong Request time:
|
||||||
= time_ago_with_tooltip(hook_log.created_at)
|
= time_ago_with_tooltip(hook_log.created_at)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Tighten up whitelisting of certain Geo routes
|
||||||
|
merge_request: 15082
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add a latest_merge_request_diff_id column to merge_requests
|
||||||
|
merge_request: 15035
|
||||||
|
author:
|
||||||
|
type: performance
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Todos spelled correctly on Todos list page
|
||||||
|
merge_request: 15015
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix webhooks recent deliveries
|
||||||
|
merge_request: 15146
|
||||||
|
author: Alexander Randa (@randaalex)
|
||||||
|
type: fixed
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: Add sudo scope for OAuth and Personal Access Tokens to be used by admins to
|
||||||
|
impersonate other users on the API
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Convert private tokens to Personal Access Tokens with sudo scope
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: security
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove private tokens from web interface and API
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: security
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove Session API now that private tokens are removed from user API endpoints
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: removed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix cancel button not working while uploading on the new issue page
|
||||||
|
merge_request: 15137
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove Filesystem check metrics that use too much CPU to handle requests
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: performance
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Make NamespaceSelect change URL when filtering
|
||||||
|
merge_request: 14888
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Make contributors page translatable
|
||||||
|
merge_request: 14915
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -58,9 +58,10 @@ en:
|
||||||
expired: "The access token expired"
|
expired: "The access token expired"
|
||||||
unknown: "The access token is invalid"
|
unknown: "The access token is invalid"
|
||||||
scopes:
|
scopes:
|
||||||
api: Access your API
|
api: Access the authenticated user's API
|
||||||
read_user: Read user information
|
read_user: Read the authenticated user's personal information
|
||||||
openid: Authenticate using OpenID Connect
|
openid: Authenticate using OpenID Connect
|
||||||
|
sudo: Perform API actions as any user in the system (if the authenticated user is an admin)
|
||||||
|
|
||||||
flash:
|
flash:
|
||||||
applications:
|
applications:
|
||||||
|
|
|
@ -6,7 +6,6 @@ resource :profile, only: [:show, :update] do
|
||||||
get :audit_log
|
get :audit_log
|
||||||
get :applications, to: 'oauth/applications#index'
|
get :applications, to: 'oauth/applications#index'
|
||||||
|
|
||||||
put :reset_private_token
|
|
||||||
put :reset_incoming_email_token
|
put :reset_incoming_email_token
|
||||||
put :reset_rss_token
|
put :reset_rss_token
|
||||||
put :update_username
|
put :update_username
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||||
|
# for more information on how to write migrations for GitLab.
|
||||||
|
|
||||||
|
class MigrateUserAuthenticationTokenToPersonalAccessToken < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
# Set this constant to true if this migration requires downtime.
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
# disable_ddl_transaction!
|
||||||
|
|
||||||
|
TOKEN_NAME = 'Private Token'.freeze
|
||||||
|
|
||||||
|
def up
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO personal_access_tokens (user_id, token, name, created_at, updated_at, scopes)
|
||||||
|
SELECT id, authentication_token, '#{TOKEN_NAME}', NOW(), NOW(), '#{%w[api].to_yaml}'
|
||||||
|
FROM users
|
||||||
|
WHERE authentication_token IS NOT NULL
|
||||||
|
AND admin = FALSE
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT true
|
||||||
|
FROM personal_access_tokens
|
||||||
|
WHERE user_id = users.id
|
||||||
|
AND token = users.authentication_token
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# Admins also need the `sudo` scope
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO personal_access_tokens (user_id, token, name, created_at, updated_at, scopes)
|
||||||
|
SELECT id, authentication_token, '#{TOKEN_NAME}', NOW(), NOW(), '#{%w[api sudo].to_yaml}'
|
||||||
|
FROM users
|
||||||
|
WHERE authentication_token IS NOT NULL
|
||||||
|
AND admin = TRUE
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT true
|
||||||
|
FROM personal_access_tokens
|
||||||
|
WHERE user_id = users.id
|
||||||
|
AND token = users.authentication_token
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
if Gitlab::Database.postgresql?
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE users
|
||||||
|
SET authentication_token = pats.token
|
||||||
|
FROM (
|
||||||
|
SELECT user_id, token
|
||||||
|
FROM personal_access_tokens
|
||||||
|
WHERE name = '#{TOKEN_NAME}'
|
||||||
|
) AS pats
|
||||||
|
WHERE id = pats.user_id
|
||||||
|
SQL
|
||||||
|
else
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE users
|
||||||
|
INNER JOIN personal_access_tokens AS pats
|
||||||
|
ON users.id = pats.user_id
|
||||||
|
SET authentication_token = pats.token
|
||||||
|
WHERE pats.name = '#{TOKEN_NAME}'
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
DELETE FROM personal_access_tokens
|
||||||
|
WHERE name = '#{TOKEN_NAME}'
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT true
|
||||||
|
FROM users
|
||||||
|
WHERE id = personal_access_tokens.user_id
|
||||||
|
AND authentication_token = personal_access_tokens.token
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue