Merge branch 'master' into fix/sm/31771-do-not-allow-jobs-to-be-erased-new

This commit is contained in:
Shinya Maeda 2017-11-07 22:12:19 +09:00
commit d89c18901b
389 changed files with 11673 additions and 4536 deletions

View File

@ -14,7 +14,7 @@ linters:
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
enabled: true
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).

View File

@ -1 +1 @@
{"iconCount":164,"spriteSize":72823,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","dashboard","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
{"iconCount":173,"spriteSize":75815,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="492.509" height="453.68" viewBox="0 0 492.50943 453.67966"><g fill="none" fill-rule="evenodd"><path d="M491.589 259.398l-27.559-84.814L409.413 6.486c-2.81-8.648-15.045-8.648-17.856 0l-54.619 168.098H155.572L100.952 6.486c-2.81-8.648-15.046-8.648-17.856 0L28.478 174.584.921 259.398a18.775 18.775 0 0 0 6.82 20.992l238.513 173.29L484.77 280.39a18.777 18.777 0 0 0 6.82-20.992" fill="#fc6d26"/><path d="M246.255 453.68l90.684-279.096H155.57z" fill="#e24329"/><path d="M246.255 453.68L155.57 174.583H28.479z" fill="#fc6d26"/><path d="M28.479 174.584L.92 259.4a18.773 18.773 0 0 0 6.821 20.99l238.514 173.29z" fill="#fca326"/><path d="M28.479 174.584H155.57L100.952 6.487c-2.81-8.65-15.047-8.65-17.856 0z" fill="#e24329"/><path d="M246.255 453.68l90.684-279.096H464.03z" fill="#fc6d26"/><path d="M464.03 174.584l27.56 84.815a18.773 18.773 0 0 1-6.822 20.99L246.255 453.68z" fill="#fca326"/><path d="M464.03 174.584H336.94L391.557 6.487c2.811-8.65 15.047-8.65 17.856 0z" fill="#e24329"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="220" viewBox="0 0 430 220"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M189.8 182l2.4-12H114c-5.523 0-10-4.477-10-10V34c0-5.523 4.477-10 10-10h200c5.523 0 10 4.477 10 10v126c0 5.523-4.477 10-10 10h-78.2l2.4 12h22.52a9.651 9.651 0 0 1 9.28 7 5.491 5.491 0 0 1-5.28 7H164.159a5.787 5.787 0 0 1-5.659-7 8.855 8.855 0 0 1 8.659-7H189.8zM114 28a6 6 0 0 0-6 6v126a6 6 0 0 0 6 6h200a6 6 0 0 0 6-6V34a6 6 0 0 0-6-6H114zm5 6h190a5 5 0 0 1 5 5v116a5 5 0 0 1-5 5H119a5 5 0 0 1-5-5V39a5 5 0 0 1 5-5zm0 4a1 1 0 0 0-1 1v116a1 1 0 0 0 1 1h190a1 1 0 0 0 1-1V39a1 1 0 0 0-1-1H119zm112.72 132h-35.44l-2.4 12h40.24l-2.4-12zm-64.561 16c-2.29 0-4.268 1.6-4.748 3.838A1.787 1.787 0 0 0 164.16 192h100.56a1.491 1.491 0 0 0 1.435-1.901A5.651 5.651 0 0 0 260.72 186h-93.561z"/><path fill="#FEF0E8" d="M177.965 99H194a2 2 0 1 1 0 4h-16.322c-1.374 6.29-6.976 11-13.678 11-6.702 0-12.304-4.71-13.678-11h-3.365l-7.395 9.249a2 2 0 0 1-3.049.089L128.11 103h-5.844a2 2 0 1 1 0-4H129a2 2 0 0 1 1.487.662l7.423 8.248 6.523-8.159a2 2 0 0 1 1.562-.751h4.04c.513-7.265 6.57-13 13.965-13 7.396 0 13.452 5.735 13.965 13zM164 110c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z"/><path fill="#EFEDF8" d="M273.847 103c-.962 6.23-6.347 11-12.847 11-6.5 0-11.885-4.77-12.847-11H232a2 2 0 0 1 0-4h16.153c.962-6.23 6.347-11 12.847-11 6.5 0 11.885 4.77 12.847 11h3.998l8.404-9.338a2 2 0 0 1 3.048.09L296.692 99H305a2 2 0 0 1 0 4h-9.27a2 2 0 0 1-1.562-.751l-6.523-8.16-7.423 8.249a2 2 0 0 1-1.487.662h-4.888zM261 110a9 9 0 1 0 0-18 9 9 0 0 0 0 18z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M213 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15z"/><path fill="#FC6D26" d="M211.586 101.828L208.757 99a2 2 0 1 0-2.828 2.828l4.243 4.243c.39.39.902.586 1.414.586.512 0 1.023-.195 1.414-.586L220.071 99a2 2 0 1 0-2.828-2.828l-5.657 5.656z"/><path fill="#FDC4A8" d="M162.95 101.07l-1.768-1.767a1.5 1.5 0 0 0-2.121 2.121l2.828 2.829c.293.293.677.439 1.06.439.385 0 .769-.146 1.062-.44l4.242-4.242a1.5 1.5 0 1 0-2.121-2.121l-3.182 3.182z"/><path fill="#6B4FBB" d="M256.39 104.841A6 6 0 1 0 261 95v6l-4.61 3.841z"/><path fill="#FEF0E8" fill-rule="nonzero" d="M99 99h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-14.384-.078l-3.643-3.425a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-11.657-10.96l-3.642-3.425a2 2 0 1 0-2.74 2.914l3.642 3.425a2 2 0 0 0 2.74-2.914zm-11.656-10.96l-3.643-3.425a2 2 0 0 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-14.367-3.885l-3.593 3.477a2 2 0 0 0 2.782 2.875l3.593-3.477a2 2 0 0 0-2.782-2.875zM19.44 84.244l-3.593 3.477a2 2 0 1 0 2.781 2.874l3.593-3.477a2 2 0 0 0-2.781-2.874zM7.94 95.371l-3.593 3.477a2 2 0 1 0 2.782 2.874l3.593-3.477a2 2 0 1 0-2.782-2.874z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M423.611 99.56l-3.598 3.472a2 2 0 0 0 2.777 2.879l3.599-3.472a2 2 0 0 0-2.778-2.878zm-11.514 11.11l-3.598 3.472a2 2 0 0 0 2.777 2.878l3.598-3.471a2 2 0 0 0-2.777-2.879zm-11.514 11.11l-3.599 3.471a2 2 0 1 0 2.778 2.879l3.598-3.472a2 2 0 1 0-2.777-2.879zm-8.799 4.48l-3.642-3.426a2 2 0 0 0-2.74 2.915l3.642 3.425a2 2 0 0 0 2.74-2.915zm-11.656-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.426a2 2 0 1 0 2.74-2.915zm-11.657-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zM353.001 99h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4z"/></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 121.94154 121.84154" width="121.942" height="121.842"><style id="style200">.st0{fill:#ecb32d}.st1{fill:#63c1a0}.st2{fill:#e01a59}.st3{fill:#331433}.st4{fill:#d62027}.st5{fill:#89d3df}.st6{fill:#258b74}.st7{fill:#819c3c}</style><path class="st0" d="M79.03 7.511c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path202" fill="#ecb32d"/><path class="st1" d="M35.53 21.611c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path204" fill="#63c1a0"/><path class="st2" d="M114.43 79.011c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.5 28.2c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.5-28.1 86.5-28.1z" id="path206" fill="#e01a59"/><path class="st3" d="M39.23 103.511c5.6-1.8 12.9-4.2 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path208" fill="#331433"/><path class="st4" d="M82.83 89.311c7.8-2.5 15.1-4.9 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path210" fill="#d62027"/><path class="st5" d="M100.23 35.511c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.4 28.1c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.4-28 86.4-28z" id="path212" fill="#89d3df"/><path class="st6" d="M25.13 59.911c5.6-1.8 12.9-4.2 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path214" fill="#258b74"/><path class="st7" d="M68.63 45.811c7.8-2.5 15.1-4.9 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path216" fill="#819c3c"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -1,7 +1,8 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import _ from 'underscore';
import { insertText, getSelectedFragment, nodeMatchesSelector } from './lib/utils/common_utils';
import { placeholderImage } from './lazy_loader';
import { insertText, getSelectedFragment, nodeMatchesSelector } from '../lib/utils/common_utils';
import { placeholderImage } from '../lazy_loader';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
@ -284,7 +285,7 @@ const gfmRules = {
},
};
class CopyAsGFM {
export class CopyAsGFM {
constructor() {
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
@ -469,7 +470,12 @@ class CopyAsGFM {
}
}
window.gl = window.gl || {};
window.gl.CopyAsGFM = CopyAsGFM;
// Export CopyAsGFM as a global for rspec to access
// see /spec/features/copy_as_gfm_spec.rb
if (process.env.NODE_ENV !== 'production') {
window.CopyAsGFM = CopyAsGFM;
}
new CopyAsGFM();
export default function initCopyAsGFM() {
return new CopyAsGFM();
}

View File

@ -1,5 +1,6 @@
import './autosize';
import './bind_in_out';
import initCopyAsGFM from './copy_as_gfm';
import './details_behavior';
import installGlEmojiElement from './gl_emoji';
import './quick_submit';
@ -7,3 +8,4 @@ import './requires_input';
import './toggler_behavior';
installGlEmojiElement();
initCopyAsGFM();

View File

@ -11,7 +11,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
// Issue boards is slightly different, we handle all the requests async
// instead or reloading the page, we just re-fire the list ajax requests
this.isHandledAsync = true;
this.cantEdit = cantEdit;
this.cantEdit = cantEdit.filter(i => typeof i === 'string');
this.cantEditWithValue = cantEdit.filter(i => typeof i === 'object');
}
updateObject(path) {
@ -42,7 +43,9 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
this.filteredSearchInput.dispatchEvent(new Event('input'));
}
canEdit(tokenName) {
return this.cantEdit.indexOf(tokenName) === -1;
canEdit(tokenName, tokenValue) {
if (this.cantEdit.includes(tokenName)) return false;
return this.cantEditWithValue.findIndex(token => token.name === tokenName &&
token.value === tokenValue) === -1;
}
}

View File

@ -14,16 +14,18 @@ gl.issueBoards.BoardsStore = {
},
state: {},
detail: {
issue: {}
issue: {},
},
moving: {
issue: {},
list: {}
list: {},
},
create () {
this.state.lists = [];
this.filter.path = getUrlParamsArray().join('&');
this.detail = { issue: {} };
this.detail = {
issue: {},
};
},
addList (listObj, defaultAvatar) {
const list = new List(listObj, defaultAvatar);

View File

@ -147,6 +147,16 @@ class DropdownUtils {
return dataValue !== null;
}
static getVisualTokenValues(visualToken) {
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
if (tokenName === 'label' && tokenValue) {
// remove leading symbol and wrapping quotes
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
}
return { tokenName, tokenValue };
}
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
const container = FilteredSearchContainer.container;

View File

@ -185,8 +185,8 @@ class FilteredSearchManager {
if (e.keyCode === 8 || e.keyCode === 46) {
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim();
const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName);
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
@ -336,8 +336,8 @@ class FilteredSearchManager {
let canClearToken = t.classList.contains('js-visual-token');
if (canClearToken) {
const tokenKey = t.querySelector('.name').textContent.trim();
canClearToken = this.canEdit && this.canEdit(tokenKey);
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(t);
canClearToken = this.canEdit && this.canEdit(tokenName, tokenValue);
}
if (canClearToken) {
@ -469,7 +469,7 @@ class FilteredSearchManager {
}
hasFilteredSearch = true;
const canEdit = this.canEdit && this.canEdit(sanitizedKey);
const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
gl.FilteredSearchVisualTokens.addFilterVisualToken(
sanitizedKey,
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,

View File

@ -38,21 +38,14 @@ class FilteredSearchVisualTokens {
}
static createVisualTokenElementHTML(canEdit = true) {
let removeTokenMarkup = '';
if (canEdit) {
removeTokenMarkup = `
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
`;
}
return `
<div class="selectable" role="button">
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
<div class="name"></div>
<div class="value-container">
<div class="value"></div>
${removeTokenMarkup}
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
</div>
</div>
`;

View File

@ -7,7 +7,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
export default class LabelsSelect {
constructor(els) {
constructor(els, options = {}) {
var _this, $els;
_this = this;
@ -57,6 +57,7 @@ export default class LabelsSelect {
labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
labelNoneHTMLTemplate = '<span class="no-value">None</span>';
}
const handleClick = options.handleClick;
$sidebarLabelTooltip.tooltip();
@ -390,6 +391,10 @@ export default class LabelsSelect {
.then(fadeOutLoader)
.catch(fadeOutLoader);
}
else if (handleClick) {
e.preventDefault();
handleClick(label);
}
else {
if ($dropdown.hasClass('js-multiselect')) {

View File

@ -46,7 +46,6 @@ import './commits';
import './compare';
import './compare_autocomplete';
import './confirm_danger_modal';
import './copy_as_gfm';
import './copy_to_clipboard';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';

View File

@ -5,7 +5,7 @@ import _ from 'underscore';
(function() {
this.MilestoneSelect = (function() {
function MilestoneSelect(currentProject, els) {
function MilestoneSelect(currentProject, els, options = {}) {
var _this, $els;
if (currentProject != null) {
_this = this;
@ -136,19 +136,26 @@ import _ from 'underscore';
},
opened: function(e) {
const $el = $(e.currentTarget);
if ($dropdown.hasClass('js-issue-board-sidebar')) {
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
}
$('a.is-active', $el).removeClass('is-active');
$(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(options) {
const { $el, e } = options;
let selected = options.selectedObj;
clicked: function(clickEvent) {
const { $el, e } = clickEvent;
let selected = clickEvent.selectedObj;
var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
if (!selected) return;
if (options.handleClick) {
e.preventDefault();
options.handleClick(selected);
return;
}
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index');

View File

@ -138,7 +138,7 @@
renderAxesPaths() {
this.timeSeries = createTimeSeries(
this.graphData.queries[0],
this.graphData.queries,
this.graphWidth,
this.graphHeight,
this.graphHeightOffset,
@ -153,8 +153,9 @@
const axisYScale = d3.scale.linear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
axisXScale.domain(d3.extent(this.timeSeries[0].values, d => d.time));
axisYScale.domain([0, d3.max(this.timeSeries[0].values.map(d => d.value))]);
const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
axisXScale.domain(d3.extent(allValues, d => d.time));
axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
const xAxis = d3.svg.axis()
.scale(axisXScale)
@ -246,6 +247,7 @@
:key="index"
:generated-line-path="path.linePath"
:generated-area-path="path.areaPath"
:line-style="path.lineStyle"
:line-color="path.lineColor"
:area-color="path.areaColor"
/>

View File

@ -79,7 +79,8 @@
},
formatMetricUsage(series) {
const value = series.values[this.currentDataIndex].value;
const value = series.values[this.currentDataIndex] &&
series.values[this.currentDataIndex].value;
if (isNaN(value)) {
return '-';
}
@ -92,6 +93,12 @@
}
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
},
strokeDashArray(type) {
if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3';
return null;
},
},
mounted() {
this.$nextTick(() => {
@ -162,13 +169,15 @@
v-for="(series, index) in timeSeries"
:key="index"
:transform="translateLegendGroup(index)">
<rect
:fill="series.areaColor"
:width="measurements.legends.width"
:height="measurements.legends.height"
x="20"
:y="graphHeight - measurements.legendOffset">
</rect>
<line
:stroke="series.lineColor"
:stroke-width="measurements.legends.height"
:stroke-dasharray="strokeDashArray(series.lineStyle)"
:x1="measurements.legends.offsetX"
:x2="measurements.legends.offsetX + measurements.legends.width"
:y1="graphHeight - measurements.legends.offsetY"
:y2="graphHeight - measurements.legends.offsetY">
</line>
<text
v-if="timeSeries.length > 1"
class="legend-metric-title"

View File

@ -9,6 +9,10 @@
type: String,
required: true,
},
lineStyle: {
type: String,
required: false,
},
lineColor: {
type: String,
required: true,
@ -18,6 +22,13 @@
required: true,
},
},
computed: {
strokeDashArray() {
if (this.lineStyle === 'dashed') return '3, 1';
if (this.lineStyle === 'dotted') return '1, 1';
return null;
},
},
};
</script>
<template>
@ -34,6 +45,7 @@
:stroke="lineColor"
fill="none"
stroke-width="1"
:stroke-dasharray="strokeDashArray"
transform="translate(-5, 20)">
</path>
</g>

View File

@ -7,15 +7,16 @@ export default {
left: 40,
},
legends: {
width: 10,
width: 15,
height: 3,
offsetX: 20,
offsetY: 32,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
legendOffset: 33,
},
large: { // This covers both md and lg screen sizes
margin: {
@ -27,13 +28,14 @@ export default {
legends: {
width: 15,
height: 3,
offsetX: 20,
offsetY: 34,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
legendOffset: 36,
},
xTicks: 8,
yTicks: 3,

View File

@ -11,7 +11,9 @@ const defaultColorPalette = {
const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
export default function createTimeSeries(queryData, graphWidth, graphHeight, graphHeightOffset) {
const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) {
let usedColors = [];
function pickColor(name) {
@ -31,17 +33,7 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
return defaultColorPalette[pick];
}
const maxValues = queryData.result.map((timeSeries, index) => {
const maxValue = d3.max(timeSeries.values.map(d => d.value));
return {
maxValue,
index,
};
});
const maxValueFromSeries = _.max(maxValues, val => val.maxValue);
return queryData.result.map((timeSeries, timeSeriesNumber) => {
return query.result.map((timeSeries, timeSeriesNumber) => {
let metricTag = '';
let lineColor = '';
let areaColor = '';
@ -52,9 +44,9 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
const timeSeriesScaleY = d3.scale.linear()
.range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
timeSeriesScaleX.domain(xDom);
timeSeriesScaleX.ticks(d3.time.minute, 60);
timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
timeSeriesScaleY.domain(yDom);
const defined = d => !isNaN(d.value) && d.value != null;
@ -72,10 +64,10 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
.y1(d => timeSeriesScaleY(d.value));
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
const seriesCustomizationData = queryData.series != null &&
_.findWhere(queryData.series[0].when,
{ value: timeSeriesMetricLabel });
if (seriesCustomizationData != null) {
const seriesCustomizationData = query.series != null &&
_.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
if (seriesCustomizationData) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
} else {
@ -83,14 +75,35 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
[lineColor, areaColor] = pickColor();
}
if (query.track) {
metricTag += ` - ${query.track}`;
}
return {
linePath: lineFunction(timeSeries.values),
areaPath: areaFunction(timeSeries.values),
timeSeriesScaleX,
values: timeSeries.values,
lineStyle,
lineColor,
areaColor,
metricTag,
};
});
}
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) {
const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat(
query.result.reduce((allResults, result) => allResults.concat(result.values), []),
), []);
const xDom = d3.extent(allValues, d => d.time);
const yDom = [0, d3.max(allValues.map(d => d.value))];
return queries.reduce((series, query, index) => {
const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length];
return series.concat(
queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle),
);
}, []);
}

View File

@ -162,13 +162,19 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
items = [
{
header: "" + name
}, {
}
];
const issueItems = [
{
text: 'Issues assigned to me',
url: issuesPath + "/?assignee_username=" + userName
}, {
text: "Issues I've created",
url: issuesPath + "/?author_username=" + userName
}, 'separator', {
}
];
const mergeRequestItems = [
{
text: 'Merge requests assigned to me',
url: mrPath + "/?assignee_username=" + userName
}, {
@ -176,6 +182,11 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
url: mrPath + "/?author_username=" + userName
}
];
if (options.issuesDisabled) {
items = items.concat(mergeRequestItems);
} else {
items = items.concat(...issueItems, 'separator', ...mergeRequestItems);
}
if (!name) {
items.splice(0, 1);
}
@ -408,6 +419,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
gl.projectOptions[projectPath] = {
name: $projectOptionsDataEl.data('name'),
issuesPath: $projectOptionsDataEl.data('issues-path'),
issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
mrPath: $projectOptionsDataEl.data('mr-path')
};
}

View File

@ -4,6 +4,7 @@
import _ from 'underscore';
import 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
export default class ShortcutsIssuable extends ShortcutsNavigation {
constructor(isMergeRequest) {
@ -33,8 +34,8 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
return false;
}
const el = window.gl.CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
const selected = window.gl.CopyAsGFM.nodeToGFM(el);
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
const selected = CopyAsGFM.nodeToGFM(el);
if (selected.trim() === '') {
return false;

View File

@ -6,7 +6,7 @@ import _ from 'underscore';
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
function UsersSelect(currentUser, els) {
function UsersSelect(currentUser, els, options = {}) {
var $els;
this.users = this.users.bind(this);
this.user = this.user.bind(this);
@ -20,6 +20,8 @@ function UsersSelect(currentUser, els) {
}
}
const { handleClick } = options;
$els = $(els);
if (!els) {
@ -442,6 +444,9 @@ function UsersSelect(currentUser, els) {
}
if ($el.closest('.add-issues-modal').length) {
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
} else if (handleClick) {
e.preventDefault();
handleClick(user, isMarking);
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {

View File

@ -3,6 +3,7 @@
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue';
import icon from '../icon.vue';
export default {
props: {
@ -37,6 +38,7 @@
components: {
markdownHeader,
markdownToolbar,
icon,
},
computed: {
shouldShowReferencedUsers() {
@ -45,8 +47,10 @@
},
},
methods: {
toggleMarkdownPreview() {
this.previewMarkdown = !this.previewMarkdown;
showPreviewTab() {
if (this.previewMarkdown) return;
this.previewMarkdown = true;
/*
Can't use `$refs` as the component is technically in the parent component
@ -54,20 +58,22 @@
*/
const text = this.$slots.textarea[0].elm.value;
if (!this.previewMarkdown) {
this.markdownPreview = '';
} else if (text) {
if (text) {
this.markdownPreviewLoading = true;
this.$http.post(this.markdownPreviewPath, { text })
.then(resp => resp.json())
.then((data) => {
this.renderMarkdown(data);
})
.then(data => this.renderMarkdown(data))
.catch(() => new Flash('Error loading markdown preview'));
} else {
this.renderMarkdown();
}
},
showWriteTab() {
this.markdownPreview = '';
this.previewMarkdown = false;
},
renderMarkdown(data = {}) {
this.markdownPreviewLoading = false;
this.markdownPreview = data.body || 'Nothing to preview.';
@ -104,7 +110,8 @@
ref="gl-form">
<markdown-header
:preview-markdown="previewMarkdown"
@toggle-markdown="toggleMarkdownPreview" />
@preview-markdown="showPreviewTab"
@write-markdown="showWriteTab" />
<div
class="md-write-holder"
v-show="!previewMarkdown">
@ -114,10 +121,10 @@
class="zen-control zen-control-leave js-zen-leave"
href="#"
aria-label="Enter zen mode">
<i
class="fa fa-compress"
aria-hidden="true">
</i>
<icon
name="screen-normal"
:size="32">
</icon>
</a>
<markdown-toolbar
:markdown-docs-path="markdownDocsPath"

View File

@ -1,6 +1,7 @@
<script>
import tooltip from '../../directives/tooltip';
import toolbarButton from './toolbar_button.vue';
import icon from '../icon.vue';
export default {
props: {
@ -14,25 +15,34 @@
},
components: {
toolbarButton,
icon,
},
methods: {
toggleMarkdownPreview(e, form) {
if (form && !form.find('.js-vue-markdown-field').length) {
return;
} else if (e.target.blur) {
e.target.blur();
}
isMarkdownForm(form) {
return form && !form.find('.js-vue-markdown-field').length;
},
this.$emit('toggle-markdown');
previewMarkdownTab(event, form) {
if (event.target.blur) event.target.blur();
if (this.isMarkdownForm(form)) return;
this.$emit('preview-markdown');
},
writeMarkdownTab(event, form) {
if (event.target.blur) event.target.blur();
if (this.isMarkdownForm(form)) return;
this.$emit('write-markdown');
},
},
mounted() {
$(document).on('markdown-preview:show.vue', this.toggleMarkdownPreview);
$(document).on('markdown-preview:hide.vue', this.toggleMarkdownPreview);
$(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
$(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
},
beforeDestroy() {
$(document).on('markdown-preview:show.vue', this.toggleMarkdownPreview);
$(document).off('markdown-preview:hide.vue', this.toggleMarkdownPreview);
$(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
},
};
</script>
@ -42,17 +52,19 @@
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }">
<a
class="js-write-link"
href="#md-write-holder"
tabindex="-1"
@click.prevent="toggleMarkdownPreview($event)">
@click.prevent="writeMarkdownTab($event)">
Write
</a>
</li>
<li :class="{ active: previewMarkdown }">
<a
class="js-preview-link"
href="#md-preview-holder"
tabindex="-1"
@click.prevent="toggleMarkdownPreview($event)">
@click.prevent="previewMarkdownTab($event)">
Preview
</a>
</li>
@ -70,7 +82,7 @@
tag="> "
:prepend="true"
button-title="Insert a quote"
icon="quote-right" />
icon="quote" />
<toolbar-button
tag="`"
tag-block="```"
@ -80,17 +92,17 @@
tag="* "
:prepend="true"
button-title="Add a bullet list"
icon="list-ul" />
icon="list-bulleted" />
<toolbar-button
tag="1. "
:prepend="true"
button-title="Add a numbered list"
icon="list-ol" />
icon="list-numbered" />
<toolbar-button
tag="* [ ] "
:prepend="true"
button-title="Add a task list"
icon="check-square-o" />
icon="task-done" />
</div>
<div class="toolbar-group">
<button
@ -101,10 +113,9 @@
tabindex="-1"
title="Go full screen"
type="button">
<i
aria-hidden="true"
class="fa fa-arrows-alt fa-fw">
</i>
<icon
name="screen-full">
</icon>
</button>
</div>
</li>

View File

@ -1,5 +1,6 @@
<script>
import tooltip from '../../directives/tooltip';
import icon from '../icon.vue';
export default {
props: {
@ -26,14 +27,12 @@
default: false,
},
},
components: {
icon,
},
directives: {
tooltip,
},
computed: {
iconClass() {
return `fa-${this.icon}`;
},
},
};
</script>
@ -49,10 +48,8 @@
:data-md-prepend="prepend"
:title="buttonTitle"
:aria-label="buttonTitle">
<i
aria-hidden="true"
class="fa fa-fw"
:class="iconClass">
</i>
<icon
:name="icon">
</icon>
</button>
</template>

View File

@ -5,17 +5,27 @@ export default {
props: {
title: {
type: String,
required: true,
required: false,
},
text: {
type: String,
required: false,
},
hideFooter: {
type: Boolean,
required: false,
default: false,
},
kind: {
type: String,
required: false,
default: 'primary',
},
modalDialogClass: {
type: String,
required: false,
default: '',
},
closeKind: {
type: String,
required: false,
@ -30,6 +40,11 @@ export default {
type: String,
required: true,
},
submitDisabled: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
@ -57,43 +72,58 @@ export default {
</script>
<template>
<div
class="modal popup-dialog"
role="dialog"
tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button"
class="close"
@click="close"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">{{this.title}}</h4>
</div>
<div class="modal-body">
<slot name="body" :text="text">
<p>{{text}}</p>
</slot>
</div>
<div class="modal-footer">
<button
type="button"
class="btn"
:class="btnCancelKindClass"
@click="close">
{{ closeButtonLabel }}
</button>
<button
type="button"
class="btn"
:class="btnKindClass"
@click="emitSubmit(true)">
{{ primaryButtonLabel }}
</button>
<div class="modal-open">
<div
class="modal popup-dialog"
role="dialog"
tabindex="-1"
>
<div
:class="modalDialogClass"
class="modal-dialog"
role="document"
>
<div class="modal-content">
<div class="modal-header">
<slot name="header">
<h4 class="modal-title pull-left">
{{this.title}}
</h4>
<button
type="button"
class="close pull-right"
@click="close"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
</slot>
</div>
<div class="modal-body">
<slot name="body" :text="text">
<p>{{this.text}}</p>
</slot>
</div>
<div class="modal-footer" v-if="!hideFooter">
<button
type="button"
class="btn pull-left"
:class="btnCancelKindClass"
@click="close">
{{ closeButtonLabel }}
</button>
<button
type="button"
class="btn pull-right"
:disabled="submitDisabled"
:class="btnKindClass"
@click="emitSubmit(true)">
{{ primaryButtonLabel }}
</button>
</div>
</div>
</div>
</div>
<div class="modal-backdrop fade in" />
</div>
</template>

View File

@ -42,8 +42,7 @@
&.avatar-inline {
float: none;
display: inline-block;
margin-left: 4px;
margin-bottom: 2px;
margin-left: 2px;
flex-shrink: 0;
-webkit-flex-shrink: 0;
@ -59,7 +58,7 @@
&.avatar-tile {
border-radius: 0;
border: none;
border: 0;
}
&:not([href]):hover {
@ -96,7 +95,7 @@
.avatar {
border-radius: 0;
border: none;
border: 0;
height: auto;
width: 100%;
margin: 0;

View File

@ -39,7 +39,7 @@
}
&.top-block {
border-top: none;
border-top: 0;
.container-fluid {
background-color: inherit;
@ -63,7 +63,7 @@
&.footer-block {
margin-top: 0;
border-bottom: none;
border-bottom: 0;
margin-bottom: -$gl-padding;
}
@ -100,7 +100,7 @@
&.build-content {
background-color: $white-light;
border-top: none;
border-top: 0;
}
}
@ -287,12 +287,12 @@
cursor: pointer;
color: $blue-300;
z-index: 1;
border: none;
border: 0;
background-color: transparent;
&:hover,
&:focus {
border: none;
border: 0;
color: $blue-400;
}
}

View File

@ -304,7 +304,7 @@
}
.btn-clipboard {
border: none;
border: 0;
padding: 0 5px;
}

View File

@ -4,6 +4,9 @@
.cred { color: $common-red; }
.cgreen { color: $common-green; }
.cdark { color: $common-gray-dark; }
.text-secondary {
color: $gl-text-color-secondary;
}
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; }
@ -28,7 +31,7 @@
pre {
&.clean {
background: none;
border: none;
border: 0;
margin: 0;
padding: 0;
}
@ -142,7 +145,7 @@ li.note {
img { max-width: 100%; }
.note-title {
li {
border-bottom: none !important;
border-bottom: 0 !important;
}
}
}
@ -187,7 +190,7 @@ li.note {
pre {
background: $white-light;
border: none;
border: 0;
font-size: 12px;
}
}
@ -386,7 +389,7 @@ img.emoji {
}
.hide-bottom-border {
border-bottom: none !important;
border-bottom: 0 !important;
}
.gl-accessibility {

View File

@ -37,6 +37,7 @@
.dropdown-menu-nav {
@include set-visible;
display: block;
min-height: 40px;
@media (max-width: $screen-xs-max) {
width: 100%;

View File

@ -142,7 +142,7 @@
*/
&.blame {
table {
border: none;
border: 0;
margin: 0;
}
@ -150,20 +150,20 @@
border-bottom: 1px solid $blame-border;
&:last-child {
border-bottom: none;
border-bottom: 0;
}
}
td {
border-top: none;
border-bottom: none;
border-top: 0;
border-bottom: 0;
&:first-child {
border-left: none;
border-left: 0;
}
&:last-child {
border-right: none;
border-right: 0;
}
&.blame-commit {

View File

@ -255,7 +255,7 @@
.clear-search {
width: 35px;
background-color: $white-light;
border: none;
border: 0;
outline: none;
z-index: 1;
@ -418,7 +418,7 @@
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
.btn {
border: none;
border: 0;
width: 100%;
text-align: left;
padding: 8px 16px;

View File

@ -10,7 +10,7 @@
z-index: 1000;
margin-bottom: 0;
min-height: $header-height;
border: none;
border: 0;
border-bottom: 1px solid $border-color;
position: fixed;
top: 0;
@ -169,7 +169,7 @@
.navbar-collapse {
flex: 0 0 auto;
border-top: none;
border-top: 0;
padding: 0;
@media (max-width: $screen-xs-max) {
@ -352,77 +352,7 @@
.header-user .dropdown-menu-nav,
.header-new .dropdown-menu-nav {
margin-top: 4px;
}
.search {
margin: 4px 8px 0;
form {
height: 32px;
border: 0;
border-radius: $border-radius-default;
transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover {
box-shadow: none;
}
}
.search-input {
color: $white-light;
background: none;
transition: color ease-in-out 0.15s;
}
.search-input::placeholder {
transition: color ease-in-out 0.15s;
}
.location-badge {
font-size: 12px;
margin: -4px 4px -4px -4px;
line-height: 25px;
padding: 4px 8px;
border-radius: 2px 0 0 2px;
height: 32px;
transition: border-color ease-in-out 0.15s;
}
&.search-active {
form {
background-color: rgba($indigo-200, .3);
box-shadow: none;
.search-input {
color: $gl-text-color;
transition: color ease-in-out 0.15s;
}
.search-input::placeholder {
color: $gl-text-color-tertiary;
}
.search-input-wrap {
.search-icon,
.clear-icon {
color: $gl-text-color-tertiary;
transition: color ease-in-out 0.15s;
}
}
}
.location-badge {
background-color: $nav-badge-bg;
border-color: $border-color;
}
.search-input-wrap {
.clear-icon {
color: $white-light;
}
}
}
margin-top: $dropdown-vertical-offset;
}
.breadcrumbs {

View File

@ -1,5 +1,5 @@
.file-content.code {
border: none;
border: 0;
box-shadow: none;
margin: 0;
padding: 0;
@ -7,7 +7,7 @@
pre {
padding: 10px 0;
border: none;
border: 0;
border-radius: 0;
font-family: $monospace_font;
font-size: $code_font_size;

View File

@ -42,7 +42,7 @@
}
&:last-child {
border-bottom: none;
border-bottom: 0;
&.bottom {
background: $gray-light;
@ -92,7 +92,7 @@ ul.unstyled-list {
}
ul.unstyled-list > li {
border-bottom: none;
border-bottom: 0;
}
// Generic content list
@ -178,7 +178,7 @@ ul.content-list {
// When dragging a list item
&.ui-sortable-helper {
border-bottom: none;
border-bottom: 0;
}
&.list-placeholder {
@ -295,7 +295,7 @@ ul.indent-list {
}
> .group-list-tree > .group-row.has-children:first-child {
border-top: none;
border-top: 0;
}
}
@ -413,7 +413,7 @@ ul.indent-list {
padding: 0;
&.has-children {
border-top: none;
border-top: 0;
}
&:first-child {

View File

@ -138,15 +138,23 @@
.toolbar-btn {
float: left;
padding: 0 5px;
color: $gl-text-color-secondary;
padding: 0 7px;
background: transparent;
border: 0;
outline: 0;
svg {
width: 14px;
height: 14px;
margin-top: 3px;
fill: $gl-text-color-secondary;
}
&:hover,
&:focus {
color: $gl-link-color;
svg {
fill: $gl-link-color;
}
}
}

View File

@ -36,7 +36,7 @@
margin: 0;
&:last-child {
border-bottom: none;
border-bottom: 0;
}
&.active {

View File

@ -7,6 +7,7 @@
}
.modal-body {
background-color: $modal-body-bg;
position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size};
@ -42,3 +43,8 @@ body.modal-open {
width: 98%;
}
}
.modal.popup-dialog {
display: block;
}

View File

@ -24,7 +24,7 @@
@media (min-width: $screen-md-min) {
margin: 0;
padding: $gl-padding 0;
border: none;
border: 0;
&:not(:last-child) {
border-bottom: 1px solid $white-normal;

View File

@ -63,7 +63,7 @@
.nav-links {
margin-bottom: 0;
border-bottom: none;
border-bottom: 0;
float: left;
&.wide {
@ -335,69 +335,16 @@
border-bottom: 1px solid $border-color;
.nav-links {
border-bottom: none;
border-bottom: 0;
}
}
}
.page-with-layout-nav {
.right-sidebar {
top: ($header-height + 1) * 2;
}
&.page-with-sub-nav {
.right-sidebar {
top: ($header-height + 1) * 3;
&.affix {
top: $header-height;
}
}
}
}
.with-performance-bar .page-with-layout-nav {
.right-sidebar {
top: ($header-height + 1) * 2 + $performance-bar-height;
}
&.page-with-sub-nav {
.right-sidebar {
top: ($header-height + 1) * 3 + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
}
}
}
}
@media (max-width: $screen-xs-max) {
.top-area {
flex-flow: row wrap;
.nav-controls {
$controls-margin: $btn-xs-side-margin - 2px;
flex: 0 0 100%;
&.controls-flex {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
padding: 0 0 $gl-padding-top;
}
.controls-item,
.controls-item-full,
.controls-item:last-child {
flex: 1 1 35%;
display: block;
width: 100%;
margin: $controls-margin;
}
}
}
.project-item-select-holder.btn-group {
display: flex;
max-width: 350px;
overflow: hidden;
float: right;
.new-project-item-link {
white-space: nowrap;

View File

@ -17,7 +17,7 @@
.select2-arrow {
background-image: none;
background-color: transparent;
border: none;
border: 0;
padding-top: 12px;
padding-right: 20px;
font-size: 10px;
@ -60,12 +60,17 @@
border-radius: $border-radius-base;
border: 1px solid $dropdown-border-color;
min-width: 175px;
color: $gl-grayish-blue;
color: $gl-text-color;
z-index: 999;
}
.select2-results .select2-result-label,
.select2-more-results {
padding: 10px 15px;
.select2-drop-mask {
z-index: 998;
}
.select2-drop.select2-drop-above.select2-drop-active {
border-top: 1px solid $dropdown-border-color;
margin-top: -6px;
}
.select2-container-active {
@ -158,18 +163,35 @@
}
}
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,
.select2-results .select2-selection-limit {
background: $gray-light;
display: list-item;
padding: 10px 15px;
}
.select2-results {
margin: 0;
padding: 10px 0;
padding: #{$gl-padding / 2} 0;
.select2-no-results,
.select2-searching,
.select2-ajax-error,
.select2-selection-limit {
background: transparent;
padding: #{$gl-padding / 2} $gl-padding;
}
.select2-result-label,
.select2-more-results {
padding: #{$gl-padding / 2} $gl-padding;
}
.select2-highlighted {
background: transparent;
color: $gl-text-color;
.select2-result-label {
background: $dropdown-item-hover-bg;
}
}
.select2-result {
padding: 0 1px;
}
li.select2-result-with-children > .select2-result-label {
font-weight: $gl-font-weight-bold;
@ -190,8 +212,6 @@
}
.select2-highlighted {
background: $gl-link-color !important;
.group-result {
.group-path {
color: $white-light;

View File

@ -9,7 +9,7 @@
&.container-blank {
background: none;
padding: 0;
border: none;
border: 0;
}
}
}
@ -111,7 +111,7 @@
}
.block:last-of-type {
border: none;
border: 0;
}
}

View File

@ -33,7 +33,7 @@ table {
th {
background-color: $gray-light;
font-weight: $gl-font-weight-normal;
border-bottom: none;
border-bottom: 0;
&.wide {
width: 55%;

View File

@ -21,7 +21,7 @@
}
&.text-file .diff-file {
border-bottom: none;
border-bottom: 0;
}
}
@ -66,5 +66,5 @@
.discussion .timeline-entry {
margin: 0;
border-right: none;
border-right: 0;
}

View File

@ -164,3 +164,36 @@ $pre-border-color: $border-color;
$table-bg-accent: $gray-light;
$zindex-popover: 900;
//== Modals
//
//##
//** Padding applied to the modal body
$modal-inner-padding: $gl-padding;
//** Padding applied to the modal title
$modal-title-padding: $gl-padding;
//** Modal title line-height
// $modal-title-line-height: $line-height-base
//** Background color of modal content area
$modal-content-bg: $gray-light;
$modal-body-bg: $white-light;
//** Modal content border color
// $modal-content-border-color: rgba(0,0,0,.2)
//** Modal content border color **for IE8**
// $modal-content-fallback-border-color: #999
//** Modal backdrop background color
// $modal-backdrop-bg: #000
//** Modal backdrop opacity
// $modal-backdrop-opacity: .5
//** Modal header border color
// $modal-header-border-color: #e5e5e5
//** Modal footer border color
// $modal-footer-border-color: $modal-header-border-color
// $modal-lg: 900px
// $modal-md: 600px
// $modal-sm: 300px

View File

@ -167,7 +167,7 @@
&.plain-readme {
background: none;
border: none;
border: 0;
padding: 0;
margin: 0;
font-size: 14px;

View File

@ -9,7 +9,7 @@
z-index: 1031;
textarea {
border: none;
border: 0;
box-shadow: none;
border-radius: 0;
color: $black;
@ -57,7 +57,15 @@
padding: 5px;
font-size: 36px;
svg {
fill: $gl-text-color;
}
&:hover {
color: $black;
svg {
fill: $black;
}
}
}

View File

@ -48,7 +48,7 @@
overflow-x: auto;
font-size: 12px;
border-radius: 0;
border: none;
border: 0;
.bash {
display: block;

View File

@ -36,7 +36,7 @@
pre.commit-message {
background: none;
padding: 0;
border: none;
border: 0;
margin: 20px 0;
border-radius: 0;
}

View File

@ -1,6 +1,6 @@
.commit-description {
background: none;
border: none;
border: 0;
padding: 0;
margin-top: 10px;
word-break: normal;
@ -247,7 +247,7 @@
word-break: normal;
pre {
border: none;
border: 0;
background: inherit;
padding: 0;
margin: 0;

View File

@ -80,7 +80,7 @@
.panel {
.content-block {
padding: 24px 0;
border-bottom: none;
border-bottom: 0;
position: relative;
@media (max-width: $screen-xs-max) {
@ -222,11 +222,11 @@
}
&:first-child {
border-top: none;
border-top: 0;
}
&:last-child {
border-bottom: none;
border-bottom: 0;
}
.stage-nav-item-cell {
@ -290,7 +290,7 @@
border-bottom: 1px solid $gray-darker;
&:last-child {
border-bottom: none;
border-bottom: 0;
margin-bottom: 0;
}

View File

@ -3,6 +3,7 @@
border-bottom: 1px solid $border-color;
color: $gl-text-color;
line-height: 34px;
display: flex;
a {
color: $gl-text-color;

View File

@ -47,7 +47,7 @@
table {
width: 100%;
font-family: $monospace_font;
border: none;
border: 0;
border-collapse: separate;
margin: 0;
padding: 0;
@ -105,7 +105,7 @@
.new_line {
@include user-select(none);
margin: 0;
border: none;
border: 0;
padding: 0 5px;
border-right: 1px solid;
text-align: right;
@ -133,7 +133,7 @@
display: block;
margin: 0;
padding: 0 1.5em;
border: none;
border: 0;
position: relative;
&.parallel {
@ -359,7 +359,7 @@
cursor: pointer;
&:first-child {
border-left: none;
border-left: 0;
}
&:hover {
@ -388,7 +388,7 @@
.file-content .diff-file {
margin: 0;
border: none;
border: 0;
}
.diff-wrap-lines .line_content {
@ -400,7 +400,7 @@
}
.files-changed {
border-bottom: none;
border-bottom: 0;
}
.diff-stats-summary-toggler {

View File

@ -3,13 +3,13 @@
border-top: 1px solid $border-color;
border-right: 1px solid $border-color;
border-left: 1px solid $border-color;
border-bottom: none;
border-bottom: 0;
border-radius: $border-radius-small $border-radius-small 0 0;
background: $gray-normal;
}
#editor {
border: none;
border: 0;
border-radius: 0;
height: 500px;
margin: 0;
@ -171,7 +171,7 @@
width: 100%;
margin: 5px 0;
padding: 0;
border-left: none;
border-left: 0;
}
}

View File

@ -117,7 +117,7 @@
}
.no-btn {
border: none;
border: 0;
background: none;
outline: none;
width: 100%;
@ -133,11 +133,11 @@
}
.folder-row {
border-left: none;
border-right: none;
border-left: 0;
border-right: 0;
@media (min-width: $screen-sm-max) {
border-top: none;
border-top: 0;
}
}
@ -256,12 +256,6 @@
padding: 0;
padding-bottom: 100%;
.label-axis-text {
fill: $black;
font-weight: $gl-font-weight-normal;
font-size: 10px;
}
.text-metric-usage,
.legend-metric-title {
fill: $black;
@ -276,19 +270,33 @@
left: 0;
top: 0;
.label-axis-text,
.text-metric-usage {
text {
fill: $gl-text-color;
stroke-width: 0;
}
.text-metric-bold {
font-weight: $gl-font-weight-bold;
}
.label-axis-text {
fill: $black;
font-weight: $gl-font-weight-normal;
font-size: 12px;
font-size: 10px;
}
.legend-axis-text {
fill: $black;
}
.tick > text {
font-size: 12px;
.tick {
> line {
stroke: $gray-darker;
}
> text {
font-size: 12px;
}
}
.text-metric-title {

View File

@ -85,7 +85,7 @@
}
pre {
border: none;
border: 0;
background: $gray-light;
border-radius: 0;
color: $events-pre-color;
@ -128,14 +128,14 @@
}
}
&:last-child { border: none; }
&:last-child { border: 0; }
.event_commits {
li {
&.commit {
background: transparent;
padding: 0;
border: none;
border: 0;
.commit-row-title {
font-size: $gl-font-size;

View File

@ -79,7 +79,7 @@
.title {
padding: 0;
margin-bottom: 16px;
border-bottom: none;
border-bottom: 0;
}
.btn-edit {
@ -131,12 +131,12 @@
top: $header-height;
bottom: 0;
right: 0;
transition: width .3s;
transition: width $right-sidebar-transition-duration;
background: $gray-light;
z-index: 200;
overflow: hidden;
a,
a:not(.btn-retry),
.btn-link {
color: inherit;
}
@ -164,7 +164,7 @@
}
&:last-child {
border: none;
border: 0;
}
span {
@ -338,7 +338,7 @@
.block {
width: $gutter_collapsed_width - 2px;
padding: 15px 0 0;
border-bottom: none;
border-bottom: 0;
overflow: hidden;
}
@ -399,7 +399,7 @@
}
.btn-clipboard {
border: none;
border: 0;
color: $issuable-sidebar-color;
&:hover {
@ -613,6 +613,8 @@
float: none;
display: inline-block;
margin-top: 0;
height: auto;
align-self: center;
@media (max-width: $screen-xs-max) {
position: absolute;
@ -626,6 +628,8 @@
padding-left: 45px;
padding-right: 45px;
line-height: 35px;
display: flex;
flex-grow: 1;
@media (min-width: $screen-sm-min) {
float: left;
@ -637,11 +641,12 @@
.issuable-actions {
@include new-style-dropdown;
padding-top: 10px;
align-self: center;
flex-shrink: 0;
flex: 0 0 auto;
@media (min-width: $screen-sm-min) {
float: right;
padding-top: 0;
}
}
@ -655,8 +660,9 @@
.issuable-meta {
display: inline-block;
line-height: 18px;
font-size: 14px;
line-height: 24px;
align-self: center;
}
.js-issuable-selector-wrap {

View File

@ -134,11 +134,24 @@ ul.related-merge-requests > li {
}
@media (max-width: $screen-xs-max) {
.issue-btn-group {
width: 100%;
.detail-page-header,
.issuable-header {
display: block;
.btn {
.issuable-meta {
line-height: 18px;
}
}
.issuable-actions {
margin-top: 10px;
.issue-btn-group {
width: 100%;
.btn {
width: 100%;
}
}
}
}

View File

@ -139,7 +139,7 @@
border-left: 1px solid $border-color;
&:first-of-type {
border-left: none;
border-left: 0;
border-top-left-radius: $border-radius-default;
}
@ -165,7 +165,7 @@
border-bottom: 1px solid $border-color;
a {
border: none;
border: 0;
border-bottom: 2px solid $link-underline-blue;
margin-right: 0;
color: $black;

View File

@ -262,7 +262,7 @@ $colors: (
.editor {
pre {
height: 350px;
border: none;
border: 0;
border-radius: 0;
margin-bottom: 0;
}

View File

@ -150,18 +150,6 @@
display: block;
}
.mr-widget-body {
@include clearfix;
&.media > *:first-child {
margin-right: 10px;
}
.approve-btn {
margin-right: 5px;
}
}
.mr-widget-pipeline-graph {
padding: 0 4px;
@ -169,9 +157,8 @@
z-index: 300;
}
.ci-action-icon-wrapper svg {
width: 16px;
height: 16px;
.ci-action-icon-wrapper {
line-height: 16px;
}
}
@ -195,10 +182,6 @@
overflow: hidden;
word-break: break-all;
&.media > *:first-child {
margin-right: 10px;
}
&.label-truncated {
position: relative;
display: inline-block;
@ -216,6 +199,18 @@
background-color: $gray-light;
}
}
}
.mr-widget-body {
@include clearfix;
&.media > *:first-child {
margin-right: 10px;
}
.approve-btn {
margin-right: 5px;
}
h4 {
float: left;
@ -239,10 +234,6 @@
margin-right: 7px;
}
.approve-btn {
margin-right: 5px;
}
label {
font-weight: $gl-font-weight-normal;
}
@ -342,17 +333,6 @@
}
}
.mini-pipeline-graph-dropdown-menu .mini-pipeline-graph-dropdown-item {
display: flex;
align-items: center;
.ci-status-text,
.ci-status-icon {
top: 0;
margin-right: 10px;
}
}
.mr-widget-help {
padding: 10px 16px 10px 48px;
font-style: italic;

View File

@ -16,7 +16,7 @@
.discussion {
.new-note {
margin: 0;
border: none;
border: 0;
}
}
@ -106,15 +106,35 @@
background-color: $orange-100;
border-radius: $border-radius-default $border-radius-default 0 0;
border: 1px solid $border-gray-normal;
border-bottom: none;
border-bottom: 0;
padding: 3px 12px;
margin: auto;
align-items: center;
.icon {
margin-right: $issuable-warning-icon-margin;
}
+ .md-area {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.disabled-comment {
border: 0;
border-radius: $label-border-radius;
padding-top: $gl-vert-padding;
padding-bottom: $gl-vert-padding;
.icon svg {
position: relative;
top: 2px;
margin-right: $btn-xs-side-margin;
width: $gl-font-size;
height: $gl-font-size;
fill: $orange-600;
}
}
}
.sidebar-item-value {

View File

@ -331,7 +331,7 @@ ul.notes {
td {
border: 1px solid $white-normal;
border-left: none;
border-left: 0;
&.notes_line {
vertical-align: middle;
@ -476,6 +476,10 @@ ul.notes {
float: none;
margin-left: 0;
}
.btn-group > .discussion-next-btn {
margin-left: -1px;
}
}
.note-actions {
@ -666,7 +670,7 @@ ul.notes {
.timeline-entry-inner {
padding-left: $gl-padding;
padding-right: $gl-padding;
border-bottom: none;
border-bottom: 0;
}
}
}
@ -679,7 +683,7 @@ ul.notes {
padding: 90px 0;
&.discussion-locked {
border: none;
border: 0;
background-color: $white-light;
}
@ -759,7 +763,7 @@ ul.notes {
top: 0;
padding: 0;
background-color: transparent;
border: none;
border: 0;
outline: 0;
color: $gray-darkest;
transition: color $general-hover-transition-duration $general-hover-transition-curve;

View File

@ -179,7 +179,7 @@
* Play button with icon in dropdowns
*/
.no-btn {
border: none;
border: 0;
background: none;
outline: none;
width: 100%;
@ -288,7 +288,7 @@
.pipeline-actions {
@include new-style-dropdown;
border-bottom: none;
border-bottom: 0;
}
.tab-pane {
@ -318,7 +318,7 @@
}
.build-log {
border: none;
border: 0;
line-height: initial;
}
}
@ -386,13 +386,13 @@
// Remove right connecting horizontal line from first build in last stage
&:first-child {
&::after {
border: none;
border: 0;
}
}
// Remove right curved connectors from all builds in last stage
&:not(:first-child) {
&::after {
border: none;
border: 0;
}
}
// Remove opposite curve
@ -409,7 +409,7 @@
// Remove left curved connectors from all builds in first stage
&:not(:first-child) {
&::before {
border: none;
border: 0;
}
}
// Remove opposite curve
@ -518,7 +518,7 @@
.dropdown-menu-toggle {
background-color: transparent;
border: none;
border: 0;
padding: 0;
&:focus {
@ -823,6 +823,11 @@ button.mini-pipeline-graph-dropdown-toggle {
margin-left: 2px;
display: inline-block;
&::after {
content: '';
display: block;
}
@media (max-width: $screen-xs-max) {
max-width: 60%;
}
@ -951,7 +956,7 @@ button.mini-pipeline-graph-dropdown-toggle {
.terminal-container {
.content-block {
border-bottom: none;
border-bottom: 0;
}
#terminal {

View File

@ -113,7 +113,7 @@
li {
padding: 3px 0;
border: none;
border: 0;
}
}

View File

@ -80,7 +80,7 @@
.project-feature-settings {
background: $gray-lighter;
border-top: none;
border-top: 0;
margin-bottom: 16px;
}
@ -128,7 +128,7 @@
.project-feature-toggle {
position: relative;
border: none;
border: 0;
outline: 0;
display: block;
width: 100px;
@ -483,7 +483,7 @@ a.deploy-project-label {
flex: 1;
padding: 0;
background: transparent;
border: none;
border: 0;
line-height: 34px;
margin: 0;
@ -1012,7 +1012,7 @@ pre.light-well {
margin: 0;
border-radius: 0 0 1px 1px;
padding: 20px 0;
border: none;
border: 0;
}
.table-bordered {
@ -1165,7 +1165,7 @@ pre.light-well {
table-layout: fixed;
&.table-responsive {
border: none;
border: 0;
}
.variable-key {

View File

@ -64,7 +64,7 @@
.monaco-editor.vs {
.current-line {
border: none;
border: 0;
background: $well-light-border;
}
@ -139,7 +139,7 @@
&.active {
background: $white-light;
border-bottom: none;
border-bottom: 0;
}
a {
@ -181,7 +181,7 @@
&.tabs-divider {
width: 100%;
background-color: $white-light;
border-right: none;
border-right: 0;
border-top-right-radius: 2px;
}
}

View File

@ -5,7 +5,7 @@
margin-bottom: $gl-padding;
&:last-child {
border-bottom: none;
border-bottom: 0;
}
}
@ -57,7 +57,7 @@ input[type="checkbox"]:hover {
}
.search-input {
border: none;
border: 0;
font-size: 14px;
padding: 0 20px 0 0;
margin-left: 5px;
@ -78,10 +78,6 @@ input[type="checkbox"]:hover {
}
.search-input-wrap {
// Fallback if flexbox is not supported
display: inline-block;
width: 100%;
.search-icon,
.clear-icon {
position: absolute;

View File

@ -141,7 +141,7 @@
}
pre {
border: none;
border: 0;
background: $gray-light;
border-radius: 0;
color: $todo-body-pre-color;

View File

@ -252,7 +252,7 @@
margin-top: 20px;
padding: 0;
border-top: 1px solid $white-dark;
border-bottom: none;
border-bottom: 0;
}
.commit-stats li {

View File

@ -57,12 +57,11 @@ module IssuableActions
def destroy
issuable.destroy
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
TodoService.new.public_send(destroy_method, issuable, current_user) # rubocop:disable GitlabSecurity/PublicSend
TodoService.new.destroy_issuable(issuable, current_user)
name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted."
index_path = polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
index_path = polymorphic_path([parent, issuable.class])
respond_to do |format|
format.html { redirect_to index_path }
@ -164,4 +163,8 @@ module IssuableActions
def update_service
raise NotImplementedError
end
def parent
@project || @group
end
end

View File

@ -39,7 +39,7 @@ module NotesActions
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
Notes::RenderService.new(current_user).execute([@note], @project)
end
respond_to do |format|
@ -52,7 +52,7 @@ module NotesActions
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
Notes::RenderService.new(current_user).execute([@note], @project)
end
respond_to do |format|

View File

@ -3,7 +3,7 @@ module RendersNotes
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes)
Banzai::NoteRenderer.render(notes, @project, current_user)
Notes::RenderService.new(current_user).execute(notes, @project)
notes
end

View File

@ -57,5 +57,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@events = EventCollection
.new(projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
end
end

View File

@ -32,6 +32,8 @@ class DashboardController < Dashboard::ApplicationController
@events = EventCollection
.new(projects, offset: params[:offset].to_i, filter: @event_filter)
.to_a
Events::RenderService.new(current_user).execute(@events)
end
def set_show_full_reference

View File

@ -155,6 +155,8 @@ class GroupsController < Groups::ApplicationController
@events = EventCollection
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
end
def user_actions

View File

@ -3,10 +3,16 @@ class MetricsController < ActionController::Base
protect_from_forgery with: :exception
before_action :validate_prometheus_metrics
def index
render text: metrics_service.metrics_text, content_type: 'text/plain; version=0.0.4'
response = if Gitlab::Metrics.prometheus_metrics_enabled?
metrics_service.metrics_text
else
help_page = help_page_url('administration/monitoring/prometheus/gitlab_metrics',
anchor: 'gitlab-prometheus-metrics'
)
"# Metrics are disabled, see: #{help_page}\n"
end
render text: response, content_type: 'text/plain; version=0.0.4'
end
private
@ -14,8 +20,4 @@ class MetricsController < ActionController::Base
def metrics_service
@metrics_service ||= MetricsService.new
end
def validate_prometheus_metrics
render_404 unless Gitlab::Metrics.prometheus_metrics_enabled?
end
end

View File

@ -1,8 +1,8 @@
class Projects::ClustersController < Projects::ApplicationController
before_action :cluster, except: [:login, :index, :new, :create]
before_action :cluster, except: [:login, :index, :new, :new_gcp, :create]
before_action :authorize_read_cluster!
before_action :authorize_create_cluster!, only: [:new, :create]
before_action :authorize_google_api, only: [:new, :create]
before_action :authorize_create_cluster!, only: [:new, :new_gcp, :create]
before_action :authorize_google_api, only: [:new_gcp, :create]
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
@ -16,7 +16,7 @@ class Projects::ClustersController < Projects::ApplicationController
def login
begin
state = generate_session_key_redirect(namespace_project_clusters_url.to_s)
state = generate_session_key_redirect(providers_gcp_new_namespace_project_clusters_url.to_s)
@authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, callback_google_api_auth_url,
@ -27,18 +27,23 @@ class Projects::ClustersController < Projects::ApplicationController
end
def new
@cluster = project.build_cluster
end
def new_gcp
@cluster = Clusters::Cluster.new.tap do |cluster|
cluster.build_provider_gcp
end
end
def create
@cluster = Ci::CreateClusterService
@cluster = Clusters::CreateService
.new(project, current_user, create_params)
.execute(token_in_session)
if @cluster.persisted?
redirect_to project_cluster_path(project, @cluster)
else
render :new
render :new_gcp
end
end
@ -58,7 +63,7 @@ class Projects::ClustersController < Projects::ApplicationController
end
def update
Ci::UpdateClusterService
Clusters::UpdateService
.new(project, current_user, update_params)
.execute(cluster)
@ -88,19 +93,19 @@ class Projects::ClustersController < Projects::ApplicationController
def create_params
params.require(:cluster).permit(
:gcp_project_id,
:gcp_cluster_zone,
:gcp_cluster_name,
:gcp_cluster_size,
:gcp_machine_type,
:project_namespace,
:enabled)
:enabled,
:name,
:provider_type,
provider_gcp_attributes: [
:gcp_project_id,
:zone,
:num_nodes,
:machine_type
])
end
def update_params
params.require(:cluster).permit(
:project_namespace,
:enabled)
params.require(:cluster).permit(:enabled)
end
def authorize_google_api

View File

@ -2,7 +2,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
before_action :check_merge_requests_available!
before_action :merge_request
before_action :authorize_read_merge_request!
before_action :ensure_ref_fetched
private
@ -10,12 +9,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
@issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end
# Make sure merge requests created before 8.0
# have head file in refs/merge-requests/
def ensure_ref_fetched
@merge_request.ensure_ref_fetched if Gitlab::Database.read_write?
end
def merge_request_params
params.require(:merge_request).permit(merge_request_params_attributes)
end

View File

@ -4,7 +4,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
include RendersCommits
skip_before_action :merge_request
skip_before_action :ensure_ref_fetched
before_action :authorize_create_merge_request!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]

View File

@ -7,7 +7,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update]
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
@ -52,7 +51,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def show
validates_merge_request
ensure_ref_fetched
close_merge_request_without_source_project
check_if_can_be_merged

View File

@ -300,6 +300,8 @@ class ProjectsController < Projects::ApplicationController
@events = EventCollection
.new(projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
end
def project_params

View File

@ -108,6 +108,8 @@ class UsersController < ApplicationController
.references(:project)
.with_associations
.limit_recent(20, params[:offset])
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
end
def load_projects

View File

@ -15,6 +15,8 @@
# Anonymous users will never return any `owned` groups. They will return all
# public groups instead, even if `all_available` is set to false.
class GroupsFinder < UnionFinder
include CustomAttributesFilter
def initialize(current_user = nil, params = {})
@current_user = current_user
@params = params
@ -22,8 +24,12 @@ class GroupsFinder < UnionFinder
def execute
items = all_groups.map do |item|
by_parent(item)
item = by_parent(item)
item = by_custom_attributes(item)
item
end
find_union(items, Group).with_route.order_id_desc
end

View File

@ -18,6 +18,8 @@
# non_archived: boolean
#
class ProjectsFinder < UnionFinder
include CustomAttributesFilter
attr_accessor :params
attr_reader :current_user, :project_ids_relation
@ -44,6 +46,7 @@ class ProjectsFinder < UnionFinder
collection = by_tags(collection)
collection = by_search(collection)
collection = by_archived(collection)
collection = by_custom_attributes(collection)
sort(collection)
end

View File

@ -20,17 +20,6 @@ module BoardsHelper
project_issues_path(@project)
end
def current_board_json
board = @board || @boards.first
board.to_json(
only: [:id, :name, :milestone_id],
include: {
milestone: { only: [:title] }
}
)
end
def board_base_url
project_boards_path(@project)
end

View File

@ -172,16 +172,6 @@ module EventsHelper
end
end
def event_note(text, options = {})
text = first_line_in_markdown(text, 150, options)
sanitize(
text,
tags: %w(a img gl-emoji b pre code p span),
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
)
end
def event_commit_title(message)
message ||= ''
(message.split("\n").first || "").truncate(70)

View File

@ -69,10 +69,16 @@ module MarkupHelper
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(text, max_chars = nil, options = {})
md = markdown(text, options).strip
def first_line_in_markdown(object, attribute, max_chars = nil, options = {})
md = markdown_field(object, attribute, options)
truncate_visible(md, max_chars || md.length) if md.present?
text = truncate_visible(md, max_chars || md.length) if md.present?
sanitize(
text,
tags: %w(a img gl-emoji b pre code p span),
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
)
end
def markdown(text, context = {})
@ -83,15 +89,17 @@ module MarkupHelper
prepare_for_rendering(html, context)
end
def markdown_field(object, field)
def markdown_field(object, field, context = {})
object = object.for_display if object.respond_to?(:for_display)
redacted_field_html = object.try(:"redacted_#{field}_html")
return '' unless object.present?
return redacted_field_html if redacted_field_html
html = Banzai.render_field(object, field)
prepare_for_rendering(html, object.banzai_render_context(field))
html = Banzai.render_field(object, field, context)
context.reverse_merge!(object.banzai_render_context(field)) if object.respond_to?(:banzai_render_context)
prepare_for_rendering(html, context)
end
def markup(file_name, text, context = {})
@ -218,7 +226,7 @@ module MarkupHelper
data: data,
title: options[:title],
aria: { label: options[:title] } do
icon(options[:icon])
sprite_icon(options[:icon])
end
end

View File

@ -0,0 +1,73 @@
module Clusters
class Cluster < ActiveRecord::Base
include Presentable
self.table_name = 'clusters'
belongs_to :user
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
# we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
# We have to ":destroy" it today to ensure that we clean also the Kubernetes Integration
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
validate :restrict_modification, on: :update
# TODO: Move back this into Clusters::Platforms::Kubernetes in 10.3
# We need callback here because `enabled` belongs to Clusters::Cluster
# Callbacks in Clusters::Platforms::Kubernetes will not be called after update
after_save :update_kubernetes_integration!
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :status_name, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
delegate :update_kubernetes_integration!, to: :platform, allow_nil: true
enum platform_type: {
kubernetes: 1
}
enum provider_type: {
user: 0,
gcp: 1
}
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
def provider
return provider_gcp if gcp?
end
def platform
return platform_kubernetes if kubernetes?
end
def first_project
return @first_project if defined?(@first_project)
@first_project = projects.first
end
alias_method :project, :first_project
private
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
return false
end
true
end
end
end

View File

@ -0,0 +1,101 @@
module Clusters
module Platforms
class Kubernetes < ActiveRecord::Base
self.table_name = 'cluster_platforms_kubernetes'
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
attr_encrypted :password,
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
attr_encrypted :token,
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case
validates :namespace,
allow_blank: true,
length: 1..63,
format: {
with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
validates :api_url, url: true, presence: true
validates :token, presence: true
# TODO: Glue code till we migrate Kubernetes Integration into Platforms::Kubernetes
after_destroy :destroy_kubernetes_integration!
alias_attribute :ca_pem, :ca_cert
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
class << self
def namespace_for_project(project)
"#{project.path}-#{project.id}"
end
end
def actual_namespace
if namespace.present?
namespace
else
default_namespace
end
end
def default_namespace
self.class.namespace_for_project(project) if project
end
def update_kubernetes_integration!
raise 'Kubernetes service already configured' unless manages_kubernetes_service?
# This is neccesary, otheriwse enabled? returns true even though cluster updated with enabled: false
cluster.reload
ensure_kubernetes_service&.update!(
active: enabled?,
api_url: api_url,
namespace: namespace,
token: token,
ca_pem: ca_cert
)
end
private
def enforce_namespace_to_lower_case
self.namespace = self.namespace&.downcase
end
# TODO: glue code till we migrate Kubernetes Service into Platforms::Kubernetes class
def manages_kubernetes_service?
return true unless kubernetes_service&.active?
kubernetes_service.api_url == api_url
end
def destroy_kubernetes_integration!
return unless manages_kubernetes_service?
kubernetes_service&.destroy!
end
def kubernetes_service
@kubernetes_service ||= project&.kubernetes_service
end
def ensure_kubernetes_service
@kubernetes_service ||= kubernetes_service || project&.build_kubernetes_service
end
end
end
end

View File

@ -0,0 +1,8 @@
module Clusters
class Project < ActiveRecord::Base
self.table_name = 'cluster_projects'
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
end
end

View File

@ -0,0 +1,79 @@
module Clusters
module Providers
class Gcp < ActiveRecord::Base
self.table_name = 'cluster_providers_gcp'
belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster'
default_value_for :zone, 'us-central1-a'
default_value_for :num_nodes, 3
default_value_for :machine_type, 'n1-standard-4'
attr_encrypted :access_token,
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
validates :gcp_project_id,
length: 1..63,
format: {
with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
validates :zone, presence: true
validates :num_nodes,
presence: true,
numericality: {
only_integer: true,
greater_than: 0
}
state_machine :status, initial: :scheduled do
state :scheduled, value: 1
state :creating, value: 2
state :created, value: 3
state :errored, value: 4
event :make_creating do
transition any - [:creating] => :creating
end
event :make_created do
transition any - [:created] => :created
end
event :make_errored do
transition any - [:errored] => :errored
end
before_transition any => [:errored, :created] do |provider|
provider.access_token = nil
provider.operation_id = nil
end
before_transition any => [:creating] do |provider, transition|
operation_id = transition.args.first
raise ArgumentError.new('operation_id is required') unless operation_id.present?
provider.operation_id = operation_id
end
before_transition any => [:errored] do |provider, transition|
status_reason = transition.args.first
provider.status_reason = status_reason if status_reason
end
end
def on_creation?
scheduled? || creating?
end
def api_client
return unless access_token
@api_client ||= GoogleApi::CloudPlatform::Client.new(access_token, nil)
end
end
end
end

View File

@ -21,8 +21,8 @@ module IgnorableColumn
@ignored_columns ||= Set.new
end
def ignore_column(name)
ignored_columns << name.to_s
def ignore_column(*names)
ignored_columns.merge(names.map(&:to_s))
end
end
end

View File

@ -1,116 +0,0 @@
module Gcp
class Cluster < ActiveRecord::Base
extend Gitlab::Gcp::Model
include Presentable
belongs_to :project, inverse_of: :cluster
belongs_to :user
belongs_to :service
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
default_value_for :gcp_cluster_zone, 'us-central1-a'
default_value_for :gcp_cluster_size, 3
default_value_for :gcp_machine_type, 'n1-standard-4'
attr_encrypted :password,
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
attr_encrypted :kubernetes_token,
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
attr_encrypted :gcp_token,
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
validates :gcp_project_id,
length: 1..63,
format: {
with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
validates :gcp_cluster_name,
length: 1..63,
format: {
with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
validates :gcp_cluster_zone, presence: true
validates :gcp_cluster_size,
presence: true,
numericality: {
only_integer: true,
greater_than: 0
}
validates :project_namespace,
allow_blank: true,
length: 1..63,
format: {
with: Gitlab::Regex.kubernetes_namespace_regex,
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
# if we do not do status transition we prevent change
validate :restrict_modification, on: :update, unless: :status_changed?
state_machine :status, initial: :scheduled do
state :scheduled, value: 1
state :creating, value: 2
state :created, value: 3
state :errored, value: 4
event :make_creating do
transition any - [:creating] => :creating
end
event :make_created do
transition any - [:created] => :created
end
event :make_errored do
transition any - [:errored] => :errored
end
before_transition any => [:errored, :created] do |cluster|
cluster.gcp_token = nil
cluster.gcp_operation_id = nil
end
before_transition any => [:errored] do |cluster, transition|
status_reason = transition.args.first
cluster.status_reason = status_reason if status_reason
end
end
def project_namespace_placeholder
"#{project.path}-#{project.id}"
end
def on_creation?
scheduled? || creating?
end
def api_url
'https://' + endpoint if endpoint
end
def restrict_modification
if on_creation?
errors.add(:base, "cannot modify during creation")
return false
end
true
end
end
end

View File

@ -26,6 +26,7 @@ class Group < Namespace
has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, class_name: 'GroupLabel'
has_many :variables, class_name: 'Ci::GroupVariable'
has_many :custom_attributes, class_name: 'GroupCustomAttribute'
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects

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