Commit 0748960b authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2017-04-26' into 'master'

CE upstream - Wednesday

Closes gitaly#182, gitlab-ce#31276, #1440, and gitlab-ce#22740

See merge request !1725
parents c7a3e99a 7d16efae
......@@ -50,7 +50,7 @@ eslint-report.html
/tags
/tmp/*
/vendor/bundle/*
/builds/*
/builds*
/shared/*
/.gitlab_workhorse_secret
/webpack-report/
......@@ -72,6 +72,16 @@ stages:
- //@gitlab-org/gitlab-ee
- //@gitlab/gitlab-ee
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
only:
- /mysql/
- master@gitlab-org/gitlab-ce
- master@gitlab/gitlabhq
- tags@gitlab-org/gitlab-ce
- tags@gitlab/gitlabhq
- //@gitlab-org/gitlab-ee
- //@gitlab/gitlab-ee
.rspec-knapsack: &rspec-knapsack
stage: test
<<: *dedicated-runner
......
......@@ -545,7 +545,7 @@ Style/Proc:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 60
Max: 57.08
# This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength:
......@@ -564,7 +564,7 @@ Metrics/ClassLength:
# of test cases needed to validate a method.
Metrics/CyclomaticComplexity:
Enabled: true
Max: 17
Max: 16
# Limit lines to 80 characters.
Metrics/LineLength:
......
......@@ -11,6 +11,7 @@ entry.
- Fix lastest commit status text on main project page. !10863
- Add index on ci_builds.updated_at. !10870 (blackst0ne)
- Fix 500 error due to trying to show issues from pending deleting projects. !10906
- Ensures that OAuth/LDAP/SAML users don't need to be confirmed.
- Ensure replying to an individual note by email creates a note with its own discussion ID.
- Fix OAuth, LDAP and SAML SSO when regular sign-ups are disabled.
- Fix usage ping docs link from empty cohorts page.
......
......@@ -17,6 +17,8 @@ gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.25.1.1'
gem 'faraday', '~> 0.11.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
......@@ -196,7 +198,7 @@ gem 'gemnasium-gitlab-service', '~> 0.2'
gem 'slack-notifier', '~> 1.5.1'
# Asana integration
gem 'asana', '~> 0.4.0'
gem 'asana', '~> 0.6.0'
# FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1'
......@@ -356,7 +358,7 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.16.2'
# OAuth
gem 'oauth2', '~> 1.2.0'
gem 'oauth2', '~> 1.3.0'
# Soft deletion
gem 'paranoia', '~> 2.2'
......
......@@ -47,7 +47,7 @@ GEM
akismet (2.0.0)
allocations (1.0.5)
arel (6.0.4)
asana (0.4.0)
asana (0.6.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0)
......@@ -214,10 +214,10 @@ GEM
factory_girl_rails (4.7.0)
factory_girl (~> 4.7.0)
railties (>= 3.0.0)
faraday (0.9.2)
faraday (0.11.0)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.10.0)
faraday (>= 0.7.4, < 0.10)
faraday_middleware (0.11.0.1)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.5)
aws-sdk (~> 2.1)
faraday (~> 0.9)
......@@ -490,15 +490,15 @@ GEM
mini_portile2 (~> 2.1.0)
numerizer (0.1.1)
oauth (0.5.1)
oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
oauth2 (1.3.1)
faraday (>= 0.8, < 0.12)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4)
oj (2.17.5)
omniauth (1.4.2)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
......@@ -752,7 +752,7 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (4.2.7)
sidekiq (4.2.10)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
......@@ -889,7 +889,7 @@ DEPENDENCIES
after_commit_queue (~> 1.3.0)
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.4.0)
asana (~> 0.6.0)
asciidoctor (~> 1.5.2)
asciidoctor-plantuml (= 0.0.7)
attr_encrypted (~> 3.0.0)
......@@ -931,6 +931,7 @@ DEPENDENCIES
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
faraday (~> 0.11.0)
faraday_middleware-aws-signers-v4
ffaker (~> 2.4)
flay (~> 2.8.0)
......@@ -988,7 +989,7 @@ DEPENDENCIES
net-ldap
net-ssh (~> 3.0.1)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0)
oauth2 (~> 1.3.0)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
......
......@@ -103,7 +103,7 @@ One small thing you also have to do when installing it yourself is to copy the e
cp config/unicorn.rb.example.development config/unicorn.rb
Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
Instructions on how to start GitLab and how to run the tests can be found in the [getting started section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#getting-started).
## Software stack
......
/* eslint-disable no-new */
import Vue from 'vue';
import VueResource from 'vue-resource';
import NotebookLab from 'vendor/notebooklab';
import notebookLab from '../../notebook/index.vue';
Vue.use(VueResource);
Vue.use(NotebookLab);
export default () => {
const el = document.getElementById('js-notebook-viewer');
......@@ -19,6 +18,9 @@ export default () => {
json: {},
};
},
components: {
notebookLab,
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
<div
......
......@@ -22,6 +22,7 @@ class PrometheusGraph {
const hasMetrics = $prometheusContainer.data('has-metrics');
this.docLink = $prometheusContainer.data('doc-link');
this.integrationLink = $prometheusContainer.data('prometheus-integration');
this.state = '';
$(document).ajaxError(() => {});
......@@ -38,8 +39,9 @@ class PrometheusGraph {
this.configureGraph();
this.init();
} else {
const prevState = this.state;
this.state = '.js-getting-started';
this.updateState();
this.updateState(prevState);
}
}
......@@ -53,26 +55,26 @@ class PrometheusGraph {
}
init() {
this.getData().then((metricsResponse) => {
return this.getData().then((metricsResponse) => {
let enoughData = true;
Object.keys(metricsResponse.metrics).forEach((key) => {
let currentKey;
if (key === 'cpu_values' || key === 'memory_values') {
currentKey = metricsResponse.metrics[key];
if (Object.keys(currentKey).length === 0) {
enoughData = false;
}
}
});
if (!enoughData) {
this.state = '.js-loading';
this.updateState();
if (typeof metricsResponse === 'undefined') {
enoughData = false;
} else {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const currentData = (metricsResponse.metrics[key])[0];
if (currentData.values.length <= 2) {
enoughData = false;
}
}
});
}
if (enoughData) {
$(prometheusStatesContainer).hide();
$(prometheusParentGraphContainer).show();
this.transformData(metricsResponse);
this.createGraph();
}
}).catch(() => {
new Flash('An error occurred when trying to load metrics. Please try again.');
});
}
......@@ -342,6 +344,8 @@ class PrometheusGraph {
getData() {
const maxNumberOfRequests = 3;
this.state = '.js-loading';
this.updateState();
return gl.utils.backOff((next, stop) => {
$.ajax({
url: metricsEndpoint,
......@@ -352,12 +356,11 @@ class PrometheusGraph {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < maxNumberOfRequests) {
next();
} else {
stop({
status: resp.status,
metrics: data,
});
} else if (this.backOffRequestCounter >= maxNumberOfRequests) {
stop(new Error('loading'));
}
} else if (!data.success) {
stop(new Error('loading'));
} else {
stop({
status: resp.status,
......@@ -373,8 +376,9 @@ class PrometheusGraph {
return resp.metrics;
})
.catch(() => {
const prevState = this.state;
this.state = '.js-unable-to-connect';
this.updateState();
this.updateState(prevState);
});
}
......@@ -382,19 +386,20 @@ class PrometheusGraph {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
if (metricValues !== undefined) {
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
}
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
}
});
}
updateState() {
updateState(prevState) {
const $statesContainer = $(prometheusStatesContainer);
$(prometheusParentGraphContainer).hide();
if (prevState) {
$(`${prevState}`, $statesContainer).addClass('hidden');
}
$(`${this.state}`, $statesContainer).removeClass('hidden');
$(prometheusStatesContainer).show();
}
......
<template>
<div class="cell">
<code-cell
type="input"
:raw-code="rawInputCode"
:count="cell.execution_count"
:code-css-class="codeCssClass" />
<output-cell
v-if="hasOutput"
:count="cell.execution_count"
:output="output"
:code-css-class="codeCssClass" />
</div>
</template>
<script>
import CodeCell from './code/index.vue';
import OutputCell from './output/index.vue';
export default {
components: {
'code-cell': CodeCell,
'output-cell': OutputCell,
},
props: {
cell: {
type: Object,
required: true,
},
codeCssClass: {
type: String,
required: false,
default: '',
},
},
computed: {
rawInputCode() {
if (this.cell.source) {
return this.cell.source.join('');
}
return '';
},
hasOutput() {
return this.cell.outputs.length;
},
output() {
return this.cell.outputs[0];
},
},
};
</script>
<style scoped>
.cell {
flex-direction: column;
}
</style>
<template>
<div :class="type">
<prompt
:type="promptType"
:count="count" />
<pre
class="language-python"
:class="codeCssClass"
ref="code"
v-text="code">
</pre>
</div>
</template>
<script>
import Prism from '../../lib/highlight';
import Prompt from '../prompt.vue';
export default {
components: {
prompt: Prompt,
},
props: {
count: {
type: Number,
required: false,
default: 0,
},
codeCssClass: {
type: String,
required: false,
default: '',
},
type: {
type: String,
required: true,
},
rawCode: {
type: String,
required: true,
},
},
computed: {
code() {
return this.rawCode;
},
promptType() {
const type = this.type.split('put')[0];
return type.charAt(0).toUpperCase() + type.slice(1);
},
},
mounted() {
Prism.highlightElement(this.$refs.code);
},
};
</script>
export { default as MarkdownCell } from './markdown.vue';
export { default as CodeCell } from './code.vue';
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
</div>
</template>
<script>
/* global katex */
import marked from 'marked';
import Prompt from './prompt.vue';
const renderer = new marked.Renderer();
/*
Regex to match KaTex blocks.
Supports the following:
\begin{equation}<math>\end{equation}
$$<math>$$
inline $<math>$
The matched text then goes through the KaTex renderer & then outputs the HTML
*/
const katexRegexString = `(
^\\\\begin{[a-zA-Z]+}\\s
|
^\\$\\$
|
\\s\\$(?!\\$)
)
(.+?)
(
\\s\\\\end{[a-zA-Z]+}$
|
\\$\\$$
|
\\$
)
`.replace(/\s/g, '').trim();
renderer.paragraph = (t) => {
let text = t;
let inline = false;
if (typeof katex !== 'undefined') {
const katexString = text.replace(/\\/g, '\\');
const matches = new RegExp(katexRegexString, 'gi').exec(katexString);
if (matches && matches.length > 0) {
if (matches[1].trim() === '$' && matches[3].trim() === '$') {
inline = true;
text = `${katexString.replace(matches[0], '')} ${katex.renderToString(matches[2])}`;
} else {
text = katex.renderToString(matches[2]);
}
}
}
return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
};
marked.setOptions({
sanitize: true,
renderer,
});
export default {
components: {
prompt: Prompt,
},
props: {
cell: {
type: Object,
required: true,
},
},
computed: {
markdown() {
return marked(this.cell.source.join(''));
},
},
};
</script>
<style>
.markdown .katex {
display: block;
text-align: center;
}
.markdown .inline-katex .katex {
display: inline;
text-align: initial;
}
</style>
<template>
<div class="output">
<prompt />
<div v-html="rawCode"></div>
</div>
</template>
<script>
import Prompt from '../prompt.vue';
export default {
props: {
rawCode: {
type: String,
required: true,
},
},
components: {
prompt: Prompt,
},
};
</script>
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
<script>
import Prompt from '../prompt.vue';
export default {
props: {
outputType: {
type: String,
required: true,
},
rawCode: {
type: String,
required: true,
},
},
components: {
prompt: Prompt,
},
};
</script>
<template>
<component :is="componentName"
type="output"
:outputType="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
</template>
<script>
import CodeCell from '../code/index.vue';
import Html from './html.vue';
import Image from './image.vue';
export default {
props: {
codeCssClass: {
type: String,
required: false,
default: '',
},
count: {
type: Number,
required: false,
default: 0,
},
output: {
type: Object,
requred: true,
},
},
components: {
'code-cell': CodeCell,
'html-output': Html,
'image-output': Image,
},
data() {
return {
outputType: '',
};
},
computed: {
componentName() {
if (this.output.text) {
return 'code-cell';
} else if (this.output.data['image/png']) {
this.outputType = 'image/png';
return 'image-output';
} else if (this.output.data['text/html']) {
this.outputType = 'text/html';
return 'html-output';
} else if (this.output.data['image/svg+xml']) {
this.outputType = 'image/svg+xml';
return 'html-output';
}
this.outputType = 'text/plain';
return 'code-cell';
},
rawCode() {
if (this.output.text) {
return this.output.text.join('');
}
return this.dataForType(this.outputType);
},
},
methods: {
dataForType(type) {
let data = this.output.data[type];
if (typeof data === 'object') {
data = data.join('');
}
return data;
},
},
};
</script>
<template>
<div class="prompt">
<span v-if="type && count">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
required: false,
},
count: {
type: Number,
required: false,
},
},
};
</script>
<style scoped>
.prompt {
padding: 0 10px;
min-width: 7em;
font-family: monospace;
}
</style>
<template>
<div v-if="hasNotebook">
<component
v-for="(cell, index) in cells"
:is="cellType(cell.cell_type)"
:cell="cell"
:key="index"
:code-css-class="codeCssClass" />
</div>
</template>
<script>
import {
MarkdownCell,
CodeCell,
} from './cells';
export default {
components: {
'code-cell': CodeCell,
'markdown-cell': MarkdownCell,
},
props: {
notebook: {
type: Object,
required: true,
},
codeCssClass: {
type: String,
required: false,
default: '',
},
},
methods: {
cellType(type) {
return `${type}-cell`;
},
},
computed: {
cells() {
if (this.notebook.worksheets) {
const data = {
cells: [],
};
return this.notebook.worksheets.reduce((cellData, sheet) => {
const cellDataCopy = cellData;
cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
return cellDataCopy;
}, data).cells;
}
return this.notebook.cells;
},
hasNotebook() {
return Object.keys(this.notebook).length;
},
},
};
</script>
<style>
.cell,
.input,
.output {
display: flex;
width: 100%;
margin-bottom: 10px;
}
.cell pre {
margin: 0;
width: 100%;
}
</style>
import Prism from 'prismjs';
import 'prismjs/components/prism-python';
import 'prismjs/plugins/custom-class/prism-custom-class';
Prism.plugins.customClass.map({
comment: 'c',
error: 'err',
operator: 'o',
constant: 'kc',
namespace: 'kn',
keyword: 'k',
string: 's',
number: 'm',
'attr-name': 'na',
builtin: 'nb',
entity: 'ni',
function: 'nf',
tag: 'nt',
variable: 'nv',
});
export default Prism;
......@@ -33,6 +33,7 @@
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
$dropdown = $(dropdown);
options.projectId = $dropdown.data('project-id');
options.groupId = $dropdown.data('group-id');
options.showCurrentUser = $dropdown.data('current-user');
options.todoFilter = $dropdown.data('todo-filter');
options.todoStateFilter = $dropdown.data('todo-state-filter');
......
......@@ -108,8 +108,7 @@
}
.award-control {
margin: 3px 5px 3px 0;
padding: .35em .4em;
margin-right: 5px;
outline: 0;
&.disabled {
......
......@@ -70,7 +70,7 @@ pre {
}
hr {
margin: $gl-padding 0;
margin: 24px 0;
border-top: 1px solid darken($gray-normal, 8%);
}
......
......@@ -73,14 +73,6 @@
&.wiki {
padding: 30px $gl-padding;
.highlight {
margin-bottom: 9px;
> pre {
margin: 0;
}
}
}
&.blob-no-preview {
......
......@@ -4,7 +4,7 @@
padding: 0;
.timeline-entry {
padding: $gl-padding $gl-btn-padding 14px;
padding: $gl-padding $gl-btn-padding 0;
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
......
......@@ -8,6 +8,13 @@
img {
max-width: 100%;
margin: 0 0 8px;
}
p a:not(.no-attachment-icon) img {
// Remove bottom padding because
// <p> already has $gl-padding bottom
margin-bottom: 0;
}
*:first-child:not(.katex-display) {
......@@ -47,44 +54,50 @@
h1 {
font-size: 1.75em;
font-weight: 600;
margin: 16px 0 10px;
padding: 0 0 0.3em;
margin: 24px 0 16px;
padding-bottom: 0.3em;
border-bottom: 1px solid $white-dark;
color: $gl-text-color;
&:first-child {
margin-top: 0;
}
}
h2 {
font-size: 1.5em;
font-weight: 600;
margin: 16px 0 10px;
margin: 24px 0 16px;
padding-bottom: 0.3em;
border-bottom: 1px solid $white-dark;
color: $gl-text-color;
}
h3 {
margin: 16px 0 10px;
margin: 24px 0 16px;
font-size: 1.3em;
}
h4 {
margin: 16px 0 10px;
margin: 24px 0 16px;
font-size: 1.2em;
}
h5 {
margin: 16px 0 10px;
margin: 24px 0 16px;
font-size: 1em;
}
h6 {
margin: 16px 0 10px;
margin: 24px 0 16px;
font-size: 0.95em;
}
blockquote {
color: $gl-grayish-blue;
font-size: inherit;
padding: 8px 21px;
margin: 12px 0;
padding: 8px 24px;
margin: 16px 0;
border-left: 3px solid $white-dark;
}
......@@ -95,19 +108,20 @@
blockquote p {
color: $gl-grayish-blue !important;
margin: 0;
font-size: inherit;
line-height: 1.5;
}
p {
color: $gl-text-color;
margin: 6px 0 0;
margin: 0 0 16px;
}
table {
@extend .table;
@extend .table-bordered;
margin: 12px 0;
margin: 16px 0;
color: $gl-text-color;
th {
......@@ -120,7 +134,7 @@
}
pre {
margin: 12px 0;
margin-bottom: 16px;
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
......@@ -134,7 +148,7 @@
ul,
ol {
padding: 0;
margin: 3px 0 !important;
margin: 0 0 16px !important;
}
ul:dir(rtl),
......
......@@ -29,11 +29,5 @@
.description {
margin-top: 6px;
p {
&:last-child {
margin-bottom: 0;
}
}
}
}
......@@ -52,7 +52,7 @@
.title {
padding: 0;
margin: 0;
margin-bottom: 16px;
border-bottom: none;
}
......@@ -357,6 +357,8 @@
}
.detail-page-description {
padding: 16px 0 0;
small {
color: $gray-darkest;
}
......@@ -364,6 +366,8 @@
.edited-text {
color: $gray-darkest;
display: block;
margin: 0 0 16px;
.author_link {
color: $gray-darkest;
......
......@@ -28,7 +28,7 @@
.note-edit-form {
.note-form-actions {
position: relative;
margin-top: $gl-padding;
margin: $gl-padding 0;
}
.note-preview-holder {
......@@ -387,6 +387,7 @@
@media (max-width: $screen-xs-max) {
display: flex;
width: 100%;
margin-bottom: 10px;
.comment-btn {
flex-grow: 1;
......
......@@ -102,13 +102,12 @@ ul.notes {
.note-awards {
.js-awards-block {
padding: 2px;
margin-top: 10px;
margin-bottom: 16px;
}
}
.note-header {
padding-bottom: 3px;
padding-bottom: 8px;
padding-right: 20px;
@media (min-width: $screen-sm-min) {
......@@ -151,6 +150,10 @@ ul.notes {
margin-left: 65px;
}
.note-header {
padding-bottom: 0;
}
&.timeline-entry::after {
clear: none;
}
......@@ -386,6 +389,10 @@ ul.notes {
.note-headline-meta {
display: inline-block;
white-space: nowrap;
.system-note-message {
white-space: normal;
}
}
/**
......
......@@ -28,7 +28,7 @@ class Admin::GroupsController < Admin::ApplicationController
if @group.save
@group.add_owner(current_user)
redirect_to [:admin, @group], notice: 'Group was successfully created.'
redirect_to [:admin, @group], notice: "Group '#{@group.name}' was successfully created."
else
render "new"
end
......
......@@ -23,6 +23,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format|
format.html do
@project_namespace = @project.namespace.becomes(Namespace)
@milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page])
end
......
......@@ -6,7 +6,7 @@
# current_user - which user use
# params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all'
# state: 'open' or 'closed' or 'all'
# state: 'open', 'closed', 'merged', or 'all'
# group_id: integer
# project_id: integer
# milestone_title: string
......
##
# DEPRECATED
#
# These helpers are deprecated in favor of detailed CI/CD statuses.
#
# See 'detailed_status?` method and `Gitlab::Ci::Status` module.
#
module CiStatusHelper
def ci_status_path(pipeline)
project = pipeline.project
namespace_project_pipeline_path(project.namespace, project, pipeline)
end
# Is used by Commit and Merge Request Widget
def ci_label_for_status(status)
if detailed_status?(status)
return status.label
......@@ -22,6 +28,23 @@ module CiStatusHelper
end
end
def ci_text_for_status(status)
if detailed_status?(status)
return status.text
end
case status
when 'success'
'passed'
when 'success_with_warnings'
'passed'
when 'manual'
'blocked'
else
status
end
end
def ci_status_for_statuseable(subject)
status = subject.try(:status) || 'not found'
status.humanize
......
......@@ -7,6 +7,11 @@ module IconsHelper
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
if (options.keys & %w[aria-hidden aria-label]).empty?
# Add `aria-hidden` if there are no aria's set
options['aria-hidden'] = true
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
end
......
......@@ -56,11 +56,12 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
# Sorting based on the `#123` or `group/project#123` reference will sort
# local issues first.
issues.map do |issue|
# Issuable sorter will sort local issues, then issues from the same
# namespace, then all other issues.
issues = Gitlab::IssuableSorter.sort(@project, issues).map do |issue|
issue.to_reference(@project)
end.sort.to_sentence
end
issues.to_sentence
end
def mr_closes_issues
......
......@@ -75,29 +75,32 @@ module Ci
pipeline.update_duration
end
before_transition any => [:manual] do |pipeline|
pipeline.update_duration
end
before_transition canceled: any - [:canceled] do |pipeline|
pipeline.auto_canceled_by = nil
end
after_transition [:created, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) }
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
after_transition any => [:success] do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) }
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
after_transition [:created, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) }
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
end
after_transition do |pipeline, transition|
next if transition.loopback?
pipeline.run_after_commit do
PipelineHooksWorker.perform_async(id)
Ci::ExpirePipelineCacheService.new(project, nil)
.execute(pipeline)
PipelineHooksWorker.perform_async(pipeline.id)
ExpirePipelineCacheWorker.perform_async(pipeline.id)
end
end
......@@ -385,6 +388,11 @@ module Ci
.select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||= project.merge_requests.where(source_branch: ref)
end
def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory
.new(self, current_user)
......
......@@ -163,7 +163,20 @@ module Routable
end
end
# Every time `project.namespace.becomes(Namespace)` is called for polymorphic_path,
# a new instance is instantiated, and we end up duplicating the same query to retrieve
# the route. Caching this per request ensures that even if we have multiple instances,
# we will not have to duplicate work, avoiding N+1 queries in some cases.
def full_path
return uncached_full_path unless RequestStore.active?
key = "routable/full_path/#{self.class.name}/#{self.id}"
RequestStore[key] ||= uncached_full_path
end
private
def uncached_full_path
if route && route.path.present?
@full_path ||= route.path
else
......@@ -173,8 +186,6 @@ module Routable
end
end
private
def full_name_changed?
name_changed? || parent_changed?
end
......
......@@ -10,4 +10,8 @@ class IndividualNoteDiscussion < Discussion
def individual_note?
true
end
def reply_attributes
super.tap { |attrs| attrs.delete(:discussion_id) }
end
end
......@@ -198,22 +198,23 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.raw_diffs(*args) : compare.raw_diffs(*args)
end
def diffs(diff_options = nil)
def diffs(diff_options = {})
if compare
compare.diffs(diff_options)
# When saving MR diffs, `no_collapse` is implicitly added (because we need
# to save the entire contents to the DB), so add that here for
# consistency.
compare.diffs(diff_options.merge(no_collapse: true))
else
merge_request_diff.diffs(diff_options)
end
end
def diff_size
# The `#diffs` method ends up at an instance of a class inheriting from
# `Gitlab::Diff::FileCollection::Base`, so use those options as defaults
# here too, to get the same diff size without performing highlighting.
#
opts = Gitlab::Diff::FileCollection::Base.default_options.merge(diff_options || {})
# Calling `merge_request_diff.diffs.real_size` will also perform
# highlighting, which we don't need here.
return real_size if merge_request_diff
raw_diffs(opts).size
diffs.real_size
end
def diff_base_commit
......
......@@ -260,7 +260,7 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
new_attributes[:real_size] = compare.diffs.real_size
new_attributes[:real_size] = diff_collection.real_size
if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
......
......@@ -15,8 +15,12 @@ class OutOfContextDiscussion < Discussion
def self.override_discussion_id(note)
discussion_id(note)
end
def self.note_class
Note
end
def reply_attributes
super.tap { |attrs| attrs.delete(:discussion_id) }
end
end
......@@ -4,21 +4,14 @@ class ProjectPolicy < BasePolicy
def rules
team_access!(user)
owner = project.owner == user ||
(project.group && project.group.has_owner?(user))
owner_access! if user.admin? || owner
owner_access! if user.admin? || owner?
auditor_access! if user.auditor?
team_member_owner_access! if owner
team_member_owner_access! if owner?
if project.public? || (project.internal? && !user.external?)
guest_access!
public_access!
if project.request_access_enabled &&
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access
end
can! :request_access if access_requestable?
end
archived_access! if project.archived?
......@@ -33,6 +26,13 @@ class ProjectPolicy < BasePolicy
@subject
end
def owner?
return @owner if defined?(@owner)
@owner = project.owner == user ||
(project.group && project.group.has_owner?(user))
end
def guest_access!
can! :read_project
can! :read_board
......@@ -251,14 +251,6 @@ class ProjectPolicy < BasePolicy
disabled_features!
end
def project_group_member?(user)
project.group &&
(
project.group.members_with_parents.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id)
)
end
def block_issues_abilities
unless project.feature_available?(:issues, user)
cannot! :read_issue if project.default_issues_tracker?
......@@ -279,6 +271,22 @@ class ProjectPolicy < BasePolicy
private
def project_group_member?(user)
project.group &&
(
project.group.members_with_parents.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id)
)
end
def access_requestable?
project.request_access_enabled &&
!owner? &&
!user.admin? &&
!project.team.member?(user) &&
!project_group_member?(user)
end
# A base set of abilities for read-only users, which
# is then augmented as necessary for anonymous and other
# read-only users.
......
module Ci
class ExpirePipelineCacheService < BaseService
attr_reader :pipeline
def execute(pipeline)
@pipeline = pipeline
store = Gitlab::EtagCaching::Store.new
store.touch(project_pipelines_path)
store.touch(commit_pipelines_path) if pipeline.commit
store.touch(new_merge_request_pipelines_path)
merge_requests_pipelines_paths.each { |path| store.touch(path) }
Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(@pipeline)
end
private
def project_pipelines_path
Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
project.namespace,
project,
format: :json)
end
def commit_pipelines_path
Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
project.namespace,
project,
pipeline.commit.id,
format: :json)
end
def new_merge_request_pipelines_path
Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path(
project.namespace,
project,
format: :json)
end
def merge_requests_pipelines_paths
pipeline.merge_requests.collect do |merge_request|
Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
project.namespace,
project,
merge_request,
format: :json)
end
end
end
end
......@@ -6,18 +6,16 @@ module Users
@params = params.dup
end
def execute
raise Gitlab::Access::AccessDeniedError unless can_create_user?
def execute(skip_authorization: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_create_user?
user = User.new(build_user_params)
user_params = build_user_params(skip_authorization: skip_authorization)
user = User.new(user_params)
if current_user&.admin?
if params[:reset_password]
user.generate_reset_token
params[:force_random_password] = true
end
@reset_token = user.generate_reset_token if params[:reset_password]
if params[:force_random_password]
if user_params[:force_random_password]
random_password = Devise.friendly_token.first(Devise.password_length.min)
user.password = user.password_confirmation = random_password
end
......@@ -81,7 +79,7 @@ module Users
]
end
def build_user_params
def build_user_params(skip_authorization:)
if current_user&.admin?
user_params = params.slice(*admin_create_params)
user_params[:created_by_id] = current_user&.id
......@@ -90,11 +88,20 @@ module Users
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
else
user_params = params.slice(*signup_params)
user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
allowed_signup_params = signup_params
allowed_signup_params << :skip_confirmation if skip_authorization
user_params = params.slice(*allowed_signup_params)
if user_params[:skip_confirmation].nil?
user_params[:skip_confirmation] = skip_user_confirmation_email_from_setting
end
end
user_params
end
def skip_user_confirmation_email_from_setting
!current_application_settings.send_user_confirmation_email
end
end
end
......@@ -6,8 +6,8 @@ module Users
@params = params.dup
end
def execute
user = Users::BuildService.new(current_user, params).execute
def execute(skip_authorization: false)
user = Users::BuildService.new(current_user, params).execute(skip_authorization: skip_authorization)
@reset_token = user.generate_reset_token if user.recently_sent_password_reset?
......
......@@ -15,27 +15,39 @@ module Users
end
def execute
# Block the user before moving records to prevent a data race.
# For example, if the user creates an issue after `migrate_issues`
# runs and before the user is destroyed, the destroy will fail with
# an exception.
user.block
transition = user.block_transition
user.transaction do
# Block the user before moving records to prevent a data race.
# For example, if the user creates an issue after `migrate_issues`
# runs and before the user is destroyed, the destroy will fail with
# an exception.
user.block
# Reverse the user block if record migration fails
if !migrate_records && transition
transition.rollback
user.save!
end
end
user.reload
end
private
def migrate_records
user.transaction(requires_new: true) do
@ghost_user = User.ghost
migrate_issues
migrate_merge_requests
migrate_notes
migrate_abuse_reports
migrate_award_emoji
migrate_award_emojis
end
user.reload
end
private
def migrate_issues
user.issues.update_all(author_id: ghost_user.id)
end
......@@ -52,7 +64,7 @@ module Users
user.reported_abuse_reports.update_all(reporter_id: ghost_user.id)
end
def migrate_award_emoji
def migrate_award_emojis
user.award_emoji.update_all(user_id: ghost_user.id)
end
end
......
......@@ -9,7 +9,7 @@
.bs-callout.bs-callout-warning.clearfix
%p
User cohorts are only shown when the
= link_to 'usage ping', help_page_path('user/admin_area/usage_statistics'), target: '_blank'
= link_to 'usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping'), target: '_blank'
is enabled. To enable it and see user cohorts,
visit
= succeed '.' do
......
......@@ -56,7 +56,7 @@
Snippets
- if project_nav_tab? :settings
= nav_link(path: %w[projects#edit members#show integrations#show repository#show ci_cd#show pages#show]) do
= nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
= link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
......
- ref = local_assigns.fetch(:ref)
- status = commit.status(ref)
- if status
= link_to pipelines_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{status}" do
= ci_icon_for_status(status)
= ci_label_for_status(status)
= ci_text_for_status(status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
......
......@@ -50,7 +50,7 @@
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
.issue-details.issuable-details
.detail-page-description.content-block{ class: ('hide-bottom-border' unless @issue.description.present? ) }
.detail-page-description.content-block
.issue-title-data.hidden{ "data" => { "initial-title" => markdown_field(@issue, :title),
"endpoint" => rendered_title_namespace_project_issue_path(@project.namespace, @project, @issue),
} }
......
......@@ -36,7 +36,7 @@
%a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
.detail-page-description.milestone-detail{ class: ('hide-bottom-border' unless @milestone.description.present? ) }
.detail-page-description.milestone-detail
%h2.title
= markdown_field(@milestone, :title)
%div
......
- page_title @service.title, "Services"
= render "projects/settings/head"
= render 'form'
......@@ -14,7 +14,7 @@
%span
Members
- if can_edit
= nav_link(controller: :integrations) do
= nav_link(controller: [:integrations, :services]) do
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
Integrations
......
......@@ -22,7 +22,7 @@
- if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id])
= dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
= render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, board: board, show_started: true
......
-# @project is present when viewing Project's milestone
- project = @project || issuable.project
- namespace = @project_namespace || project.namespace.becomes(Namespace)
- assignee = issuable.assignee
- issuable_type = issuable.class.table_name
- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
- base_url_args = [namespace, project]
- issuable_type_args = base_url_args + [issuable_type]
- issuable_url_args = base_url_args + [issuable]
- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable)
%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'is-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-id' => issuable.id, 'data-url' => polymorphic_path(issuable) }
%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'is-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-id' => issuable.id, 'data-url' => polymorphic_path(issuable_url_args) }
%span
- if show_project_name
%strong #{project.name} &middot;
......@@ -13,17 +16,17 @@
%strong #{project.name_with_namespace} &middot;
- if issuable.is_a?(Issue)
= confidential_icon(issuable)
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
= link_to_gfm issuable.title, issuable_url_args, title: issuable.title
.issuable-detail
= link_to [project.namespace.becomes(Namespace), project, issuable] do
%span.issuable-number= issuable.to_reference
- issuable.labels.each do |label|
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- render_colored_label(label)
%span.assignee-icon
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
class ExpirePipelineCacheWorker
include Sidekiq::Worker
include PipelineQueue
def perform(pipeline_id)
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
return unless pipeline
project = pipeline.project
store = Gitlab::EtagCaching::Store.new
store.touch(project_pipelines_path(project))
store.touch(commit_pipelines_path(project, pipeline.commit)) if pipeline.commit
store.touch(new_merge_request_pipelines_path(project))
each_pipelines_merge_request_path(project, pipeline) do |path|
store.touch(path)
end
Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
end
private
def project_pipelines_path(project)
Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
project.namespace,
project,
format: :json)
end
def commit_pipelines_path(project, commit)
Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
project.namespace,
project,
commit.id,
format: :json)
end
def new_merge_request_pipelines_path(project)
Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path(
project.namespace,
project,
format: :json)
end
def each_pipelines_merge_request_path(project, pipeline)
pipeline.all_merge_requests.each do |merge_request|
path = Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
project.namespace,
project,
merge_request,
format: :json)
yield(path)
end
end
end
---
title: Database SSL support for backup script.
merge_request: 9715
author: Guillaume Simon
---
title: Change issues list in MR to natural sorting
merge_request: 7110
author: Jeff Stubler
---
title: Show group name on flash container when group is created from Admin area.
merge_request: 10905
author:
---
title: Add issues/:iid/closed_by api endpoint
merge_request:
author: mhasbini
---
title: Cleanup markdown spacing
merge_request:
author:
---
title: Decrease ABC threshold to 57.08
merge_request: 10724
author: Rydkin Maxim
---
title: Remove unnecessary test helpers includes
merge_request: 10567
author: Jacopo Beschi @jacopo-beschi
---
title: Add Slack slash command api to services documentation and rearrange order and
cases
merge_request: 10757
author: TM Lee
---
title: Don't display the is_admin flag in most API responses
merge_request: 10846
author:
---
title: Allow OAuth clients to push code
merge_request: 10677
author:
---
title: Decrease Cyclomatic Complexity threshold to 16
merge_request: 10928
author: Rydkin Maxim
---
title: Fixes an issue preventing screen readers from reading some icons
merge_request:
author:
---
title: Add index on ci_builds.user_id
merge_request: 10874
author: blackst0ne
---
title: Fixed spacing of discussion submit buttons
merge_request:
author:
---
title: Fix commenting on an existing discussion on an unchanged line that is no longer
in the diff
merge_request:
author:
---
title: Add index to webhooks type column
merge_request:
author:
---
title: Fix dead link to GDK on the README page
merge_request:
author: Dino Maric
---
title: Fixed group issues assignee dropdown loading all users
merge_request:
author:
---
title: Fixed Prometheus monitoring graphs not showing empty states in certain scenarios
merge_request:
author:
---
title: 'API: Filter merge requests by milestone and labels'
merge_request: Robert Schilling
author: 10924
---
title: Show sizes correctly in merge requests when diffs overflow
merge_request:
author:
---
title: Fix updating merge_when_build_succeeds via merge API endpoint
merge_request: 10873
author:
---
title: Upgrade Sidekiq to 4.2.10
merge_request:
author:
---
title: Cache Routable#full_path in RequestStore to reduce duplicate route loads
merge_request:
author:
......@@ -592,6 +592,11 @@ production: &base
# If you use non-standard ssh port you need to specify it
# ssh_port: 22
workhorse:
# File that contains the secret key for verifying access for gitlab-workhorse.
# Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_workhorse_secret
## Git settings
# CAUTION!
# Use the default values unless you really know what you are doing
......
......@@ -463,6 +463,12 @@ Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user
Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user
Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.__send__(:build_gitlab_shell_ssh_path_prefix)
#
# Workhorse
#
Settings['workhorse'] ||= Settingslogic.new({})
Settings.workhorse['secret_file'] ||= Rails.root.join('.gitlab_workhorse_secret')
#
# Repositories
#
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexOnCiBuildsUpdatedAt < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_builds, :updated_at
end
def down
remove_concurrent_index :ci_builds, :updated_at if index_exists?(:ci_builds, :updated_at)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexOnCiBuildsUserId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_builds, :user_id
end
def down
remove_concurrent_index :ci_builds, :user_id if index_exists?(:ci_builds, :user_id)
end
end
class AddIndexToWebHooksType < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :web_hooks, :type
end
def down
remove_concurrent_index :web_hooks, :type
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170421113144) do
ActiveRecord::Schema.define(version: 20170424142900) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -289,6 +289,8 @@ ActiveRecord::Schema.define(version: 20170421113144) do
add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
create_table "ci_pipelines", force: :cascade do |t|
t.string "ref"
......@@ -1577,6 +1579,7 @@ ActiveRecord::Schema.define(version: 20170421113144) do
end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
add_foreign_key "approver_groups", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "boards", "projects"
......
# Gitaly
[Gitaly](https://gitlab.com/gitlab-org/gitlay) (introduced in GitLab
[Gitaly](https://gitlab.com/gitlab-org/gitaly) (introduced in GitLab
9.0) is a service that provides high-level RPC access to Git
repositories. As of GitLab 9.1 it is still an optional component with
limited scope.
......
......@@ -48,7 +48,6 @@ Example of response
"bio": null,
"created_at": "2016-08-11T07:09:20.351Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"location": null,
"name": "Administrator",
......@@ -106,7 +105,6 @@ Example of response
"bio": null,
"created_at": "2016-08-11T07:09:20.351Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"location": null,
"name": "Administrator",
......@@ -195,7 +193,6 @@ Example of response
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/root",
"created_at": "2016-08-11T07:09:20.351Z",
"is_admin": true,
"bio": null,
"location": null,
"skype": "",
......
......@@ -834,6 +834,67 @@ Example response:
}
```
## List merge requests that will close issue on merge
Get all the merge requests that will close issue when merged.
```
GET /projects/:id/issues/:issue_iid/closed_by
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `issue_iid` | integer | yes | The internal ID of a project issue |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/11/closed_by
```
Example response:
```json
[
{
"id": 6471,
"iid": 6432,
"project_id": 1,
"title": "add a test for cgi lexer options",
"description": "closes #11",
"state": "opened",
"created_at": "2017-04-06T18:33:34.168Z",
"updated_at": "2017-04-09T20:10:24.983Z",
"target_branch": "master",
"source_branch": "feature.custom-highlighting",
"upvotes": 0,
"downvotes": 0,
"author": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
},
"assignee": null,
"source_project_id": 1,
"target_project_id": 1,
"labels": [],
"work_in_progress": false,
"milestone": null,
"merge_when_pipeline_succeeds": false,
"merge_status": "unchecked",
"sha": "5a62481d563af92b8e32d735f2fa63b94e806835",
"merge_commit_sha": null,
"user_notes_count": 1,
"should_remove_source_branch": null,
"force_remove_source_branch": false,
"web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/merge_requests/6432"
}
]
```
## Comments on issues
Comments are done via the [notes](notes.md) resource.
......@@ -57,7 +57,6 @@ Example of response
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
......@@ -101,7 +100,6 @@ Example of response
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
......@@ -120,7 +118,7 @@ Example of response
Get a list of jobs for a pipeline.
```
GET /projects/:id/pipeline/:pipeline_id/jobs
GET /projects/:id/pipelines/:pipeline_id/jobs
```
| Attribute | Type | Required | Description |
......@@ -173,7 +171,6 @@ Example of response
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
......@@ -217,7 +214,6 @@ Example of response
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
......@@ -284,7 +280,6 @@ Example of response
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
......
......@@ -26,7 +26,6 @@ Parameters:
"avatar_url": "http://www.gravatar.com/avatar/cfa35b8cd2ec278026357769582fa563?s=40\u0026d=identicon",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2015-09-03T07:24:01.670Z",
"is_admin": false,
"bio": null,
"skype": "",
"linkedin": "",
......
......@@ -11,15 +11,21 @@ GET /projects/:id/merge_requests
GET /projects/:id/merge_requests?state=opened
GET /projects/:id/merge_requests?state=all
GET /projects/:id/merge_requests?iids[]=42&iids[]=43
GET /projects/:id/merge_requests?milestone=release
GET /projects/:id/merge_requests?labels=bug,reproduced
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `iid` (optional) - Return the request having the given `iid`
- `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed`
- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `iids` | Array[integer] | no | Return the request having the given `iid` |
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged`|
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
| `milestone` | string | no | Return merge requests for a specific milestone |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
```json
[
......
......@@ -869,6 +869,17 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `file` | string | yes | The file to be uploaded |
To upload a file from your filesystem, use the `--form` argument. This causes
cURL to post data using the header `Content-Type: multipart/form-data`.
The `file=` parameter must point to a file on your filesystem and be preceded
by `@`. For example:
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "file=@dk.png" https://gitlab.example.com/api/v3/projects/5/uploads
```
Returned object:
```json
{
"alt": "dk",
......@@ -878,8 +889,8 @@ Parameters:
```
**Note**: The returned `url` is relative to the project path.
In Markdown contexts, the link is automatically expanded when the format in `markdown` is used.
In Markdown contexts, the link is automatically expanded when the format in
`markdown` is used.
## Project members
......
......@@ -490,41 +490,98 @@ Remove all previously JIRA settings from a project.
DELETE /projects/:id/services/jira
```
## Mattermost Slash Commands
## Slack slash commands
Ability to receive slash commands from a Mattermost chat instance.
Ability to receive slash commands from a Slack chat instance.
### Create/Edit Mattermost Slash Command service
### Get Slack slash command service settings
Set Mattermost Slash Command for a project.
Get Slack slash command service settings for a project.
```
PUT /projects/:id/services/mattermost-slash-commands
GET /projects/:id/services/slack-slash-commands
```
Example response:
```json
{
"id": 4,
"title": "Slack slash commands",
"created_at": "2017-06-27T05:51:39-07:00",
"updated_at": "2017-06-27T05:51:39-07:00",
"active": true,
"push_events": true,
"issues_events": true,
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
"build_events": true,
"pipeline_events": true,
"properties": {
"token": "9koXpg98eAheJpvBs5tK"
}
}
```
### Create/Edit Slack slash command service
Set Slack slash command for a project.
```
PUT /projects/:id/services/slack-slash-commands
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `token` | string | yes | The Mattermost token |
| `token` | string | yes | The Slack token |
### Delete Mattermost Slash Command service
### Delete Slack slash command service
Delete Mattermost Slash Command service for a project.
Delete Slack slash command service for a project.
```
DELETE /projects/:id/services/mattermost-slash-commands
DELETE /projects/:id/services/slack-slash-commands
```
### Get Mattermost Slash Command service settings
## Mattermost slash commands
Ability to receive slash commands from a Mattermost chat instance.
### Get Mattermost slash command service settings
Get Mattermost Slash Command service settings for a project.
Get Mattermost slash command service settings for a project.
```
GET /projects/:id/services/mattermost-slash-commands
```
### Create/Edit Mattermost slash command service
Set Mattermost slash command for a project.
```
PUT /projects/:id/services/mattermost-slash-commands
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `token` | string | yes | The Mattermost token |
### Delete Mattermost slash command service
Delete Mattermost slash command service for a project.
```
DELETE /projects/:id/services/mattermost-slash-commands
```
## Pipeline-Emails
Get emails for GitLab CI pipelines.
......
......@@ -64,7 +64,6 @@ GET /users
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
......@@ -97,7 +96,6 @@ GET /users
"avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
"web_url": "http://localhost:3000/jack_smith",
"created_at": "2012-05-23T08:01:01Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
......@@ -171,7 +169,6 @@ Parameters:
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
......@@ -202,7 +199,6 @@ Parameters:
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
......@@ -327,7 +323,6 @@ GET /user
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
......
......@@ -34,7 +34,6 @@
## Backend howtos
- [Architecture](architecture.md) of GitLab
- [CI setup](ci_setup.md) for testing GitLab
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
......
# CI setup
This document describes what services we use for testing GitLab and GitLab CI.
We currently use four CI services to test GitLab:
1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
4. [Mock CI Service](../user/project/integrations/mock_ci.md) for local development
| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore |
|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------|
| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | |
| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) |
| GitLab EE @ MySQL | ✓ | | |
| GitLab CI @ MySQL | ✓ | | |
| GitLab CI @ PostgreSQL | | | ✓ |
| GitLab CI Runner | ✓ | | ✓ |
| GitLab Shell | ✓ | | ✓ |
| GitLab Shell | ✓ | | ✓ |
Core team has access to trigger builds if needed for GitLab CE.
We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) for testing with GitLab CI.
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
- Language: Ruby
- Ruby version: 2.1.8
- database.yml: pg
Build commands
```bash
sudo apt-get install cmake libicu-dev -y (Setup)
bundle install --deployment --path vendor/bundle (Setup)
cp config/gitlab.yml.example config/gitlab.yml (Setup)
bundle exec rake db:create (Setup)
bundle exec rake spinach (Thread #1)
bundle exec rake spec (thread #2)
bundle exec rake rubocop (thread #3)
bundle exec rake brakeman (thread #4)
bundle exec rake jasmine:ci (thread #5)
```
Use rubygems mirror.
......@@ -75,7 +75,7 @@ sharing a Merge Request with a reviewer or a maintainer.
1. Follow the steps in [Vue.js Best Practices](vue.md)
1. Follow the style guide.
1. Only a handful of people are allowed to merge Vue related features.
Reach out to @jschatz, @iamphill, @fatihacet or @filipa early in this process.
Reach out to one of Vue experts early in this process.
---
......
......@@ -14,8 +14,10 @@ for more information on general testing practices at GitLab.
GitLab uses the [Karma][karma] test runner with [Jasmine][jasmine] as its test
framework for our JavaScript unit tests. For tests that rely on DOM
manipulation we use fixtures which are pre-compiled from HAML source files and
served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
manipulation, we generate HTML files using RSpec suites (see `spec/javascripts/fixtures/*.rb` for examples).
Some fixtures are still HAML templates that are translated to HTML files using the same mechanism (see `static_fixtures.rb`).
Those will be migrated over time.
Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
JavaScript tests live in `spec/javascripts/`, matching the folder structure
of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js`
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment