Commit a582ee2f authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge branch 'master' into droplab-templating-xss-fix

parents b34534b6 cd041082
This diff is collapsed.
......@@ -16,47 +16,44 @@ const defaults = {
class BlobForkSuggestion {
constructor(options) {
this.elementMap = Object.assign({}, defaults, options);
this.onClickWrapper = this.onClick.bind(this);
document.addEventListener('click', this.onClickWrapper);
this.onOpenButtonClick = this.onOpenButtonClick.bind(this);
this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
}
showSuggestionSection(forkPath, action = 'edit') {
[].forEach.call(this.elementMap.suggestionSections, (suggestionSection) => {
suggestionSection.classList.remove('hidden');
});
init() {
this.bindEvents();
[].forEach.call(this.elementMap.forkButtons, (forkButton) => {
forkButton.setAttribute('href', forkPath);
});
return this;
}
[].forEach.call(this.elementMap.actionTextPieces, (actionTextPiece) => {
// eslint-disable-next-line no-param-reassign
actionTextPiece.textContent = action;
});
bindEvents() {
$(this.elementMap.openButtons).on('click', this.onOpenButtonClick);
$(this.elementMap.cancelButtons).on('click', this.onCancelButtonClick);
}
hideSuggestionSection() {
[].forEach.call(this.elementMap.suggestionSections, (suggestionSection) => {
suggestionSection.classList.add('hidden');
});
showSuggestionSection(forkPath, action = 'edit') {
$(this.elementMap.suggestionSections).removeClass('hidden');
$(this.elementMap.forkButtons).attr('href', forkPath);
$(this.elementMap.actionTextPieces).text(action);
}
onClick(e) {
const el = e.target;
hideSuggestionSection() {
$(this.elementMap.suggestionSections).addClass('hidden');
}
if ([].includes.call(this.elementMap.openButtons, el)) {
const { forkPath, action } = el.dataset;
onOpenButtonClick(e) {
const forkPath = $(e.currentTarget).attr('data-fork-path');
const action = $(e.currentTarget).attr('data-action');
this.showSuggestionSection(forkPath, action);
}
if ([].includes.call(this.elementMap.cancelButtons, el)) {
onCancelButtonClick() {
this.hideSuggestionSection();
}
}
destroy() {
document.removeEventListener('click', this.onClickWrapper);
$(this.elementMap.openButtons).off('click', this.onOpenButtonClick);
$(this.elementMap.cancelButtons).off('click', this.onCancelButtonClick);
}
}
......
/* 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
......
......@@ -97,7 +97,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
});
})
.init();
}
switch (page) {
......
<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;
......@@ -387,6 +387,7 @@
@media (max-width: $screen-xs-max) {
display: flex;
width: 100%;
margin-bottom: 10px;
.comment-btn {
flex-grow: 1;
......
......@@ -386,6 +386,10 @@ ul.notes {
.note-headline-meta {
display: inline-block;
white-space: nowrap;
.system-note-message {
white-space: normal;
}
}
/**
......
......@@ -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
......
##
# 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
......
......@@ -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
......@@ -19,4 +19,8 @@ class OutOfContextDiscussion < Discussion
def self.note_class
Note
end
def reply_attributes
super.tap { |attrs| attrs.delete(:discussion_id) }
end
end
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
- 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"
......
-# @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: Remove unnecessary test helpers includes
merge_request: 10567
author: Jacopo Beschi @jacopo-beschi
---
title: Allow OAuth clients to push code
merge_request: 10677
author:
---
title: Fixes an issue preventing screen readers from reading some icons
merge_request:
author:
---
title: Add index on ci_builds.updated_at
merge_request: 10870
author: blackst0ne
---
title: Fixed spacing of discussion submit buttons
merge_request:
author:
---
title: Ensure replying to an individual note by email creates a note with its own
discussion ID
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: Fix missing duration for blocked pipelines
merge_request: 10856
author:
---
title: Fix lastest commit status text on main project page
merge_request: 10863
author:
---
title: Fix updating merge_when_build_succeeds via merge API endpoint
merge_request: 10873
author:
---
title: Cache Routable#full_path in RequestStore to reduce duplicate route loads
merge_request:
author:
---
title: Eliminate N+1 queries in loading namespaces for every issuable in milestones
merge_request:
author:
---
title: Properly expire cache for all MRs of a pipeline
merge_request: 10770
author:
......@@ -25,6 +25,7 @@ development:
pool: 5
username: root
password: "secure password"
# host: localhost
# socket: /tmp/mysql.sock
# Warning: The database defined as "test" will be erased and
......@@ -39,4 +40,5 @@ test: &test
pool: 5
username: root
password:
# host: localhost
# socket: /tmp/mysql.sock
......@@ -21,6 +21,7 @@ development:
pool: 5
username: postgres
password:
# host: localhost
#
# Staging specific
......@@ -32,6 +33,7 @@ staging:
pool: 5
username: postgres
password:
# host: localhost
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
......@@ -43,3 +45,4 @@ test: &test
pool: 5
username: postgres
password:
# host: localhost
......@@ -505,6 +505,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
......
......@@ -387,6 +387,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
#
......
......@@ -38,7 +38,7 @@ if Rails.env.test?
end
end
if ENV.has_key?('CI')
if ENV.has_key?('CI') && ENV['GITLAB_DATABASE'] == 'postgresql'
RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
RspecProfiling::Run.prepend(RspecProfilingExt::Run)
end
......
# 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
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170419001229) do
ActiveRecord::Schema.define(version: 20170423064036) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -241,6 +241,7 @@ ActiveRecord::Schema.define(version: 20170419001229) 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
create_table "ci_pipelines", force: :cascade do |t|
t.string "ref"
......
# 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.
......
......@@ -120,7 +120,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 |
......
......@@ -859,6 +859,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",
......@@ -868,8 +879,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
......
......@@ -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.
---
......
......@@ -470,10 +470,8 @@ with setting up Gitaly until you upgrade to GitLab 9.2 or later.
sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
sudo chown git /home/git/gitlab/tmp/sockets/private
# Configure Gitaly
cd /home/git/gitaly
sudo -u git cp config.toml.example config.toml
# If you are using non-default settings you need to update config.toml
cd /home/git/gitaly
sudo -u git -H editor config.toml
# Enable Gitaly in the init script
......
......@@ -317,6 +317,17 @@ the socket path, but with `unix:` in front.
Each entry under `storages:` should use the same `gitaly_address`.
#### Compile Gitaly
This step will also create `config.toml.example` which you need below.
```shell
cd /home/git/gitaly
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
#### Gitaly config.toml
In GitLab 9.1 we are replacing environment variables in Gitaly with a
......
# Health Check
> [Introduced][ce-3888] in GitLab 8.8.
GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
endpoint. The health check reports on the overall system status based on the status of
the database connection, the state of the database migrations, and the ability to write
and access the cache. This endpoint can be provided to uptime monitoring services like
[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health].
>**Notes:**
- Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
- The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will
be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
section.
GitLab provides liveness and readiness probes to indicate service health and
reachability to required services. These probes report on the status of the
database connection, Redis connection, and access to the filesystem. These
endpoints [can be provided to schedulers like Kubernetes][kubernetes] to hold
traffic until the system is ready or restart the container as needed.
## Access Token
An access token needs to be provided while accessing the health check endpoint. The current
accepted token can be found on the `admin/health_check` page of your GitLab instance.
An access token needs to be provided while accessing the probe endpoints. The current
accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check**
(`admin/health_check`) page of your GitLab instance.
![access token](img/health_check_token.png)
The access token can be passed as a URL parameter:
```
https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN
https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
```
or as an HTTP header:
which will then provide a report of system health in JSON format:
```bash
curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
```
{
"db_check": {
"status": "ok"
},
"redis_check": {
"status": "ok"
},
"fs_shards_check": {
"status": "ok",
"labels": {
"shard": "default"
}
}
}
```
## Using the Endpoint
Once you have the access token, health information can be retrieved as plain text, JSON,
or XML using the `health_check` endpoint:
Once you have the access token, the probes can be accessed:
- `https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN`
- `https://gitlab.example.com/-/liveness?token=ACCESS_TOKEN`
## Status
On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
will return a valid successful HTTP status code, and a `success` message.
## Old behavior
>**Notes:**
- Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
- The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will
be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
section.
GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
endpoint. The health check reports on the overall system status based on the status of
the database connection, the state of the database migrations, and the ability to write
and access the cache. This endpoint can be provided to uptime monitoring services like
[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health].
Once you have the [access token](#access-token), health information can be
retrieved as plain text, JSON, or XML using the `health_check` endpoint:
- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN`
- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN`
......@@ -54,13 +96,13 @@ would be like:
{"healthy":true,"message":"success"}
```
## Status
On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
will return a valid successful HTTP status code, and a `success` message. Ideally your
uptime monitoring should look for the success message.
[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
[pingdom]: https://www.pingdom.com
[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring
[kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
......@@ -60,7 +60,9 @@ Feature: Profile
Then I should see a password error message
Scenario: I visit history tab
Given I have activity
Given I logout
And I sign in via the UI
And I have activity
When I visit Audit Log page
Then I should see my activity
......
......@@ -41,8 +41,7 @@ Feature: Project Forked Merge Requests
@javascript
Scenario: I see the users in the target project for a new merge request
Given I logout
And I sign in as an admin
Given I sign in as an admin
And I have a project forked off of "Shop" called "Forked Shop"
Then I visit project "Forked Shop" merge requests page
And I click link "New Merge Request"
......
......@@ -6,7 +6,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
include Select2Helper
step 'I am a member of project "Shop"' do
@project = Project.find_by(name: "Shop")
@project = ::Project.find_by(name: "Shop")
@project ||= create(:project, :repository, name: "Shop")
@project.team << [@user, :reporter]
end
......
......@@ -43,7 +43,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
end
step 'I am signed in as a developer of the project' do
login_as(@user)
sign_in(@user)
end
step 'I should see merge request merged' do
......
......@@ -31,7 +31,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
step 'I am signed in as a developer of the project' do
@user = create(:user) { |u| @project.add_developer(u) }
login_as(@user)
sign_in(@user)
end
step 'There is an open Merge Request' do
......
......@@ -7,7 +7,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
include SharedMarkdown
step 'I own project "Delta"' do
@project = Project.find_by(name: "Delta")
@project = ::Project.find_by(name: "Delta")
@project ||= create(:project, :repository, name: "Delta", namespace: @user.namespace)
@project.team << [@user, :master]
end
......
require Rails.root.join('spec', 'support', 'login_helpers')
require Rails.root.join('features', 'support', 'login_helpers')
module SharedAuthentication
include Spinach::DSL
include LoginHelpers
step 'I sign in as a user' do
login_as :user
sign_out(@user) if @user
@user = create(:user)
sign_in(@user)
end
step 'I sign in via the UI' do
gitlab_sign_in(create(:user))
end
step 'I sign in as an admin' do
login_as :admin
sign_out(@user) if @user
@user = create(:admin)
sign_in(@user)
end
step 'I sign in as "John Doe"' do
login_with(user_exists("John Doe"))
gitlab_sign_in(user_exists("John Doe"))
end
step 'I sign in as "Mary Jane"' do
login_with(user_exists("Mary Jane"))
gitlab_sign_in(user_exists("Mary Jane"))
end
step 'I should be redirected to sign in page' do
......@@ -25,14 +35,41 @@ module SharedAuthentication
end
step "I logout" do
logout
gitlab_sign_out
end
step "I logout directly" do
logout_direct
gitlab_sign_out
end
def current_user
@user || User.reorder(nil).first
end
private
def gitlab_sign_in(user)
visit new_user_session_path
fill_in "user_login", with: user.email
fill_in "user_password", with: "12345678"
check 'user_remember_me'
click_button "Sign in"
@user = user
end
def gitlab_sign_out
return unless @user
if Capybara.current_driver == Capybara.javascript_driver
find('.header-user-dropdown-toggle').click
click_link 'Sign out'
expect(page).to have_button('Sign in')
else
sign_out(@user)
end
@user = nil
end
end
module LoginHelpers
# After inclusion, IntegrationHelpers calls these two methods that aren't
# supported by Spinach, so we perform the end results ourselves
class << self
def setup(*args)
Spinach.hooks.before_scenario do
Warden.test_mode!
end
end
def teardown(*args)
Spinach.hooks.after_scenario do
Warden.test_reset!
end
end
end
include Devise::Test::IntegrationHelpers
end
......@@ -197,14 +197,15 @@ module API
end
put ':id/merge_requests/:merge_request_iid/merge' do
merge_request = find_project_merge_request(params[:merge_request_iid])
merge_when_pipeline_succeeds = to_boolean(params[:merge_when_pipeline_succeeds])
# Merge request can not be merged
# because user dont have permissions to push into target branch
unauthorized! unless merge_request.can_be_merged_by?(current_user)
not_allowed! unless merge_request.mergeable_state?
not_allowed! unless merge_request.mergeable_state?(skip_ci_check: merge_when_pipeline_succeeds)
render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?
render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds)
if params[:sha] && merge_request.diff_head_sha != params[:sha]
render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
......@@ -215,7 +216,7 @@ module API
should_remove_source_branch: params[:should_remove_source_branch]
}
if params[:merge_when_pipeline_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active?
if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active?
::MergeRequests::MergeWhenPipelineSucceedsService
.new(merge_request.target_project, current_user, merge_params)
.execute(merge_request)
......
......@@ -80,16 +80,32 @@ module Backup
'port' => '--port',
'socket' => '--socket',
'username' => '--user',
'encoding' => '--default-character-set'
'encoding' => '--default-character-set',
# SSL
'sslkey' => '--ssl-key',
'sslcert' => '--ssl-cert',
'sslca' => '--ssl-ca',
'sslcapath' => '--ssl-capath',
'sslcipher' => '--ssl-cipher'
}
args.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
end
def pg_env
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
args = {
'username' => 'PGUSER',
'host' => 'PGHOST',
'port' => 'PGPORT',
'password' => 'PGPASSWORD',
# SSL
'sslmode' => 'PGSSLMODE',
'sslkey' => 'PGSSLKEY',
'sslcert' => 'PGSSLCERT',
'sslrootcert' => 'PGSSLROOTCERT',
'sslcrl' => 'PGSSLCRL',
'sslcompression' => 'PGSSLCOMPRESSION'
}
args.each { |opt, arg| ENV[arg] = config[opt].to_s if config[opt] }
end
def report_success(success)
......
......@@ -108,7 +108,7 @@ module Gitlab
token = Doorkeeper::AccessToken.by_token(password)
if valid_oauth_token?(token)
user = User.find_by(id: token.resource_owner_id)
Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)
Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
end
end
end
......
......@@ -82,7 +82,7 @@ module Gitlab
file_diff, old_line, new_line = results
Position.new(
new_position = Position.new(
old_path: file_diff.old_path,
new_path: file_diff.new_path,
head_sha: new_diff_refs.head_sha,
......@@ -91,6 +91,13 @@ module Gitlab
old_line: old_line,
new_line: new_line
)
# If a position is found, but is not actually contained in the diff, for example
# because it was an unchanged line in the context of a change that was undone,
# we cannot return this as a successful trace.
return unless new_position.diff_line(repository)
new_position
end
private
......
......@@ -16,6 +16,10 @@ module Gitlab
def execute
raise NotImplementedError
end
def metrics_params
{ handler: self.class.name }
end
end
end
end
......
require 'gitlab/email/handler/base_handler'
module Gitlab
......@@ -37,6 +36,10 @@ module Gitlab
@project ||= Project.find_by_full_path(project_path)
end
def metrics_params
super.merge(project: project)
end
private
def create_issue
......
......@@ -28,6 +28,10 @@ module Gitlab
record_name: 'comment')
end
def metrics_params
super.merge(project: project)
end
private
def author
......
......@@ -19,6 +19,10 @@ module Gitlab
noteable.unsubscribe(sent_notification.recipient)
end
def metrics_params
super.merge(project: project)
end
private
def sent_notification
......
require_dependency 'gitlab/email/handler'
# Inspired in great part by Discourse's Email::Receiver
......@@ -32,9 +31,7 @@ module Gitlab
raise UnknownIncomingEmail unless handler
Gitlab::Metrics.add_event(:receive_email,
project: handler.try(:project),
handler: handler.class.name)
Gitlab::Metrics.add_event(:receive_email, handler.metrics_params)
handler.execute
end
......
......@@ -168,7 +168,7 @@ module Gitlab
end
def secret_path
Rails.root.join('.gitlab_workhorse_secret')
Gitlab.config.workhorse.secret_file
end
def set_key_and_notify(key, value, expire: nil, overwrite: true)
......
......@@ -2,6 +2,8 @@ namespace :gitlab do
namespace :gitaly do
desc "GitLab | Install or upgrade gitaly"
task :install, [:dir] => :environment do |t, args|
require 'toml'
warn_user_is_not_gitlab
unless args.dir.present?
abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]")
......@@ -16,6 +18,7 @@ namespace :gitlab do
command = status.zero? ? 'gmake' : 'make'
Dir.chdir(args.dir) do
create_gitaly_configuration
run_command!([command])
end
end
......@@ -33,5 +36,39 @@ namespace :gitlab do
puts TOML.dump(storage: config)
end
private
# We cannot create config.toml files for all possible Gitaly configuations.
# For instance, if Gitaly is running on another machine then it makes no
# sense to write a config.toml file on the current machine. This method will
# only write a config.toml file in the most common and simplest case: the
# case where we have exactly one Gitaly process and we are sure it is
# running locally because it uses a Unix socket.
def create_gitaly_configuration
storages = []
address = nil
Gitlab.config.repositories.storages.each do |key, val|
if address
if address != val['gitaly_address']
raise ArgumentError, "Your gitlab.yml contains more than one gitaly_address."
end
elsif URI(val['gitaly_address']).scheme != 'unix'
raise ArgumentError, "Automatic config.toml generation only supports 'unix:' addresses."
else
address = val['gitaly_address']
end
storages << { name: key, path: val['path'] }
end
File.open("config.toml", "w") do |f|
f.puts TOML.dump(socket_path: address.sub(%r{\Aunix:}, ''), storages: storages)
end
rescue ArgumentError => e
puts "Skipping config.toml generation:"
puts e.message
end
end
end
#!/bin/sh
retry() {
if eval "$@"; then
return 0
fi
. scripts/utils.sh
for i in 2 1; do
sleep 3s
echo "Retrying $i..."
if eval "$@"; then
return 0
fi
done
return 1
}
export SETUP_DB=${SETUP_DB:-true}
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS="--without production --jobs $(nproc) --path vendor --retry 3 --quiet"
# Determine the database by looking at the job name.
# For example, we'll get pg if the job is `rspec pg 19 20`
export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f2 -d' ')
# This would make the default database postgresql, and we could also use
# pg to mean postgresql.
if [ "$GITLAB_DATABASE" != 'mysql' ]; then
export GITLAB_DATABASE='postgresql'
fi
cp config/database.yml.$GITLAB_DATABASE config/database.yml
cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml
sed -i 's/password:.*/password:/g' config/database.yml
sed -i 's/# socket:.*/host: mysql/g' config/database.yml
if [ "$GITLAB_DATABASE" = 'postgresql' ]; then
sed -i 's/# host:.*/host: postgres/g' config/database.yml
else # Assume it's mysql
sed -i 's/username:.*/username: root/g' config/database.yml
sed -i 's/password:.*/password:/g' config/database.yml
sed -i 's/# host:.*/host: mysql/g' config/database.yml
fi
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
export FLAGS="--path vendor --retry 3 --quiet"
cp config/gitlab.yml.example config/gitlab.yml
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
retry bundle install --clean $BUNDLE_INSTALL_FLAGS
fi
# Only install knapsack after bundle install! Otherwise oddly some native
# gems could not be found under some circumstance. No idea why, hours wasted.
retry gem install knapsack fog-aws mime-types
if [ "$SETUP_DB" != "false" ]; then
bundle exec rake db:drop db:create db:schema:load db:migrate
if [ "$GITLAB_DATABASE" = "mysql" ]; then
bundle exec rake add_limits_mysql
fi
fi
retry() {
if eval "$@"; then
return 0
fi
for i in 2 1; do
sleep 3s
echo "Retrying $i..."
if eval "$@"; then
return 0
fi
done
return 1
}
require 'spec_helper'
describe Dashboard::TodosController do
include ApiHelpers
let(:user) { create(:user) }
let(:author) { create(:user) }
let(:project) { create(:empty_project) }
......
require 'spec_helper'
describe Projects::BuildsController do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) }
......@@ -63,4 +61,44 @@ describe Projects::BuildsController do
expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
describe 'GET trace.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:user) { create(:user) }
context 'when user is logged in as developer' do
before do
project.add_developer(user)
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
context 'when user is logged in as non member' do
before do
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
def get_trace
get :trace, namespace_id: project.namespace,
project_id: project,
id: build.id,
format: :json
end
end
end
require 'spec_helper'
describe Projects::BuildsController do
include ApiHelpers
let(:project) { create(:empty_project, :public) }
describe 'GET trace.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:user) { create(:user) }
context 'when user is logged in as developer' do
before do
project.add_developer(user)
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
context 'when user is logged in as non member' do
before do
sign_in(user)
get_trace
end
it 'traces build log' do
expect(response).to have_http_status(:ok)
expect(json_response['id']).to eq build.id
expect(json_response['status']).to eq build.status
end
end
def get_trace
get :trace, namespace_id: project.namespace,
project_id: project,
id: build.id,
format: :json
end
end
end
require 'spec_helper'
describe Projects::EnvironmentsController do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
......
require 'spec_helper'
describe Projects::MergeRequestsController do
include ApiHelpers
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
......
......@@ -5,6 +5,7 @@ describe Projects::MilestonesController do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
let(:issue) { create(:issue, project: project, milestone: milestone) }
let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
before do
......@@ -13,6 +14,20 @@ describe Projects::MilestonesController do
controller.instance_variable_set(:@project, project)
end
describe "#show" do
render_views
def view_milestone
get :show, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
end
it 'shows milestone page' do
view_milestone
expect(response).to have_http_status(200)
end
end
describe "#destroy" do
it "removes milestone" do
expect(issue.milestone_id).to eq(milestone.id)
......
require 'spec_helper'
describe Projects::PipelinesController do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) }
......
require('spec_helper')
describe Projects::TodosController do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
......
require 'spec_helper'
RSpec.describe 'admin issues labels' do
include WaitForAjax
let!(:bug_label) { Label.create(title: 'bug', template: true) }
let!(:feature_label) { Label.create(title: 'feature', template: true) }
......
require 'spec_helper'
describe "Admin::Users", feature: true do
include WaitForAjax
let!(:user) do
create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
end
......
require 'spec_helper'
describe 'Auto deploy' do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
before do
project.create_kubernetes_service(
......
require 'rails_helper'
describe 'Issue Boards add issue modal', :feature, :js do
include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
......
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
include DragTo
......
require 'rails_helper'
describe 'Issue Boards new issue', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
......
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
let(:user) { create(:user) }
......
require 'spec_helper'
feature 'Contributions Calendar', :feature, :js do
include WaitForAjax
let(:user) { create(:user) }
let(:contributed_project) { create(:project, :public) }
let(:contributed_project) { create(:empty_project, :public) }
let(:issue_note) { create(:note, project: contributed_project) }
# Ex/ Sunday Jan 1, 2016
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Commits' do
include CiStatusHelper
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
describe 'CI' do
before do
......
......@@ -433,7 +433,7 @@ describe 'Copy as GFM', feature: true, js: true do
end
describe 'Copying code' do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
context 'from a diff' do
before do
......
require 'spec_helper'
feature 'Cycle Analytics', feature: true, js: true do
include WaitForAjax
let(:user) { create(:user) }
let(:guest) { create(:user) }
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(issue) }
......
require 'spec_helper'
feature 'Tooltips on .timeago dates', feature: true, js: true do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:created_date) { Date.yesterday.to_time }
......
require 'spec_helper'
describe 'Dashboard Groups page', js: true, feature: true do
include WaitForAjax
let!(:user) { create :user }
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, :nested) }
......
require 'spec_helper'
feature 'Project member activity', feature: true, js: true do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public, name: 'x', namespace: user.namespace) }
......
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe "Dashboard Issues filtering", feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project) }
context 'filtering by milestone' do
......
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