Commit f4b4a34b authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 27574-pipelines-empty-state

* master: (65 commits)
  Merge branch 'render-json-leak' into 'security'
  Merge branch 'ssrf' into 'security'
  Merge branch 'ssrf' into 'security'
  Merge branch 'fix-links-target-blank' into 'security'
  Merge branch '28058-hide-emails-in-atom-feeds' into 'security'
  Fix karma test
  Reset filters after click
  Handle Route#name being nil after an update
  Only add frontend code coverage instrumentation when generating coverage report
  fix recompile assets step in 9.0 upgrade guide to use yarn
  Undo explicit conversion to Integer
  Make level_value accept string integers
  Make feature spec more robust
  Removed d3.js from the main application.js bundle
  Update css to be nice and tidy.
  add an index to the ghost column
  Improve rename projects migration
  Add additional check for when inputContainer does not exist
  Make the v3_to_v4.md more consistent
  Fix input token spacing
  ...
parents 2dc4eddf 4146be04
{
"presets": [
["latest", { "es2015": { "modules": false } }],
"stage-2"
],
"env": {
"coverage": {
"plugins": [
["istanbul", {
"exclude": [
"app/assets/javascripts/droplab/**/*",
"spec/javascripts/**/*"
]
}],
["transform-define", {
"process.env.BABEL_ENV": "coverage"
}]
]
}
}
}
......@@ -277,6 +277,8 @@ rake karma:
stage: test
<<: *use-db
<<: *dedicated-runner
variables:
BABEL_ENV: "coverage"
script:
- bundle exec rake karma
artifacts:
......@@ -389,9 +391,11 @@ trigger_docs:
cache: {}
artifacts: {}
script:
- "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/1794617/trigger/builds"
- "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)"
- if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
# Notify slack in the end
notify:slack:
......
......@@ -2,6 +2,11 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 8.17.4 (2017-03-19)
- Only show public emails in atom feeds.
- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443.
## 8.17.3 (2017-03-07)
- Fix the redirect to custom home page URL. !9518
......@@ -210,6 +215,11 @@ entry.
- Remove deprecated GitlabCiService.
- Requeue pending deletion projects.
## 8.16.8 (2017-03-19)
- Only show public emails in atom feeds.
- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443.
## 8.16.7 (2017-02-27)
- No changes.
......@@ -411,6 +421,11 @@ entry.
- Add margin to markdown math blocks.
- Add hover state to MR comment reply button.
## 8.15.8 (2017-03-19)
- Only show public emails in atom feeds.
- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443.
## 8.15.7 (2017-02-15)
- No changes.
......
......@@ -304,7 +304,7 @@ GEM
multi_json (~> 1.10)
retriable (~> 1.4)
signet (~> 0.6)
google-protobuf (3.2.0)
google-protobuf (3.2.0.2)
googleauth (0.5.1)
faraday (~> 0.9)
jwt (~> 1.4)
......
......@@ -30,7 +30,7 @@
if (this.activeTab === 'selected') {
obj.title = 'You haven\'t selected any issues yet';
obj.content = `
Go back to <strong>All issues</strong> and select some issues
Go back to <strong>Open issues</strong> and select some issues
to add to your board.
`;
}
......@@ -59,7 +59,7 @@
class="btn btn-default"
@click="changeTab('all')"
v-if="activeTab === 'selected'">
All issues
Open issues
</button>
</div>
</div>
......
......@@ -64,6 +64,7 @@ require('./empty_state');
},
filter: {
handler() {
this.page = 1;
this.loadIssues(true);
},
deep: true,
......@@ -115,6 +116,9 @@ require('./empty_state');
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
},
},
created() {
this.page = 1;
},
components: {
'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList,
......
......@@ -23,7 +23,7 @@
href="#"
role="button"
@click.prevent="changeTab('all')">
All issues
Open issues
<span class="badge">
{{ issuesCount }}
</span>
......
......@@ -25,6 +25,7 @@ import collapseIcon from '../icons/collapse_icon.svg';
role="button"
data-container="body"
data-placement="top"
data-html="true"
:data-line-type="lineType"
:title="note.authorName + ': ' + note.noteTruncated"
:src="note.authorAvatar"
......
import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make this a bundle
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
/* global UsernameValidator */
/* global ActiveTabMemoizer */
......@@ -329,8 +328,6 @@ const UserCallout = require('./user_callout');
case 'ci:lints:show':
new gl.CILintEditor();
break;
case 'projects:environments:metrics':
new PrometheusGraph();
case 'users:show':
new UserCallout();
break;
......
......@@ -132,7 +132,7 @@ class DueDateSelect {
const selectedDateValue = this.datePayload[this.abilityName].due_date;
const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
this.$loading.fadeIn();
this.$loading.removeClass('hidden').fadeIn();
if (isDropdown) {
this.$dropdown.trigger('loading.gl.dropdown');
......
......@@ -14,6 +14,7 @@ export default {
class="btn external_url"
:href="externalUrl"
target="_blank"
rel="noopener noreferrer"
title="Environment external URL">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
......
......@@ -38,6 +38,7 @@
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
}
......@@ -107,7 +108,7 @@
const hook = this.getCurrentHook();
if (hook) {
const data = hook.list.data;
const data = hook.list.data || [];
const results = data.map((o) => {
const updated = o;
updated.droplab_hidden = false;
......
......@@ -40,6 +40,8 @@ import FilteredSearchContainer from './container';
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
this.filteredSearchInputForm = this.filteredSearchInput.form;
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
......@@ -51,11 +53,13 @@ import FilteredSearchContainer from './container';
this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.addEventListener('click', this.tokenChange);
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
document.addEventListener('keydown', this.removeSelectedTokenWrapper);
}
......@@ -69,11 +73,13 @@ import FilteredSearchContainer from './container';
this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.removeEventListener('click', this.tokenChange);
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
}
......@@ -124,6 +130,26 @@ import FilteredSearchContainer from './container';
}
}
addInputContainerFocus() {
const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container');
if (inputContainer) {
inputContainer.classList.add('focus');
}
}
removeInputContainerFocus(e) {
const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null;
if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown &&
!isElementInStaticFilterDropdown && inputContainer) {
inputContainer.classList.remove('focus');
}
}
static selectToken(e) {
const button = e.target.closest('.selectable');
......
......@@ -2,6 +2,7 @@
/* global Flash */
require('./flash');
require('~/lib/utils/text_utility');
require('vendor/jquery.waitforimages');
require('./task_list');
......@@ -50,20 +51,21 @@ class Issue {
success: function(data, textStatus, jqXHR) {
if ('id' in data) {
$(document).trigger('issuable:change');
const currentTotal = Number($('.issue_counter').text());
let total = Number($('.issue_counter').text().replace(/[^\d]/, ''));
if (isClose) {
$('a.btn-close').addClass('hidden');
$('a.btn-reopen').removeClass('hidden');
$('div.status-box-closed').removeClass('hidden');
$('div.status-box-open').addClass('hidden');
$('.issue_counter').text(currentTotal - 1);
total -= 1;
} else {
$('a.btn-reopen').addClass('hidden');
$('a.btn-close').removeClass('hidden');
$('div.status-box-closed').addClass('hidden');
$('div.status-box-open').removeClass('hidden');
$('.issue_counter').text(currentTotal + 1);
total += 1;
}
$('.issue_counter').text(gl.text.addDelimiter(total));
} else {
new Flash(issueFailMessage, 'alert');
}
......
......@@ -76,7 +76,7 @@
if (!selected.length) {
data[abilityName].label_ids = [''];
}
$loading.fadeIn();
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return $.ajax({
type: 'PUT',
......
......@@ -14,13 +14,13 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
<%= ci_success_icon %>
<span>
Deployed to
<a href="<%- url %>" target="_blank" class="environment">
<a href="<%- url %>" target="_blank" rel="noopener noreferrer" class="environment">
<%- name %>
</a>
<span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>">
<%- deployed_at %>
</span>
<a class="js-environment-link" href="<%- external_url %>" target="_blank">
<a class="js-environment-link" href="<%- external_url %>" target="_blank" rel="noopener noreferrer">
<i class="fa fa-external-link"></i>
View on <%- external_url_formatted %>
</a>
......
......@@ -159,7 +159,7 @@
}
$dropdown.trigger('loading.gl.dropdown');
$loading.fadeIn();
$loading.removeClass('hidden').fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
......@@ -171,7 +171,7 @@
data = {};
data[abilityName] = {};
data[abilityName].milestone_id = selected != null ? selected : null;
$loading.fadeIn();
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return $.ajax({
type: 'PUT',
......
import PrometheusGraph from './prometheus_graph';
document.addEventListener('DOMContentLoaded', function onLoad() {
document.removeEventListener('DOMContentLoaded', onLoad, false);
return new PrometheusGraph();
}, false);
......@@ -2,10 +2,9 @@
/* global Flash */
import d3 from 'd3';
import _ from 'underscore';
import statusCodes from '~/lib/utils/http_status';
import '~/lib/utils/common_utils';
import '~/flash';
import '../lib/utils/common_utils';
import '../flash';
const prometheusGraphsContainer = '.prometheus-graph';
const metricsEndpoint = 'metrics.json';
......@@ -31,22 +30,21 @@ class PrometheusGraph {
}
createGraph() {
const self = this;
_.each(this.data, (value, key) => {
if (value.length > 0 && (key === 'cpu_values' || key === 'memory_values')) {
self.plotValues(value, key);
Object.keys(this.data).forEach((key) => {
const value = this.data[key];
if (value.length > 0) {
this.plotValues(value, key);
}
});
}
init() {
const self = this;
this.getData().then((metricsResponse) => {
if (metricsResponse === {}) {
if (Object.keys(metricsResponse).length === 0) {
new Flash('Empty metrics', 'alert');
} else {
self.transformData(metricsResponse);
self.createGraph();
this.transformData(metricsResponse);
this.createGraph();
}
});
}
......@@ -321,12 +319,14 @@ class PrometheusGraph {
transformData(metricsResponse) {
const metricTypes = {};
_.each(metricsResponse.metrics, (value, key) => {
const metricValues = value[0].values;
metricTypes[key] = _.map(metricValues, metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
metricTypes[key] = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
}
});
this.data = metricTypes;
}
......
......@@ -53,7 +53,7 @@
$loading = $block.find('.block-loading').fadeOut();
var updateIssueBoardsIssue = function () {
$loading.fadeIn();
$loading.removeClass('hidden').fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
$loading.fadeOut();
......@@ -90,7 +90,7 @@
data = {};
data[abilityName] = {};
data[abilityName].assignee_id = selected != null ? selected : null;
$loading.fadeIn();
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return $.ajax({
type: 'PUT',
......
......@@ -429,3 +429,9 @@ table {
@include str-truncated(100%);
}
}
.tooltip {
.tooltip-inner {
word-wrap: break-word;
}
}
......@@ -76,12 +76,14 @@
}
.input-token {
flex: 1;
-webkit-flex: 1;
max-width: 200px;
}
.filtered-search-token + .input-token:not(:last-child) {
max-width: 200px;
.input-token:only-child,
.input-token:last-child {
flex: 1;
-webkit-flex: 1;
max-width: initial;
}
}
......@@ -158,8 +160,8 @@
background-color: $white-light;
@media (max-width: $screen-xs-min) {
-webkit-flex: 1 1 100%;
flex: 1 1 100%;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
margin-bottom: 10px;
.dropdown-menu {
......@@ -171,17 +173,26 @@
}
}
&:hover {
@extend .form-control:hover;
}
&.focus,
&.focus:hover {
border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $search-input-focus-shadow-color;
}
&.focus .fa-filter {
color: $common-gray-dark;
}
.form-control {
position: relative;
min-width: 200px;
padding-left: 0;
padding-right: 25px;
padding: 5px 25px 6px 0;
border-color: transparent;
&:focus ~ .fa-filter {
color: $common-gray-dark;
}
&:focus,
&:hover {
outline: none;
......@@ -221,6 +232,10 @@
.filter-dropdown-container {
display: -webkit-flex;
display: flex;
.dropdown-toggle {
line-height: 22px;
}
}
.dropdown-menu .filter-dropdown-item {
......@@ -246,7 +261,9 @@
background-color: $white-light;
border-top: 0;
}
}
@media (max-width: $screen-xs) {
.filter-dropdown-container {
.dropdown-toggle,
.dropdown {
......
......@@ -138,7 +138,6 @@
.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0;
border-bottom: none;
......
......@@ -306,6 +306,11 @@ a > code {
* Textareas intended for GFM
*
*/
textarea.js-gfm-input {
font-family: $monospace_font;
font-size: 13px;
}
.strikethrough {
text-decoration: line-through;
}
......
......@@ -148,6 +148,18 @@
.error-alert > .alert {
margin-top: 5px;
margin-bottom: 5px;
&.alert-dismissable {
.close {
color: $white-light;
opacity: 0.85;
font-weight: normal;
&:hover {
opacity: 1;
}
}
}
}
.discussion-body,
......
......@@ -37,7 +37,6 @@ module ServiceParams
:namespace,
:new_issue_url,
:notify,
:notify_only_broken_builds,
:notify_only_broken_pipelines,
:password,
:priority,
......
......@@ -51,7 +51,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
private
def find_todos
@todos ||= TodosFinder.new(current_user, params).execute
@todos ||= TodosFinder.new(current_user, params.merge(include_associations: true)).execute
end
def todos_counts
......
......@@ -6,6 +6,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableCollections
include SpammableActions
prepend_before_action :authenticate_user!, only: [:new]
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
......@@ -146,7 +148,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
render json: @issue.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
end
end
......
......@@ -308,7 +308,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
format.json do
render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
render json: @merge_request.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
end
end
rescue ActiveRecord::StaleObjectError
......
......@@ -45,8 +45,9 @@ class Projects::WikisController < Projects::ApplicationController
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = @project_wiki.find_page(params[:id])
@page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
if @page.valid?
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
......
......@@ -24,6 +24,7 @@ class TodosFinder
def execute
items = current_user.todos
items = include_associations(items)
items = by_action_id(items)
items = by_action(items)
items = by_author(items)
......@@ -38,6 +39,17 @@ class TodosFinder
private
def include_associations(items)
return items unless params[:include_associations]
items.includes(
[
target: { project: [:route, namespace: :route] },
author: { namespace: :route },
]
)
end
def action_id?
action_id.present? && Todo::ACTION_NAMES.has_key?(action_id.to_i)
end
......
......@@ -215,6 +215,6 @@ module BlobHelper
end
def open_raw_file_button(path)
link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', title: 'Open raw', data: { container: 'body' }
link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' }
end
end
......@@ -211,7 +211,7 @@ module CommitsHelper
external_url = environment.external_url_for(diff_new_path, commit_sha)
return unless external_url
link_to(external_url, class: 'btn btn-file-option has-tooltip', target: '_blank', title: "View on #{environment.formatted_external_url}", data: { container: 'body' }) do
link_to(external_url, class: 'btn btn-file-option has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: "View on #{environment.formatted_external_url}", data: { container: 'body' }) do
icon('external-link')
end
end
......
......@@ -7,7 +7,7 @@ module ImportHelper
def provider_project_link(provider, path_with_namespace)
url = __send__("#{provider}_project_url", path_with_namespace)
link_to path_with_namespace, url, target: '_blank'
link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
end
private
......
......@@ -16,6 +16,7 @@ module NavHelper
"page-gutter build-sidebar right-sidebar-expanded"
elsif current_path?('wikis#show') ||
current_path?('wikis#edit') ||
current_path?('wikis#update') ||
current_path?('wikis#history') ||
current_path?('wikis#git_access')
"page-gutter wiki-sidebar right-sidebar-expanded"
......
......@@ -39,9 +39,13 @@ module TodosHelper
namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
todo.target, anchor: anchor)
else
path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
path.unshift(:pipelines) if todo.build_failed?
if todo.build_failed?
# associated namespace and route would be loaded from the db again if todo.project was used
project = todo.target.project
path = [:pipelines, project.namespace.becomes(Namespace), project, todo.target]
else
path = [todo.target]
end
polymorphic_path(path, anchor: anchor)
end
......
module Emails
module Builds
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
add_project_headers
add_build_headers('failed')
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
add_project_headers
add_build_headers('success')
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
private
def add_build_headers(status)
headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref
headers['X-GitLab-Build-Status'] = status.to_s
end
end
end
......@@ -6,7 +6,6 @@ class Notify < BaseMailer
include Emails::Notes
include Emails::Projects
include Emails::Profile
include Emails::Builds
include Emails::Pipelines
include Emails::Members
......
......@@ -163,6 +163,8 @@ class ApplicationSetting < ActiveRecord::Base
end
def self.current
ensure_cache_setup
Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
......@@ -176,9 +178,16 @@ class ApplicationSetting < ActiveRecord::Base
end
def self.cached
ensure_cache_setup
Rails.cache.fetch(CACHE_KEY)
end
def self.ensure_cache_setup
# This is a workaround for a Rails bug that causes attribute methods not
# to be loaded when read from cache: https://github.com/rails/rails/issues/27348
ApplicationSetting.define_attribute_methods
end
def self.defaults_ce
{
after_sign_up_text: nil,
......
......@@ -15,7 +15,7 @@ module Ci
def persisted_environment
@persisted_environment ||= Environment.find_by(
name: expanded_environment_name,
project_id: gl_project_id
project: project
)
end
......@@ -223,7 +223,8 @@ module Ci
def merge_request
merge_requests = MergeRequest.includes(:merge_request_diff)
.where(source_branch: ref, source_project_id: pipeline.gl_project_id)
.where(source_branch: ref,
source_project: pipeline.project)
.reorder(iid: :asc)
merge_requests.find do |merge_request|
......@@ -231,10 +232,6 @@ module Ci
end
end
def project_id
gl_project_id
end
def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
......@@ -542,6 +539,16 @@ module Ci
Gitlab::Ci::Build::Credentials::Factory.new(self).create!
end
def dependencies
depended_jobs = depends_on_builds
return depended_jobs unless options[:dependencies].present?
depended_jobs.select do |job|
options[:dependencies].include?(job.name)
end
end
private
def update_artifacts_size
......@@ -561,7 +568,7 @@ module Ci
end
def unscoped_project
@unscoped_project ||= Project.unscoped.find_by(id: gl_project_id)
@unscoped_project ||= Project.unscoped.find_by(id: project_id)
end
CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
......
......@@ -5,9 +5,7 @@ module Ci
include Importable
include AfterCommitQueue
self.table_name = 'ci_commits'
belongs_to :project, foreign_key: :gl_project_id
belongs_to :project
belongs_to :user
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
......
......@@ -9,7 +9,7 @@ module Ci
has_many :builds
has_many :runner_projects, dependent: :destroy
has_many :projects, through: :runner_projects, foreign_key: :gl_project_id
has_many :projects, through: :runner_projects
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
......@@ -24,7 +24,7 @@ module Ci
scope :owned_or_shared, ->(project_id) do
joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
.where("ci_runner_projects.project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end
scope :assignable_for, ->(project) do
......
module Ci
class RunnerProject < ActiveRecord::Base
extend Ci::Model
belongs_to :runner
belongs_to :project, foreign_key: :gl_project_id
belongs_to :project
validates :runner_id, uniqueness: { scope: :gl_project_id }
validates :runner_id, uniqueness: { scope: :project_id }
end
end
......@@ -4,7 +4,7 @@ module Ci
acts_as_paranoid
belongs_to :project, foreign_key: :gl_project_id
belongs_to :project
belongs_to :owner, class_name: "User"
has_many :trigger_requests, dependent: :destroy
......
......@@ -2,11 +2,11 @@ module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
belongs_to :project, foreign_key: :gl_project_id
belongs_to :project
validates :key,
presence: true,
uniqueness: { scope: :gl_project_id },
uniqueness: { scope: :project_id },
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
......
......@@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds'
belongs_to :project, foreign_key: :gl_project_id
belongs_to :project
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :user
......@@ -133,6 +133,12 @@ class CommitStatus < ActiveRecord::Base
false
end
# Added in 9.0 to keep backward compatibility for projects exported in 8.17
# and prior.
def gl_project_id
'dummy'
end
def detailed_status(current_user)
Gitlab::Ci::Status::Factory
.new(self, current_user)
......
......@@ -48,11 +48,13 @@ module Issuable
delegate :name,
:email,
:public_email,
to: :author,
prefix: true
delegate :name,
:email,
:public_email,
to: :assignee,
allow_nil: true,
prefix: true
......
......@@ -51,11 +51,13 @@ module Routable
paths.each do |path|
path = connection.quote(path)
where = "(routes.path = #{path})"
if cast_lower
where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))"
end
where =
if cast_lower
"(LOWER(routes.path) = LOWER(#{path}))"
else
"(routes.path = #{path})"
end
wheres << where
end
......
......@@ -16,7 +16,7 @@ class Event < ActiveRecord::Base
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
delegate :name, :email, to: :author, prefix: true, allow_nil: true
delegate :name, :email, :public_email, to: :author, prefix: true, allow_nil: true
delegate :title, to: :issue, prefix: true, allow_nil: true
delegate :title, to: :merge_request, prefix: true, allow_nil: true
delegate :title, to: :note, prefix: true, allow_nil: true
......
......@@ -55,6 +55,14 @@ class Issue < ActiveRecord::Base
state :opened
state :reopened
state :closed
before_transition any => :closed do |issue|
issue.closed_at = Time.zone.now
end
before_transition closed: any do |issue|
issue.closed_at = nil
end
end
def hook_attrs
......
......@@ -7,6 +7,7 @@ class MergeRequest < ActiveRecord::Base
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
belongs_to :project, foreign_key: :target_project_id
belongs_to :merge_user, class_name: "User"
has_many :merge_request_diffs, dependent: :destroy
......@@ -540,10 +541,6 @@ class MergeRequest < ActiveRecord::Base
target_project != source_project
end
def project
target_project
end
# If the merge request closes any issues, save this information in the
# `MergeRequestsClosingIssues` model. This is a performance optimization.
# Calculating this information for a number of merge requests requires
......
......@@ -89,7 +89,6 @@ class Project < ActiveRecord::Base
has_one :campfire_service, dependent: :destroy
has_one :drone_ci_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy
has_one :builds_email_service, dependent: :destroy
has_one :pipelines_email_service, dependent: :destroy
has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy
......@@ -159,13 +158,13 @@ class Project < ActiveRecord::Base
has_one :project_feature, dependent: :destroy
has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete
has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
has_many :commit_statuses, dependent: :destroy
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline'
has_many :builds, class_name: 'Ci::Build' # the builds are created from the commit_statuses
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
has_many :environments, dependent: :destroy
has_many :deployments, dependent: :destroy
......@@ -197,6 +196,7 @@ class Project < ActiveRecord::Base
validates :name, uniqueness: { scope: :namespace_id }
validates :path, uniqueness: { scope: :namespace_id }
validates :import_url, addressable_url: true, if: :external_import?
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :avatar_type,
......@@ -881,13 +881,9 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo(user = nil)
url = web_url
credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user)
if user
url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" }
end
"#{url}.git"
Gitlab::UrlSanitizer.new("#{web_url}.git", credentials: credentials).full_url
end
# Check if current branch name is marked as protected in the system
......
# This class is to be removed with 9.1
# We should also by then remove BuildsEmailService from database
class BuildsEmailService < Service
prop_accessor :recipients
boolean_accessor :add_pusher
boolean_accessor :notify_only_broken_builds
validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? }
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
end
end
def title
'Builds emails'
end
def description
'Email the builds status to a list of recipients.'
end
def self.to_param
'builds_email'
end
def self.supported_events
%w(build)
end
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
return unless should_build_be_notified?(push_data)
recipients = all_recipients(push_data)
if recipients.any?
BuildEmailWorker.perform_async(
push_data[:build_id],
recipients,
push_data
)
end
end
def can_test?
project.builds.any?
end
def disabled_title
"Please setup a build on your repository."
end
def test_data(project = nil, user = nil)
Gitlab::DataBuilder::Build.build(project.builds.last)
end
def fields
[
{ type: 'textarea', name: 'recipients', placeholder: 'Emails separated by comma' },
{ type: 'checkbox', name: 'add_pusher', label: 'Add pusher to recipients list' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
]
end
def test(data)
begin
# bypass build status verification when testing
data[:build_status] = "failed"
data[:build_allow_failure] = false
result = execute(data)
rescue StandardError => error
return { success: false, result: error }
end
{ success: true, result: result }
end
def should_build_be_notified?(data)
case data[:build_status]
when 'success'
!notify_only_broken_builds?
when 'failed'
!allow_failure?(data)
else
false
end
end
def allow_failure?(data)
data[:build_allow_failure] == true
end
def all_recipients(data)
all_recipients = []
unless recipients.blank?
all_recipients += recipients.split(',').compact.reject(&:blank?)
end
if add_pusher? && data[:user][:email]
all_recipients << data[:user][:email]
end
all_recipients
%w[]
end
end
module ChatMessage
class BuildMessage < BaseMessage
attr_reader :sha
attr_reader :ref_type
attr_reader :ref
attr_reader :status
attr_reader :project_name
attr_reader :project_url
attr_reader :user_name
attr_reader :user_url
attr_reader :duration
attr_reader :stage
attr_reader :build_id
attr_reader :build_name
def initialize(params)
@sha = params[:sha]
@ref_type = params[:tag] ? 'tag' : 'branch'
@ref = params[:ref]
@project_name = params[:project_name]
@project_url = params[:project_url]
@status = params[:commit][:status]
@user_name = params[:commit][:author_name]
@user_url = params[:commit][:author_url]
@duration = params[:commit][:duration]
@stage = params[:build_stage]
@build_name = params[:build_name]
@build_id = params[:build_id]
end
def pretext
''
end
def fallback
format(message)
end
def attachments
[{ text: format(message), color: attachment_color }]
end
private
def message
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_link} #{humanized_status} on build #{build_link} of stage #{stage} in #{duration} #{'second'.pluralize(duration)}"
end
def build_url
"#{project_url}/builds/#{build_id}"
end
def build_link
link(build_name, build_url)
end
def user_link
link(user_name, user_url)
end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end
def humanized_status
case status
when 'success'
'passed'
else
status
end
end
def attachment_color
if status == 'success'
'good'
else
'danger'
end
end
def branch_url
"#{project_url}/commits/#{ref}"
end
def branch_link
link(ref, branch_url)
end
def project_link
link(project_name, project_url)
end
def commit_url
"#{project_url}/commit/#{sha}/builds"
end
def commit_link
link(Commit.truncate_sha(sha), commit_url)
end
end
end
......@@ -6,7 +6,7 @@ class ChatNotificationService < Service
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
boolean_accessor :notify_only_broken_pipelines
validates :webhook, presence: true, url: true, if: :activated?
......@@ -16,7 +16,6 @@ class ChatNotificationService < Service
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
self.notify_only_broken_pipelines = true
end
end
......@@ -27,7 +26,7 @@ class ChatNotificationService < Service
def self.supported_events
%w[push issue confidential_issue merge_request note tag_push
build pipeline wiki_page]
pipeline wiki_page]
end
def execute(data)
......@@ -89,8 +88,6 @@ class ChatNotificationService < Service
ChatMessage::MergeMessage.new(data) unless is_update?(data)
when "note"
ChatMessage::NoteMessage.new(data)
when "build"
ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
......@@ -125,17 +122,6 @@ class ChatNotificationService < Service
data[:object_attributes][:action] == 'update'
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
def should_pipeline_be_notified?(data)
case data[:object_attributes][:status]
when 'success'
......
......@@ -9,13 +9,13 @@ class HipchatService < Service
].freeze
prop_accessor :token, :room, :server, :color, :api_version
boolean_accessor :notify_only_broken_builds, :notify
boolean_accessor :notify_only_broken_pipelines, :notify
validates :token, presence: true, if: :activated?
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
self.notify_only_broken_pipelines = true
end
end
......@@ -41,12 +41,12 @@ class HipchatService < Service
placeholder: 'Leave blank for default (v2)' },
{ type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://hipchat.example.com' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def self.supported_events
%w(push issue confidential_issue merge_request note tag_push build)
%w(push issue confidential_issue merge_request note tag_push pipeline)
end
def execute(data)
......@@ -90,8 +90,8 @@ class HipchatService < Service
create_merge_request_message(data) unless is_update?(data)
when "note"
create_note_message(data)
when "build"
create_build_message(data) if should_build_be_notified?(data)
when "pipeline"
create_pipeline_message(data) if should_pipeline_be_notified?(data)
end
end
......@@ -240,28 +240,29 @@ class HipchatService < Service
message
end
def create_build_message(data)
ref_type = data[:tag] ? 'tag' : 'branch'
ref = data[:ref]
sha = data[:sha]
user_name = data[:commit][:author_name]
status = data[:commit][:status]
duration = data[:commit][:duration]
def create_pipeline_message(data)
pipeline_attributes = data[:object_attributes]
pipeline_id = pipeline_attributes[:id]
ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
ref = pipeline_attributes[:ref]
user_name = (data[:user] && data[:user][:name]) || 'API'
status = pipeline_attributes[:status]
duration = pipeline_attributes[:duration]
branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
commit_link = "<a href=\"#{project_url}/commit/#{CGI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
pipeline_url = "<a href=\"#{project_url}/pipelines/#{pipeline_id}\">##{pipeline_id}</a>"
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
"#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
def message_color(data)
build_status_color(data) || color || 'yellow'
pipeline_status_color(data) || color || 'yellow'
end
def build_status_color(data)
return unless data && data[:object_kind] == 'build'
def pipeline_status_color(data)
return unless data && data[:object_kind] == 'pipeline'
case data[:commit][:status]
case data[:object_attributes][:status]
when 'success'
'green'
else
......@@ -294,10 +295,10 @@ class HipchatService < Service
end
end
def should_build_be_notified?(data)
case data[:commit][:status]
def should_pipeline_be_notified?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_builds?
!notify_only_broken_pipelines?
when 'failed'
true
else
......
......@@ -30,7 +30,6 @@ class MattermostService < ChatNotificationService
[
{ type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
{ type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
......
......@@ -29,7 +29,6 @@ class SlackService < ChatNotificationService
[
{ type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
{ type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
......
......@@ -42,8 +42,11 @@ class ProjectWiki
url_to_repo
end
def http_url_to_repo
[Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
def http_url_to_repo(user = nil)
url = "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git"
credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user)
Gitlab::UrlSanitizer.new(url, credentials: credentials).full_url
end
def wiki_base_path
......
......@@ -21,7 +21,7 @@ class Route < ActiveRecord::Base
attributes[:path] = route.path.sub(path_was, path)
end
if name_changed? && route.name.present?
if name_changed? && name_was.present? && route.name.present?
attributes[:name] = route.name.sub(name_was, name)
end
......
......@@ -215,7 +215,6 @@ class Service < ActiveRecord::Base
assembla
bamboo
buildkite
builds_email
bugzilla
campfire
custom_issue_tracker
......
......@@ -877,7 +877,7 @@ class User < ActiveRecord::Base
def ci_authorized_runners
@ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject.
where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
where("ci_runner_projects.project_id IN (#{ci_projects_union.to_sql})").
select(:runner_id)
Ci::Runner.specific.where(id: runner_ids)
end
......
......@@ -155,7 +155,7 @@ class WikiPage
end
# Returns boolean True or False if this instance
# has been fully saved to disk or not.
# has been fully created on disk or not.
def persisted?
@persisted == true
end
......@@ -226,6 +226,8 @@ class WikiPage
end
def save(method, *args)
saved = false
project_wiki = wiki
if valid? && project_wiki.send(method, *args)
......@@ -243,10 +245,10 @@ class WikiPage
set_attributes
@persisted = true
saved = true
else
errors.add(:base, project_wiki.error_message) if project_wiki.error_message
@persisted = false
end
@persisted
saved
end
end
......@@ -55,13 +55,13 @@ module Ci
new_builds.
# don't run projects which have not enabled shared runners and builds
joins(:project).where(projects: { shared_runners_enabled: true }).
joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id').
where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id").
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
end
......@@ -71,7 +71,7 @@ module Ci
def running_builds_for_shared_runners
Ci::Build.running.where(runner: Ci::Runner.shared).
group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
group(:project_id).select(:project_id, 'count(*) AS running_builds')
end
def new_builds
......
......@@ -6,7 +6,7 @@ module MergeRequests
merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch
merge_request.can_be_created = branches_valid? && source_branch_specified? && target_branch_specified?
merge_request.can_be_created = branches_valid?
compare_branches if branches_present?
assign_title_and_description if merge_request.can_be_created
......
......@@ -33,6 +33,7 @@ module Projects
def import_repository
begin
raise Error, "Blocked import URL." if Gitlab::UrlBlocker.blocked_url?(project.import_url)
gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
rescue => e
# Expire cache to prevent scenarios such as:
......@@ -40,7 +41,7 @@ module Projects
# 2. Retried import, repo is broken or not imported but +exists?+ still returns true
project.repository.before_import if project.repository_exists?
raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
end
end
......
......@@ -24,10 +24,9 @@ class SystemHooksService
key: model.key,
id: model.id
)
if model.user
data.merge!(
username: model.user.username
)
data[:username] = model.user.username
end
when Project
data.merge!(project_data(model))
......@@ -35,8 +34,6 @@ class SystemHooksService
if event == :rename || event == :transfer
data[:old_path_with_namespace] = model.old_path_with_namespace
end
data
when User
data.merge!({
name: model.name,
......@@ -59,6 +56,8 @@ class SystemHooksService
when GroupMember
data.merge!(group_member_data(model))
end
data
end
def build_event_name(model, event)
......
# ImportableUrlValidator
#
# This validator blocks projects from using dangerous import_urls to help
# protect against Server-side Request Forgery (SSRF).
class ImportableUrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if Gitlab::UrlBlocker.blocked_url?(value)
record.errors.add(attribute, "imports are not allowed from that URL")
end
end
end
......@@ -48,7 +48,7 @@
.form-actions
= f.submit 'Save', class: 'btn btn-save append-right-10'
- if @appearance.persisted?
= link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank'
= link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
- if @appearance.updated_at
%span.pull-right
......
......@@ -404,7 +404,7 @@
Enable Sentry
.help-block
Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
%a{ href: 'https://getsentry.com', target: '_blank' } https://getsentry.com
%a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com
.form-group
= f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
......
......@@ -9,7 +9,7 @@ xml.entry do
xml.author do
xml.name event.author_name
xml.email event.author_email
xml.email event.author_public_email
end
xml.summary(type: "xhtml") do |summary|
......
......@@ -15,6 +15,6 @@
= link_to note.attachment.url, target: '_blank' do
= image_tag note.attachment.url, class: 'note-image-attach'
- else
= link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do
= link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do
%i.fa.fa-paperclip
= note.attachment_identifier
......@@ -17,7 +17,7 @@
%br
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
%br
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}.
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}.
- if current_application_settings.help_page_text.present?
%hr
= markdown_field(current_application_settings, :help_page_text)
......
......@@ -33,7 +33,7 @@
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank'
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer'
%td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
......@@ -50,7 +50,7 @@
- @repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
%td
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: "_blank"
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target
%fieldset.row
.input-group
......@@ -70,7 +70,7 @@
- @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
%td
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank'
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target
%td.import-actions-job-status
= label_tag 'Incompatible Project', nil, class: 'label label-danger'
......
......@@ -43,7 +43,7 @@
- @repos.each do |repo|
%tr{ id: "repo_#{repo["id"]}" }
%td
= link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
= link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target
= import_project_target(repo['namespace']['path'], repo['name'])
%td.import-actions.job-status
......
......@@ -13,7 +13,7 @@
%li
%p
Go to
#{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: "_blank"}.
#{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer'}.
%li
%p
Make sure you're logged into the account that owns the projects you'd like to import.
......
......@@ -36,7 +36,7 @@
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank", rel: 'noopener noreferrer'
%td
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
......@@ -53,7 +53,7 @@
- @repos.each do |repo|
%tr{ id: "repo_#{repo.id}" }
%td
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target
#{current_user.username}/#{repo.name}
%td.import-actions.job-status
......@@ -63,7 +63,7 @@
- @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.id}" }
%td
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target
%td.import-actions-job-status
= label_tag "Incompatible Project", nil, class: "label label-danger"
......
......@@ -7,7 +7,7 @@ xml.entry do
xml.author do
xml.name issue.author_name
xml.email issue.author_email
xml.email issue.author_public_email
end
xml.summary issue.title
......@@ -26,7 +26,7 @@ xml.entry do
if issue.assignee
xml.assignee do
xml.name issue.assignee.name
xml.email issue.assignee.email
xml.email issue.assignee_public_email
end
end
end
......@@ -2,5 +2,5 @@
%p
= icon('circle', class: 'cgreen')
Integration is active for
= link_to koding_project_url, target: '_blank' do
= link_to koding_project_url, target: '_blank', rel: 'noopener noreferrer' do
#{current_application_settings.koding_url}
- content_for :header do
%h1{ style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" }
GitLab (job failed)
%h3
Project:
= link_to namespace_project_url(@project.namespace, @project) do
= @project.name
%p
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p
Author: #{@build.pipeline.git_author_name}
%p
Branch: #{@build.ref}
%p
Stage: #{@build.stage}
%p
Job: #{@build.name}
%p
Message: #{@build.pipeline.git_commit_message}
%p
Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
Job failed for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.pipeline.short_sha %>
Author: <%= @build.pipeline.git_author_name %>
Branch: <%= @build.ref %>
Stage: <%= @build.stage %>
Job: <%= @build.name %>
Message: <%= @build.pipeline.git_commit_message %>
Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
- content_for :header do
%h1{ style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" }
GitLab (job successful)
%h3
Project:
= link_to namespace_project_url(@project.namespace, @project) do
= @project.name
%p
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p
Author: #{@build.pipeline.git_author_name}
%p
Branch: #{@build.ref}
%p
Stage: #{@build.stage}
%p
Job: #{@build.name}
%p
Message: #{@build.pipeline.git_commit_message}
%p
Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
Job successful for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.pipeline.short_sha %>
Author: <%= @build.pipeline.git_author_name %>
Branch: <%= @build.ref %>
Stage: <%= @build.stage %>
Job: <%= @build.name %>
Message: <%= @build.pipeline.git_commit_message %>
Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
......@@ -18,7 +18,7 @@
or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
.col-lg-9
.clearfix.avatar-image.append-bottom-default
= link_to avatar_icon(@user, 400), target: '_blank' do
= link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
%h5.prepend-top-0
Upload new avatar
......
......@@ -9,7 +9,7 @@
- else
.nothing-here-block
The SVG could not be displayed as it is too large, you can
#{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')}
#{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer')}
instead.
- else
%img{ src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path)), alt: "#{blob.name}" }
......@@ -3,7 +3,7 @@
.nothing-here-block
File too large, you can
= succeed '.' do
= link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank'
= link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer'
- else
- blob.load_all_data!(@repository)
......
......@@ -9,7 +9,7 @@
- if @conflict
.alert.alert-danger
Someone edited the file the same time you did. Please check out
= link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank"
= link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs.
.file-editor
......
- if koding_enabled? && current_user && @repository.koding_yml && can_push_branch?(@project, @project.default_branch)
= link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do
= link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank', rel: 'noopener noreferrer' do
Run in IDE (Koding)
......@@ -9,7 +9,7 @@
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
To set up CA, you must first define a production environment by setting up your CI and then deploy to production.
%p
%a.btn{ href: help_page_path('user/project/cycle_analytics'), target: "_blank" } Read more
%a.btn{ href: help_page_path('user/project/cycle_analytics'), target: '_blank' } Read more
.col-md-6.overview-image
%span.overview-icon
= custom_icon ('icon_cycle_analytics_overview')
- if environment.external_url && can?(current_user, :read_environment, environment)
= link_to environment.external_url, target: '_blank', class: 'btn external-url' do
= link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url' do
= icon('external-link')
- @no_container = true
- page_title "Metrics for environment", @environment.name
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('monitoring')
= render "projects/pipelines/head"
%div{ class: container_class }
......
......@@ -19,15 +19,14 @@
.nav-controls
= link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss')
- if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace,
@project,
issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New Issue",
id: "new_issue_link" do
New Issue
= link_to new_namespace_project_issue_path(@project.namespace,
@project,
issue: { assignee_id: issues_finder.assignee.try(:id),
milestone_id: issues_finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New Issue",
id: "new_issue_link" do
New Issue
= render 'shared/issuable/search_bar', type: :issues
.issues-holder
......
......@@ -20,37 +20,34 @@
= confidential_icon(@issue)
= issuable_meta(@issue, @project, "Issue")
- if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
Options
= icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can?(current_user, :create_issue, @project)
%li
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- if can?(current_user, :update_issue, @issue)
%li
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
%li
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- if @issue.submittable_as_spam_by?(current_user)
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
Options
= icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
%li
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- if can?(current_user, :update_issue, @issue)
%li
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
%li
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- if @issue.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- if @issue.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= 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
......
......@@ -16,7 +16,7 @@
.pull-right
- if @merge_request.source_branch_exists?
- if koding_enabled? && @repository.koding_yml
= link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank' do
= link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank', rel: 'noopener noreferrer' do
Run in IDE (Koding)
= link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do
Check out branch
......
......@@ -49,7 +49,7 @@
%strong Tip:
= succeed '.' do
You can also checkout merge requests locally by
= link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank'
= link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
:javascript
$(function(){
......
......@@ -4,13 +4,13 @@
%ul.list-unstyled.indent-list
%li
1.
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noreferrer noopener nofollow' do
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noopener noreferrer nofollow' do
Enable custom slash commands
= icon('external-link')
on your Mattermost installation
%li
2.
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noreferrer noopener nofollow' do
= link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noopener noreferrer nofollow' do
Add a slash command
= icon('external-link')
in your Mattermost team with these options:
......
......@@ -4,7 +4,7 @@
%p
This service allows users to perform common operations on this
project by entering slash commands in Mattermost.
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank', ref: 'noreferrer nofollow noopener' do
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do
View documentation
= icon('external-link')
%p.inline
......
......@@ -5,7 +5,7 @@
%p
This service allows users to perform common operations on this
project by entering slash commands in Slack.
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank', ref: 'noreferrer nofollow noopener' do
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do
View documentation
= icon('external-link')
%p.inline
......@@ -57,7 +57,7 @@
= label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.text-block
= image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36)
= link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank')
= link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
.form-group
= label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label'
......
.dropdown.inline.prepend-left-10
%button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } }
%span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
......
......@@ -16,7 +16,6 @@
Also, issues are searchable and filterable.
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
- else
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
- else
%h4.text-center There are no issues to show.
%h4 There are no issues to show.
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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