Commit 5d2c70b1 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into widget-widget-widget-fun

* master: (163 commits)
  Add KUBE_CA_PEM_FILE, deprecate KUBE_CA_PEM
  Remove useless assignment
  Rename query parameter to `membership`
  Stop setting Strict-Transport-Securty header from within the app
  Add filter param for authorized projects for current_user for V4
  Improve EE compatibility MR docs [ci skip]
  Fix the `Gitlab::Seeder` monkey patch to disable mail delivery
  Use separate error class for cherry-pick and revert tree errors
  Fix cherry-picking or reverting through an MR
  Resolve "missing count badge styling in new sidebar dropdown"
  Fix Rubocop offense in build retry service
  Decrease tanuki logo size
  remove extra whitespace on dashboard projects page
  Changes after review
  Delete hooks from project with empty repository
  Only add a newline in Markdown Editor if the current line is not empty
  Revert "Disallow system notes for closed issuables"
  Cleaning up navigational order - Project
  Backport EE changes. Service method should be `get` and not `all`
  Remove bundle-audit ignores that are no longer applicable
  ...
parents 01f634d3 afd56634
......@@ -300,7 +300,7 @@ bundler:audit:
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941 CVE-2016-6316 CVE-2016-6317"
- "bundle exec bundle-audit check --update"
migration paths:
stage: test
......
......@@ -23,6 +23,7 @@ AllCops:
- 'tmp/**/*'
- 'bin/**/*'
- 'generator_templates/**/*'
- 'builds/**/*'
# Gems in consecutive lines should be alphabetically sorted
Bundler/OrderedGems:
......
......@@ -2,6 +2,25 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 8.17.2 (2017-03-01)
- Expire all webpack assets after 8.17.1 included a badly compiled asset. !9602
## 8.17.1 (2017-02-28)
- Replace setInterval with setTimeout to prevent highly frequent requests. !9271 (Takuya Noguchi)
- Disable unused tags count cache for Projects, Builds and Runners.
- Spam check and reCAPTCHA improvements.
- Allow searching issues for strings containing colons.
- Disabled tooltip on add issues button in usse boards.
- Fixed commit search UI.
- Fix MR changes tab size count when there are over 100 files in the diff.
- Disable invalid service templates.
- Use default branch as target_branch when parameter is missing.
- Upgrade GitLab Pages to v0.3.2.
- Add performance query regression fix for !9088 affecting #27267.
- Chat slash commands show labels correctly.
## 8.17.0 (2017-02-22)
- API: Fix file downloading. !0 (8267)
......
## Contributor license agreement
By submitting code as an individual you agree to the
[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md).
By submitting code as an entity you agree to the
[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
_This notice should stay as the first item in the CONTRIBUTING.MD file._
---
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contributor license agreement](#contributor-license-agreement)
- [Contribute to GitLab](#contribute-to-gitlab)
- [Contributor license agreement](#contributor-license-agreement)
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Helping others](#helping-others)
- [I want to contribute!](#i-want-to-contribute)
- [Implement design & UI elements](#implement-design-ui-elements)
- [Issue tracker](#issue-tracker)
- [Feature proposals](#feature-proposals)
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
- [Code of conduct](#code-of-conduct)
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Helping others](#helping-others)
- [I want to contribute!](#i-want-to-contribute)
- [Implement design & UI elements](#implement-design-ui-elements)
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- [Retrospective](#retrospective)
- [Kickoff](#kickoff)
- [Issue tracker](#issue-tracker)
- [Feature proposals](#feature-proposals)
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
- [Code of conduct](#code-of-conduct)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Contribute to GitLab
---
## Contribute to GitLab
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone.
......@@ -41,13 +57,6 @@ operates please see [the GitLab contributing process](PROCESS.md).
- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
## Contributor license agreement
By submitting code as an individual you agree to the
[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md).
By submitting code as an entity you agree to the
[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to
......
......@@ -68,7 +68,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 0.18.0'
gem 'grape', '~> 0.19.0'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
......@@ -236,7 +236,6 @@ gem 'gemojione', '~> 3.0'
gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
......@@ -329,8 +328,6 @@ group :test do
gem 'timecop', '~> 0.8.0'
end
gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.6.2'
gem 'mail_room', '~> 0.9.1'
......@@ -352,3 +349,6 @@ gem 'health_check', '~> 2.2.0'
# System information
gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client
gem 'gitaly', '~> 0.2.1'
......@@ -245,6 +245,9 @@ GEM
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
gitaly (0.2.1)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
......@@ -296,6 +299,7 @@ GEM
multi_json (~> 1.10)
retriable (~> 1.4)
signet (~> 0.6)
google-protobuf (3.2.0)
googleauth (0.5.1)
faraday (~> 0.9)
jwt (~> 1.4)
......@@ -304,7 +308,7 @@ GEM
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
grape (0.18.0)
grape (0.19.1)
activesupport
builder
hashie (>= 2.1.0)
......@@ -317,6 +321,9 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grpc (1.1.2)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
tilt
haml_lint (0.21.0)
......@@ -353,8 +360,8 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
i18n (0.8.0)
ice_nine (0.11.1)
i18n (0.8.1)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
json
......@@ -367,8 +374,6 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.6)
json-schema (2.6.2)
addressable (~> 2.3.8)
......@@ -417,7 +422,7 @@ GEM
minitest (5.7.0)
mousetrap-rails (1.4.6)
multi_json (1.12.1)
multi_xml (0.5.5)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustermann (0.4.0)
tool (~> 0.2)
......@@ -427,7 +432,6 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.16.0.318)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
numerizer (0.1.1)
......@@ -660,7 +664,7 @@ GEM
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
rubyzip (1.2.0)
rubyzip (1.2.1)
rufus-scheduler (3.1.10)
rugged (0.24.0)
safe_yaml (1.0.4)
......@@ -758,7 +762,7 @@ GEM
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
thor (0.19.4)
thread_safe (0.3.5)
thread_safe (0.3.6)
tilt (2.0.6)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
......@@ -878,6 +882,7 @@ DEPENDENCIES
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0)
gitaly (~> 0.2.1)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
......@@ -886,7 +891,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.2)
gon (~> 6.1.0)
google-api-client (~> 0.8.6)
grape (~> 0.18.0)
grape (~> 0.19.0)
grape-entity (~> 0.6.0)
haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
......@@ -899,7 +904,6 @@ DEPENDENCIES
jira-ruby (~> 1.1.2)
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2)
jwt (~> 1.5.6)
kaminari (~> 0.17.0)
......@@ -915,7 +919,6 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.16)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0)
octokit (~> 4.6.2)
......@@ -1011,4 +1014,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.14.3
1.14.5
......@@ -9,7 +9,6 @@
window.$ = window.jQuery = require('jquery');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
require('vendor/jquery.highlight');
require('vendor/jquery.waitforimages');
require('vendor/jquery.caret');
require('vendor/jquery.atwho');
......@@ -18,15 +17,11 @@ window.Cookies = require('js-cookie');
require('./autosave');
require('bootstrap/js/affix');
require('bootstrap/js/alert');
require('bootstrap/js/button');
require('bootstrap/js/collapse');
require('bootstrap/js/dropdown');
require('bootstrap/js/modal');
require('bootstrap/js/scrollspy');
require('bootstrap/js/tab');
require('bootstrap/js/transition');
require('bootstrap/js/tooltip');
require('bootstrap/js/popover');
require('select2/select2.js');
window.Pikaday = require('pikaday');
window._ = require('underscore');
......@@ -236,6 +231,10 @@ require('es6-promise').polyfill();
var bootstrapBreakpoint = bp.getBreakpointSize();
var fitSidebarForSize;
$(document).on('scroll', function() {
$('.has-tooltip').tooltip('hide');
});
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
......
......@@ -3,7 +3,7 @@ require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore;
module.exports = {
export default {
name: 'BoardsIssueCard',
template: `
<li class="card"
......
......@@ -2,8 +2,8 @@
/* global Vue */
/* global Sortable */
const boardCard = require('./board_card');
require('./board_new_issue');
import boardNewIssue from './board_new_issue';
import boardCard from './board_card';
(() => {
const Store = gl.issueBoards.BoardsStore;
......@@ -15,7 +15,7 @@ require('./board_new_issue');
template: '#js-board-list-template',
components: {
boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
boardNewIssue,
},
props: {
disabled: Boolean,
......@@ -81,6 +81,12 @@ require('./board_new_issue');
});
}
},
toggleForm() {
this.showIssueForm = !this.showIssueForm;
},
},
created() {
gl.IssueBoardsApp.$on(`hide-issue-form-${this.list.id}`, this.toggleForm);
},
mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
......@@ -115,6 +121,9 @@ require('./board_new_issue');
this.loadNextPage();
}
};
}
},
beforeDestroy() {
gl.IssueBoardsApp.$off(`hide-issue-form-${this.list.id}`, this.toggleForm);
},
});
})();
/* global ListIssue */
const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardNewIssue',
props: {
list: Object,
},
data() {
return {
title: '',
error: false,
};
},
methods: {
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return;
this.error = false;
const labels = this.list.label ? [this.list.label] : [];
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true,
});
this.list.newIssue(issue)
.then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
Store.detail.issue = issue;
Store.detail.list = this.list;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
// Remove the issue
this.list.removeIssue(issue);
// Show error message
this.error = true;
});
this.cancel();
},
cancel() {
this.title = '';
gl.IssueBoardsApp.$emit(`hide-issue-form-${this.list.id}`);
},
},
mounted() {
this.$refs.input.focus();
},
template: `
<div class="card board-new-issue-form">
<form @submit="submit($event)">
<div class="flash-container"
v-if="error">
<div class="flash-alert">
An error occured. Please try again.
</div>
</div>
<label class="label-light"
:for="list.id + '-title'">
Title
</label>
<input class="form-control"
type="text"
v-model="title"
ref="input"
:id="list.id + '-title'" />
<div class="clearfix prepend-top-10">
<button class="btn btn-success pull-left"
type="submit"
:disabled="title === ''"
ref="submit-button">
Submit issue
</button>
<button class="btn btn-default pull-right"
type="button"
@click="cancel">
Cancel
</button>
</div>
</form>
</div>
`,
};
/* eslint-disable comma-dangle, no-unused-vars */
/* global Vue */
/* global ListIssue */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
gl.issueBoards.BoardNewIssue = Vue.extend({
props: {
list: Object,
},
data() {
return {
title: '',
error: false
};
},
methods: {
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return;
this.error = false;
const labels = this.list.label ? [this.list.label] : [];
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true
});
this.list.newIssue(issue)
.then((data) => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
Store.detail.issue = issue;
Store.detail.list = this.list;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
// Remove the issue
this.list.removeIssue(issue);
// Show error message
this.error = true;
});
this.cancel();
},
cancel() {
this.title = '';
this.$parent.showIssueForm = false;
}
},
mounted() {
this.$refs.input.focus();
},
});
})();
......@@ -39,15 +39,10 @@ const PipelineStore = require('./pipelines_store');
*/
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const svgsData = document.querySelector('.pipeline-svgs').dataset;
const store = new PipelineStore();
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
return {
endpoint: pipelinesTableData.endpoint,
svgs: svgsObject,
store,
state: store.state,
isLoading: false,
......@@ -69,7 +64,9 @@ const PipelineStore = require('./pipelines_store');
return pipelinesService.all()
.then(response => response.json())
.then((json) => {
this.store.storePipelines(json);
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = json.pipelines || json;
this.store.storePipelines(pipelines);
this.isLoading = false;
})
.catch(() => {
......@@ -99,10 +96,7 @@ const PipelineStore = require('./pipelines_store');
<div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component
:pipelines="state.pipelines"
:svgs="svgs">
</pipelines-table-component>
<pipelines-table-component :pipelines="state.pipelines"/>
</div>
</div>
`,
......
......@@ -25,6 +25,9 @@ require('./lib/utils/common_utils');
},
},
ReferenceFilter: {
'.tooltip'(el, text) {
return '';
},
'a.gfm:not([data-link=true])'(el, text) {
return el.dataset.original || text;
},
......
/* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
import iconCommit from '../svg/icon_commit.svg';
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -9,6 +10,11 @@
items: Array,
stage: Object,
},
data() {
return { iconCommit };
},
template: `
<div>
<div class="events-description">
......@@ -31,7 +37,7 @@
</h5>
<span>
First
<span class="commit-icon">${global.cycleAnalytics.svgs.iconCommit}</span>
<span class="commit-icon">${iconCommit}</span>
<a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
pushed by
<a :href="commit.author.webUrl" class="commit-author-link">
......
/* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
import iconBranch from '../svg/icon_branch.svg';
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -9,6 +10,9 @@
items: Array,
stage: Object,
},
data() {
return { iconBranch };
},
template: `
<div>
<div class="events-description">
......@@ -22,7 +26,7 @@
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
<span class="icon-branch">${global.cycleAnalytics.svgs.iconBranch}</span>
<span class="icon-branch">${iconBranch}</span>
<a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
</h5>
<span>
......
/* eslint-disable no-param-reassign */
/* global Vue */
import Vue from 'vue';
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -9,6 +11,9 @@
items: Array,
stage: Object,
},
data() {
return { iconBuildStatus, iconBranch };
},
template: `
<div>
<div class="events-description">
......@@ -18,13 +23,13 @@
<li v-for="build in items" class="stage-event-item item-build-component">
<div class="item-details">
<h5 class="item-title">
<span class="icon-build-status">${global.cycleAnalytics.svgs.iconBuildStatus}</span>
<span class="icon-build-status">${iconBuildStatus}</span>
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
<span class="icon-branch">${global.cycleAnalytics.svgs.iconBranch}</span>
<span class="icon-branch">${iconBranch}</span>
<a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
</h5>
<span>
......
......@@ -4,9 +4,6 @@
window.Vue = require('vue');
window.Cookies = require('js-cookie');
require('./svg/icon_branch');
require('./svg/icon_build_status');
require('./svg/icon_commit');
require('./components/stage_code_component');
require('./components/stage_issue_component');
require('./components/stage_plan_component');
......
......@@ -75,8 +75,11 @@ const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
eventItem.totalTime = eventItem.total_time;
eventItem.author.webUrl = eventItem.author.web_url;
eventItem.author.avatarUrl = eventItem.author.avatar_url;
if (eventItem.author) {
eventItem.author.webUrl = eventItem.author.web_url;
eventItem.author.avatarUrl = eventItem.author.avatar_url;
}
if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
......
/* eslint-disable no-param-reassign */
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {};
global.cycleAnalytics.svgs.iconBranch = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><path fill="#8C8C8C" fill-rule="evenodd" d="M9.678 6.722C9.353 5.167 8.053 4 6.5 4S3.647 5.167 3.322 6.722h-2.6c-.397 0-.722.35-.722.778 0 .428.325.778.722.778h2.6C3.647 9.833 4.947 11 6.5 11s2.853-1.167 3.178-2.722h2.6c.397 0 .722-.35.722-.778 0-.428-.325-.778-.722-.778h-2.6zM4.694 7.5c0-1.09.795-1.944 1.806-1.944 1.01 0 1.806.855 1.806 1.944 0 1.09-.795 1.944-1.806 1.944-1.01 0-1.806-.855-1.806-1.944z"/></svg>';
})(window.gl || (window.gl = {}));
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><path fill="#8C8C8C" fill-rule="evenodd" d="M9.678 6.722C9.353 5.167 8.053 4 6.5 4S3.647 5.167 3.322 6.722h-2.6c-.397 0-.722.35-.722.778 0 .428.325.778.722.778h2.6C3.647 9.833 4.947 11 6.5 11s2.853-1.167 3.178-2.722h2.6c.397 0 .722-.35.722-.778 0-.428-.325-.778-.722-.778h-2.6zM4.694 7.5c0-1.09.795-1.944 1.806-1.944 1.01 0 1.806.855 1.806 1.944 0 1.09-.795 1.944-1.806 1.944-1.01 0-1.806-.855-1.806-1.944z"/></svg>
/* eslint-disable no-param-reassign */
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {};
global.cycleAnalytics.svgs.iconBuildStatus = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><g fill="#31AF64" fill-rule="evenodd"><path d="M12.5 7c0-3.038-2.462-5.5-5.5-5.5S1.5 3.962 1.5 7s2.462 5.5 5.5 5.5 5.5-2.462 5.5-5.5zM0 7c0-3.866 3.134-7 7-7s7 3.134 7 7-3.134 7-7 7-7-3.134-7-7z"/><path d="M6.28 7.697L5.045 6.464c-.117-.117-.305-.117-.42-.002l-.614.614c-.11.113-.11.303.007.42l1.91 1.91c.19.19.51.197.703.004l.264-.265L9.997 6.04c.108-.107.107-.293-.01-.408l-.612-.614c-.114-.113-.298-.12-.41-.01L6.28 7.7z"/></g></svg>';
})(window.gl || (window.gl = {}));
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><g fill="#31AF64" fill-rule="evenodd"><path d="M12.5 7c0-3.038-2.462-5.5-5.5-5.5S1.5 3.962 1.5 7s2.462 5.5 5.5 5.5 5.5-2.462 5.5-5.5zM0 7c0-3.866 3.134-7 7-7s7 3.134 7 7-3.134 7-7 7-7-3.134-7-7z"/><path d="M6.28 7.697L5.045 6.464c-.117-.117-.305-.117-.42-.002l-.614.614c-.11.113-.11.303.007.42l1.91 1.91c.19.19.51.197.703.004l.264-.265L9.997 6.04c.108-.107.107-.293-.01-.408l-.612-.614c-.114-.113-.298-.12-.41-.01L6.28 7.7z"/></g></svg>
/* eslint-disable no-param-reassign */
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {};
global.cycleAnalytics.svgs.iconCommit = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path fill="#8F8F8F" fill-rule="evenodd" d="M28.777 18c-.91-4.008-4.494-7-8.777-7-4.283 0-7.868 2.992-8.777 7H4.01C2.9 18 2 18.895 2 20c0 1.112.9 2 2.01 2h7.213c.91 4.008 4.494 7 8.777 7 4.283 0 7.868-2.992 8.777-7h7.214C37.1 22 38 21.105 38 20c0-1.112-.9-2-2.01-2h-7.213zM20 25c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z"/></svg>';
})(window.gl || (window.gl = {}));
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path fill="#8F8F8F" fill-rule="evenodd" d="M28.777 18c-.91-4.008-4.494-7-8.777-7-4.283 0-7.868 2.992-8.777 7H4.01C2.9 18 2 18.895 2 20c0 1.112.9 2 2.01 2h7.213c.91 4.008 4.494 7 8.777 7 4.283 0 7.868-2.992 8.777-7h7.214C37.1 22 38 21.105 38 20c0-1.112-.9-2-2.01-2h-7.213zM20 25c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z"/></svg>
......@@ -25,6 +25,10 @@ require('./lib/utils/url_utility');
isBound = true;
}
if (gl.utils.getLocationHash()) {
this.highlightSelectedLine();
}
this.openAnchoredDiff();
}
......@@ -78,7 +82,7 @@ require('./lib/utils/url_utility');
if (nothingHereBlock.length) {
const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
this.highlighSelectedLine();
this.highlightSelectedLine();
if (cb) cb();
});
} else if (cb) {
......@@ -94,7 +98,7 @@ require('./lib/utils/url_utility');
} else {
window.location.hash = hash;
}
this.highlighSelectedLine();
this.highlightSelectedLine();
}
diffViewType() {
......@@ -108,7 +112,7 @@ require('./lib/utils/url_utility');
return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10));
}
highlighSelectedLine() {
highlightSelectedLine() {
const hash = gl.utils.getLocationHash();
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');
......
......@@ -35,6 +35,8 @@
/* global Labels */
/* global Shortcuts */
import GroupsList from './groups_list';
const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
......@@ -96,6 +98,10 @@ const UserCallout = require('./user_callout');
case 'dashboard:todos:index':
new gl.Todos();
break;
case 'dashboard:groups:index':
case 'explore:groups:index':
new GroupsList();
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
case 'projects:milestones:update':
......
......@@ -35,9 +35,6 @@ module.exports = Vue.component('environment-component', {
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties,
paginationInformation: {},
......@@ -78,7 +75,7 @@ module.exports = Vue.component('environment-component', {
this.isLoading = true;
return service.all()
return service.get()
.then(resp => ({
headers: resp.headers,
body: resp.json(),
......@@ -145,7 +142,7 @@ module.exports = Vue.component('environment-component', {
</div>
</div>
<div class="environments-container">
<div class="content-list environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
......@@ -176,17 +173,13 @@ module.exports = Vue.component('environment-component', {
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg">
</environment-table>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation">
</table-pagination>
:can-read-environment="canReadEnvironmentParsed"/>
</div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation">
</table-pagination>
</div>
</div>
`,
......
const Vue = require('vue');
const playIconSvg = require('icons/_icon_play.svg');
module.exports = Vue.component('actions-component', {
props: {
......@@ -7,11 +8,10 @@ module.exports = Vue.component('actions-component', {
required: false,
default: () => [],
},
},
playIconSvg: {
type: String,
required: false,
},
data() {
return { playIconSvg };
},
template: `
......@@ -28,9 +28,7 @@ module.exports = Vue.component('actions-component', {
data-method="post"
rel="nofollow"
class="js-manual-action-link">
<span class="js-action-play-icon-container" v-html="playIconSvg"></span>
${playIconSvg}
<span>
{{action.name}}
</span>
......
......@@ -46,21 +46,6 @@ module.exports = Vue.component('environment-item', {
required: false,
default: false,
},
commitIconSvg: {
type: String,
required: false,
},
playIconSvg: {
type: String,
required: false,
},
terminalIconSvg: {
type: String,
required: false,
},
},
computed: {
......@@ -487,9 +472,7 @@ module.exports = Vue.component('environment-item', {
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
:commit-icon-svg="commitIconSvg">
</commit-component>
:author="commitAuthor"/>
</div>
<p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title">
No deployments yet
......@@ -503,30 +486,23 @@ module.exports = Vue.component('environment-item', {
</span>
</td>
<td class="hidden-xs environments-actions">
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:play-icon-svg="playIconSvg"
:actions="manualActions">
</actions-component>
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL">
</external-url-component>
:external-url="externalURL"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path">
</stop-component>
:stop-url="model.stop_path"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-icon-svg="terminalIconSvg"
:terminal-path="model.terminal_path">
</terminal-button-component>
:terminal-path="model.terminal_path"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl">
</rollback-component>
:retry-url="retryUrl"/>
</div>
</td>
</tr>
......
......@@ -3,6 +3,7 @@
* Used in environments table.
*/
const Vue = require('vue');
const terminalIconSvg = require('icons/_icon_terminal.svg');
module.exports = Vue.component('terminal-button-component', {
props: {
......@@ -10,16 +11,16 @@ module.exports = Vue.component('terminal-button-component', {
type: String,
default: '',
},
terminalIconSvg: {
type: String,
default: '',
},
},
data() {
return { terminalIconSvg };
},
template: `
<a class="btn terminal-button"
:href="terminalPath">
<span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
${terminalIconSvg}
</a>
`,
});
......@@ -28,25 +28,10 @@ module.exports = Vue.component('environment-table-component', {
required: false,
default: false,
},
commitIconSvg: {
type: String,
required: false,
},
playIconSvg: {
type: String,
required: false,
},
terminalIconSvg: {
type: String,
required: false,
},
},
template: `
<table class="table ci-table environments">
<table class="table ci-table">
<thead>
<tr>
<th class="environments-name">Environment</th>
......@@ -54,7 +39,7 @@ module.exports = Vue.component('environment-table-component', {
<th class="environments-build">Job</th>
<th class="environments-commit">Commit</th>
<th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th>
<th class="environments-actions"></th>
</tr>
</thead>
<tbody>
......@@ -63,10 +48,7 @@ module.exports = Vue.component('environment-table-component', {
<tr is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"></tr>
:can-read-environment="canReadEnvironment"></tr>
</template>
</tbody>
</table>
......
......@@ -92,7 +92,7 @@ module.exports = Vue.component('environment-folder-view', {
this.isLoading = true;
return service.all()
return service.get()
.then(resp => ({
headers: resp.headers,
body: resp.json(),
......
......@@ -5,7 +5,7 @@ class EnvironmentsService {
this.environments = Vue.resource(endpoint);
}
all() {
get() {
return this.environments.get();
}
}
......
/**
* Based on project list search.
* Makes search request for groups when user types a value in the search input.
* Updates the html content of the page with the received one.
*/
export default class GroupsList {
constructor() {
this.groupsListFilterElement = document.querySelector('.js-groups-list-filter');
this.groupsListHolderElement = document.querySelector('.js-groups-list-holder');
this.initSearch();
}
initSearch() {
this.debounceFilter = _.debounce(this.filterResults.bind(this), 500);
this.groupsListFilterElement.removeEventListener('input', this.debounceFilter);
this.groupsListFilterElement.addEventListener('input', this.debounceFilter);
}
filterResults() {
const form = document.querySelector('form#group-filter-form');
const groupFilterUrl = `${form.getAttribute('action')}?${$(form).serialize()}`;
$(this.groupsListHolderElement).fadeTo(250, 0.5);
return $.ajax({
url: form.getAttribute('action'),
data: $(form).serialize(),
type: 'GET',
dataType: 'json',
context: this,
complete() {
$(this.groupsListHolderElement).fadeTo(250, 1);
},
success(data) {
this.groupsListHolderElement.innerHTML = data.html;
// Change url so if user reload a page - search results are saved
return window.history.replaceState({
page: groupFilterUrl,
}, document.title, groupFilterUrl);
},
});
}
}
/* global Vue */
import stopwatchSvg from 'icons/_icon_stopwatch.svg';
require('../../../lib/utils/pretty_time');
(() => {
......@@ -11,7 +13,6 @@ require('../../../lib/utils/pretty_time');
'showNoTimeTrackingState',
'timeSpentHumanReadable',
'timeEstimateHumanReadable',
'stopwatchSvg',
],
methods: {
abbreviateTime(timeStr) {
......@@ -20,7 +21,7 @@ require('../../../lib/utils/pretty_time');
},
template: `
<div class='sidebar-collapsed-icon'>
<div v-html='stopwatchSvg'></div>
${stopwatchSvg}
<div class='time-tracking-collapsed-summary'>
<div class='compare' v-if='showComparisonState'>
<span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
......
......@@ -15,7 +15,6 @@ require('./comparison_pane');
'time_spent',
'human_time_estimate',
'human_time_spent',
'stopwatchSvg',
'docsUrl',
],
data() {
......@@ -71,20 +70,19 @@ require('./comparison_pane');
:show-spent-only-state='showSpentOnlyState'
:show-estimate-only-state='showEstimateOnlyState'
:time-spent-human-readable='timeSpentHumanReadable'
:time-estimate-human-readable='timeEstimateHumanReadable'
:stopwatch-svg='stopwatchSvg'>
:time-estimate-human-readable='timeEstimateHumanReadable'>
</time-tracking-collapsed-state>
<div class='title hide-collapsed'>
Time tracking
<div class='help-button pull-right'
v-if='!showHelpState'
@click='toggleHelpState(true)'>
<i class='fa fa-question-circle'></i>
<i class='fa fa-question-circle' aria-hidden='true'></i>
</div>
<div class='close-help-button pull-right'
v-if='showHelpState'
@click='toggleHelpState(false)'>
<i class='fa fa-close'></i>
<i class='fa fa-close' aria-hidden='true'></i>
</div>
</div>
<div class='time-tracking-content hide-collapsed'>
......
......@@ -39,8 +39,9 @@ require('../../subbable_resource');
listenForSlashCommands() {
$(document).on('ajax:success', '.gfm-form', (e, data) => {
const subscribedCommands = ['spend_time', 'time_estimate'];
const changedCommands = data.commands_changes;
const changedCommands = data.commands_changes
? Object.keys(data.commands_changes)
: [];
if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) {
this.fetchIssuable();
}
......
......@@ -246,17 +246,6 @@
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
});
/**
* Transforms a DOMStringMap into a plain object.
*
* @param {DOMStringMap} DOMStringMapObject
* @returns {Object}
*/
w.gl.utils.DOMStringMapToObject = DOMStringMapObject => Object.keys(DOMStringMapObject).reduce((acc, element) => {
acc[element] = DOMStringMapObject[element];
return acc;
}, {});
/**
* Updates the search parameter of a URL given the parameter and values provided.
*
......
......@@ -65,9 +65,10 @@ require('vendor/latinise');
}
};
gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine;
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
// Remove the first newline
if (selected.indexOf('\n') === 0) {
......@@ -82,7 +83,17 @@ require('vendor/latinise');
}
selectedSplit = selected.split('\n');
startChar = !wrap && textArea.selectionStart > 0 ? '\n' : '';
if (!wrap) {
lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
// Check whether the current line is empty or consists only of spaces(=handle as empty)
if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
currentLineEmpty = true;
}
}
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
if (blockTag != null) {
......@@ -142,9 +153,8 @@ require('vendor/latinise');
}
};
gl.text.updateText = function(textArea, tag, blockTag, wrap) {
var $textArea, oldVal, selected, text;
var $textArea, selected, text;
$textArea = $(textArea);
oldVal = $textArea.val();
textArea = $textArea.get(0);
text = $textArea.val();
selected = this.selectedText(text, textArea);
......
......@@ -129,8 +129,9 @@ require('./smart_interval');
};
MergeRequestWidget.prototype.getMergeStatus = function() {
return $.get(this.opts.merge_check_url, function(data) {
return $.get(this.opts.merge_check_url, (data) => {
var $html = $(data);
this.updateMergeButton(this.status, this.hasCi, $html);
$('.mr-widget-body').replaceWith($html.find('.mr-widget-body'));
$('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer'));
});
......@@ -154,9 +155,9 @@ require('./smart_interval');
return $.getJSON(this.opts.ci_status_url, (function(_this) {
return function(data) {
var message, status, title;
if (!data.status) {
return;
}
_this.status = data.status;
_this.hasCi = data.has_ci;
_this.updateMergeButton(_this.status, _this.hasCi);
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (data.status !== _this.opts.ci_status ||
data.sha !== _this.opts.ci_sha ||
......@@ -232,36 +233,45 @@ require('./smart_interval');
return;
}
$('.ci_widget').hide();
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
if (indexOf.call(allowed_states, state) !== -1) {
$('.ci_widget.ci-' + state).show();
$('.ci_widget.ci-' + state).show();
this.initMiniPipelineGraph();
};
MergeRequestWidget.prototype.showCICoverage = function(coverage) {
var text = `Coverage ${coverage}%`;
return $('.ci_widget:visible .ci-coverage').text(text);
};
MergeRequestWidget.prototype.updateMergeButton = function(state, hasCi, $html) {
const allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
let stateClass = 'btn-danger';
if (!hasCi) {
stateClass = 'btn-create';
} else if (indexOf.call(allowed_states, state) !== -1) {
switch (state) {
case "failed":
case "canceled":
case "not_found":
this.setMergeButtonClass('btn-danger');
stateClass = 'btn-danger';
break;
case "running":
this.setMergeButtonClass('btn-info');
stateClass = 'btn-info';
break;
case "success":
case "success_with_warnings":
this.setMergeButtonClass('btn-create');
stateClass = 'btn-create';
}
} else {
$('.ci_widget.ci-error').show();
this.setMergeButtonClass('btn-danger');
stateClass = 'btn-danger';
}
};
MergeRequestWidget.prototype.showCICoverage = function(coverage) {
var text;
text = 'Coverage ' + coverage + '%';
return $('.ci_widget:visible .ci-coverage').text(text);
this.setMergeButtonClass(stateClass, $html);
};
MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) {
return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class);
MergeRequestWidget.prototype.setMergeButtonClass = function(css_class, $html = $('.mr-state-widget')) {
return $html.find('.js-merge-button').removeClass('btn-danger btn-info btn-create').addClass(css_class);
};
MergeRequestWidget.prototype.updatePipelineUrls = function(id) {
......
......@@ -15,15 +15,15 @@
});
$(document)
.off('click', '.accept_merge_request')
.on('click', '.accept_merge_request', () => {
$('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
.off('click', '.accept-merge-request')
.on('click', '.accept-merge-request', () => {
$('.js-merge-button, .js-merge-when-pipeline-succeeds-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
});
$(document)
.off('click', '.merge_when_build_succeeds')
.on('click', '.merge_when_build_succeeds', () => {
$('#merge_when_build_succeeds').val('1');
.off('click', '.merge-when-pipeline-succeeds')
.on('click', '.merge-when-pipeline-succeeds', () => {
$('#merge_when_pipeline_succeeds').val('1');
});
$(document)
......
......@@ -246,12 +246,21 @@ require('./task_list');
};
Notes.prototype.handleCreateChanges = function(note) {
var votesBlock;
if (typeof note === 'undefined') {
return;
}
if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) {
$.get(mrRefreshWidgetUrl);
if (note.commands_changes) {
if ('merge' in note.commands_changes) {
$.get(mrRefreshWidgetUrl);
}
if ('emoji_award' in note.commands_changes) {
votesBlock = $('.js-awards-block').eq(0);
gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.commands_changes.emoji_award);
return gl.awardsHandler.scrollToAwards();
}
}
};
......@@ -262,26 +271,16 @@ require('./task_list');
*/
Notes.prototype.renderNote = function(note) {
var $notesList, votesBlock;
var $notesList;
if (!note.valid) {
if (note.award) {
new Flash('You have already awarded this emoji!', 'alert', this.parentTimeline);
}
else {
if (note.errors.commands_only) {
new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
this.refresh();
}
if (note.errors.commands_only) {
new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
this.refresh();
}
return;
}
if (note.award) {
votesBlock = $('.js-awards-block').eq(0);
gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
return gl.awardsHandler.scrollToAwards();
// render note if it not present in loaded list
// or skip if rendered
} else if (this.isNewNote(note)) {
if (this.isNewNote(note)) {
this.note_ids.push(note.id);
$notesList = $('ul.main-notes-list');
$notesList.append(note.html).syntaxHighlight();
......
......@@ -13,7 +13,7 @@
this.onPickImageClick = this.onPickImageClick.bind(this);
this.fileInput = $(input);
this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`);
this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `${this.fileInput.attr('id')}-trigger`);
this.exportWidth = exportWidth;
this.exportHeight = exportHeight;
this.cropBoxWidth = cropBoxWidth;
......
......@@ -16,9 +16,6 @@ require('./shortcuts');
Mousetrap.bind('g p', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
});
Mousetrap.bind('g e', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
});
Mousetrap.bind('g f', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
});
......@@ -31,9 +28,6 @@ require('./shortcuts');
Mousetrap.bind('g n', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
});
Mousetrap.bind('g g', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs');
});
Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
});
......
......@@ -43,6 +43,8 @@ class UserCallout {
this.userCalloutBody.append($template);
$template.find(closeButton).on('click', e => this.dismissCallout(e));
$template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
} else {
this.userCalloutBody.remove();
}
}
......@@ -50,7 +52,7 @@ class UserCallout {
Cookies.set(USER_CALLOUT_COOKIE, 'true');
const $currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('close-user-callout')) {
this.userCalloutBody.empty();
this.userCalloutBody.remove();
}
}
}
......
......@@ -11,15 +11,10 @@ $(() => new Vue({
data() {
const project = document.querySelector('.pipelines');
const svgs = document.querySelector('.pipeline-svgs').dataset;
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgs);
return {
scope: project.dataset.url,
store: new gl.PipelineStore(),
svgs: svgsObject,
};
},
components: {
......@@ -27,10 +22,8 @@ $(() => new Vue({
},
template: `
<vue-pipelines
:scope='scope'
:store='store'
:svgs='svgs'
>
:scope="scope"
:store="store">
</vue-pipelines>
`,
}));
/* global Vue, Flash, gl */
/* eslint-disable no-param-reassign, no-alert */
/* eslint-disable no-param-reassign, no-alert */
const playIconSvg = require('icons/_icon_play.svg');
((gl) => {
gl.VuePipelineActions = Vue.extend({
props: ['pipeline', 'svgs'],
props: ['pipeline'],
computed: {
actions() {
return this.pipeline.details.manual_actions.length > 0;
......@@ -31,8 +32,13 @@
}
},
},
data() {
return { playIconSvg };
},
template: `
<td class="pipeline-actions hidden-xs">
<td class="pipeline-actions">
<div class="pull-right">
<div class="btn-group">
<div class="btn-group" v-if="actions">
......@@ -42,7 +48,7 @@
title="Manual job"
data-placement="top"
aria-label="Manual job">
<span v-html="svgs.iconPlay" aria-hidden="true"></span>
<span v-html="playIconSvg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
......@@ -50,8 +56,8 @@
<a
rel="nofollow"
data-method="post"
:href="action.path">
<span v-html="svgs.iconPlay" aria-hidden="true"></span>
:href="action.path" >
<span v-html="playIconSvg" aria-hidden="true"></span>
<span>{{action.name}}</span>
</a>
</li>
......
......@@ -27,7 +27,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
pageRequest: false,
};
},
props: ['scope', 'store', 'svgs'],
props: ['scope', 'store'],
created() {
const pagenum = gl.utils.getParameterByName('page');
const scope = gl.utils.getParameterByName('scope');
......@@ -45,18 +45,15 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
methods: {
/**
* Changes the URL according to the pagination component.
* Will change the page number and update the URL.
*
* If no scope is provided, 'all' is assumed.
*
* Pagination component sends "null" when no scope is provided.
*
* @param {Number} pagenum
* @param {String} apiScope = 'all'
* @param {Number} pageNumber desired page to go to.
*/
change(pagenum, apiScope) {
if (!apiScope) apiScope = 'all';
gl.utils.visitUrl(`?scope=${apiScope}&page=${pagenum}`);
change(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
},
template: `
......@@ -73,10 +70,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
</div>
<div class="table-holder" v-if='!pageRequest && pipelines.length'>
<pipelines-table-component
:pipelines='pipelines'
:svgs='svgs'>
</pipelines-table-component>
<pipelines-table-component :pipelines='pipelines'/>
</div>
<gl-pagination
......
/* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */
import canceledSvg from 'icons/_icon_status_canceled_borderless.svg';
import createdSvg from 'icons/_icon_status_created_borderless.svg';
import failedSvg from 'icons/_icon_status_failed_borderless.svg';
import manualSvg from 'icons/_icon_status_manual_borderless.svg';
import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
import runningSvg from 'icons/_icon_status_running_borderless.svg';
import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
import successSvg from 'icons/_icon_status_success_borderless.svg';
import warningSvg from 'icons/_icon_status_warning_borderless.svg';
((gl) => {
gl.VueStage = Vue.extend({
data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
};
},
props: {
stage: {
type: Object,
required: true,
},
svgs: {
type: Object,
required: true,
},
match: {
type: Function,
required: true,
},
},
updated() {
......@@ -73,11 +88,6 @@
tooltip() {
return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
},
svg() {
const { icon } = this.stage.status;
const stageIcon = icon.replace(/icon/i, 'stage_icon');
return this.svgs[this.match(stageIcon)];
},
triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
},
......@@ -91,8 +101,7 @@
data-placement="top"
data-toggle="dropdown"
type="button"
:aria-label="stage.title"
>
:aria-label="stage.title">
<span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
......@@ -101,8 +110,7 @@
<div
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner"
>
v-html="buildsOrSpinner">
</div>
</ul>
</div>
......
/* global Vue, gl */
/* eslint-disable no-param-reassign */
import canceledSvg from 'icons/_icon_status_canceled.svg';
import createdSvg from 'icons/_icon_status_created.svg';
import failedSvg from 'icons/_icon_status_failed.svg';
import manualSvg from 'icons/_icon_status_manual.svg';
import pendingSvg from 'icons/_icon_status_pending.svg';
import runningSvg from 'icons/_icon_status_running.svg';
import skippedSvg from 'icons/_icon_status_skipped.svg';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
((gl) => {
gl.VueStatusScope = Vue.extend({
props: [
'pipeline', 'svgs', 'match',
'pipeline',
],
data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return {
svg: svgsDictionary[this.pipeline.details.status.icon],
};
},
computed: {
cssClasses() {
const cssObject = { 'ci-status': true };
cssObject[`ci-${this.pipeline.details.status.group}`] = true;
return cssObject;
},
svg() {
return this.svgs[this.match(this.pipeline.details.status.icon)];
},
detailsPath() {
const { status } = this.pipeline.details;
return status.has_details ? status.details_path : false;
},
content() {
return `${this.svg} ${this.pipeline.details.status.text}`;
},
},
template: `
<td class="commit-link">
<a
:class='cssClasses'
:href='detailsPath'
v-html='svg + pipeline.details.status.text'
>
:class="cssClasses"
:href="detailsPath"
v-html="content">
</a>
</td>
`,
......
......@@ -4,14 +4,17 @@
window.Vue = require('vue');
require('../lib/utils/datetime_utility');
const iconTimerSvg = require('../../../views/shared/icons/_icon_timer.svg');
((gl) => {
gl.VueTimeAgo = Vue.extend({
data() {
return {
currentTime: new Date(),
iconTimerSvg,
};
},
props: ['pipeline', 'svgs'],
props: ['pipeline'],
computed: {
timeAgo() {
return gl.utils.getTimeago();
......@@ -56,7 +59,7 @@ require('../lib/utils/datetime_utility');
template: `
<td class="pipelines-time-ago">
<p class="duration" v-if='duration'>
<span v-html='svgs.iconTimer'></span>
<span v-html="iconTimerSvg"></span>
{{duration}}
</p>
<p class="finished-at" v-if='timeStopped'>
......
/* global Vue */
window.Vue = require('vue');
const commitIconSvg = require('icons/_icon_commit.svg');
(() => {
window.gl = window.gl || {};
......@@ -69,11 +70,6 @@ window.Vue = require('vue');
required: false,
default: () => ({}),
},
commitIconSvg: {
type: String,
required: false,
},
},
computed: {
......@@ -116,6 +112,10 @@ window.Vue = require('vue');
},
},
data() {
return { commitIconSvg };
},
template: `
<div class="branch-commit">
......
......@@ -21,14 +21,6 @@ require('./pipelines_table_row');
default: () => ([]),
},
/**
* TODO: Remove this when we have webpack.
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
},
components: {
......@@ -44,15 +36,14 @@ require('./pipelines_table_row');
<th class="js-pipeline-commit pipeline-commit">Commit</th>
<th class="js-pipeline-stages pipeline-stages">Stages</th>
<th class="js-pipeline-date pipeline-date"></th>
<th class="js-pipeline-actions pipeline-actions hidden-xs"></th>
<th class="js-pipeline-actions pipeline-actions"></th>
</tr>
</thead>
<tbody>
<template v-for="model in pipelines"
v-bind:model="model">
<tr is="pipelines-table-row-component"
:pipeline="model"
:svgs="svgs"></tr>
:pipeline="model"></tr>
</template>
</tbody>
</table>
......
......@@ -25,14 +25,6 @@ require('./commit');
default: () => ({}),
},
/**
* TODO: Remove this when we have webpack;
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
},
components: {
......@@ -174,30 +166,9 @@ require('./commit');
},
},
methods: {
/**
* FIXME: This should not be in this component but in the components that
* need this function.
*
* Used to render SVGs in the following components:
* - status-scope
* - dropdown-stage
*
* @param {String} string
* @return {String}
*/
match(string) {
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
},
},
template: `
<tr class="commit">
<status-scope
:pipeline="pipeline"
:svgs="svgs"
:match="match">
</status-scope>
<status-scope :pipeline="pipeline"/>
<pipeline-url :pipeline="pipeline"></pipeline-url>
......@@ -208,26 +179,20 @@ require('./commit');
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
:commit-icon-svg="svgs.commitIconSvg">
</commit-component>
:author="commitAuthor"/>
</td>
<td class="stage-cell">
<div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages">
<dropdown-stage
:stage="stage"
:svgs="svgs"
:match="match">
</dropdown-stage>
<dropdown-stage :stage="stage"/>
</div>
</td>
<time-ago :pipeline="pipeline" :svgs="svgs"></time-ago>
<time-ago :pipeline="pipeline"/>
<pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions>
<pipeline-actions :pipeline="pipeline" />
</tr>
`,
});
......
......@@ -19,12 +19,11 @@ window.Vue = require('vue');
/**
This function will take the information given by the pagination component
And make a new Turbolinks call
Here is an example `change` method:
change(pagenum, apiScope) {
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
change(pagenum) {
gl.utils.visitUrl(`?page=${pagenum}`);
},
*/
......@@ -57,8 +56,6 @@ window.Vue = require('vue');
},
methods: {
changePage(e) {
const apiScope = gl.utils.getParameterByName('scope');
const text = e.target.innerText;
const { totalPages, nextPage, previousPage } = this.pageInfo;
......@@ -66,19 +63,19 @@ window.Vue = require('vue');
case SPREAD:
break;
case LAST:
this.change(totalPages, apiScope);
this.change(totalPages);
break;
case NEXT:
this.change(nextPage, apiScope);
this.change(nextPage);
break;
case PREV:
this.change(previousPage, apiScope);
this.change(previousPage);
break;
case FIRST:
this.change(1, apiScope);
this.change(1);
break;
default:
this.change(+text, apiScope);
this.change(+text);
break;
}
},
......
.calender-block {
padding-left: 0;
padding-right: 0;
border-top: 0;
direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
......
......@@ -107,11 +107,12 @@
&.fa-spinner {
font-size: 16px;
margin-top: -8px;
margin-top: -3px;
}
}
.fa-chevron-down {
.fa-chevron-down,
.fa-spinner {
position: absolute;
top: 11px;
right: 8px;
......@@ -192,6 +193,10 @@
&.is-focused {
background-color: $dropdown-link-hover-bg;
text-decoration: none;
.badge {
background-color: darken($row-hover, 5%);
}
}
&.dropdown-menu-empty-link {
......@@ -228,6 +233,12 @@
padding: 5px 8px;
color: $gl-text-color-secondary;
}
.badge {
position: absolute;
right: 8px;
top: 5px;
}
}
.dropdown-menu-drop-up {
......
......@@ -271,6 +271,7 @@ span.idiff {
font-size: 13px;
line-height: 28px;
display: inline-block;
float: none;
}
}
}
......@@ -148,34 +148,20 @@ header {
}
.header-logo {
position: absolute;
left: 50%;
top: 7px;
display: inline-block;
margin: 0 7px 0 2px;
position: relative;
top: 10px;
transition-duration: .3s;
z-index: 999;
#logo {
position: relative;
left: -50%;
}
svg,
img {
height: 36px;
height: 28px;
}
&:hover {
cursor: pointer;
}
@media (max-width: $screen-xs-max) {
right: 20px;
left: auto;
#logo {
left: auto;
}
}
}
.title {
......@@ -183,7 +169,6 @@ header {
padding-right: 20px;
margin: 0;
font-size: 18px;
max-width: 385px;
display: inline-block;
line-height: $header-height;
font-weight: normal;
......@@ -193,14 +178,18 @@ header {
vertical-align: top;
white-space: nowrap;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
max-width: 300px;
}
@media (max-width: $screen-xs-max) {
max-width: 190px;
}
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
max-width: 428px;
}
@media (min-width: $screen-lg-min) {
max-width: 685px;
}
a {
color: $gl-text-color;
......
......@@ -73,10 +73,6 @@
right: $gutter_collapsed_width;
}
}
&.with-overlay {
padding-right: $gutter_collapsed_width;
}
}
.right-sidebar {
......
......@@ -21,6 +21,7 @@ $dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5;
$dark-expanded-bg: #3e3e3e;
$dark-c: #969896;
$dark-err: #c66;
$dark-k: #b294bb;
......@@ -155,6 +156,22 @@ $dark-il: #de935f;
.line_content.match {
@include dark-diff-match-line;
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $black;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $dark-expanded-bg;
border-color: $dark-expanded-bg;
}
}
}
// highlight line via anchor
......
......@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5;
$monokai-expanded-bg: #3e3e3e;
$monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15);
......@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e;
.line_content.match {
@include dark-diff-match-line;
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $black;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $monokai-expanded-bg;
border-color: $monokai-expanded-bg;
}
}
}
// highlight line via anchor
......
......@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5;
$solarized-dark-expanded-bg: #010d10;
$solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1;
......@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198;
.line_content.match {
@include dark-diff-match-line;
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $black;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $solarized-dark-expanded-bg;
border-color: $solarized-dark-expanded-bg;
}
}
}
// highlight line via anchor
......
......@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc;
$solarized-light-expanded-border: #d2cdbd;
$solarized-light-expanded-bg: #ece6d4;
$solarized-light-c: #93a1a1;
$solarized-light-err: #586e75;
$solarized-light-g: #586e75;
......@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198;
.line_content.match {
@include matchLine;
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $solarized-light-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $solarized-light-expanded-bg;
border-color: $solarized-light-expanded-bg;
}
}
}
// highlight line via anchor
......
......@@ -8,6 +8,8 @@ $white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
......@@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
......
......@@ -133,8 +133,13 @@
width: 35px;
font-weight: normal;
&:hover {
text-decoration: underline;
&[disabled] {
cursor: default;
&:hover,
&:active {
text-decoration: none;
}
}
}
}
......
......@@ -15,112 +15,97 @@
padding-top: 20px;
}
@media (max-width: $screen-xs-max) {
.environments-container {
.environments-container {
.table-holder {
width: 100%;
overflow: auto;
}
}
.environments {
table-layout: fixed;
.environments-commit,
.environments-actions,
.environments-deploy,
.environments-build,
.environments-date {
position: static;
float: none;
display: table-cell;
}
.environments-commit,
.environments-actions {
width: 20%;
}
.environments-date {
width: 10%;
}
.environments-name,
.environments-deploy,
.environments-build {
width: 15%;
}
.environment-name,
.environments-build-cell,
.deployment-column {
word-break: break-all;
}
.deployment-column {
.avatar {
float: none;
.table.ci-table {
.environments-actions {
min-width: 200px;
}
}
.btn-group {
.environments-commit,
.environments-actions {
width: 20%;
}
> a {
color: $gl-text-color-secondary;
.environments-date {
width: 10%;
}
svg path {
fill: $gl-text-color-secondary;
.environments-name,
.environments-deploy,
.environments-build {
width: 15%;
}
.dropdown {
outline: none;
.deployment-column {
> span {
word-break: break-all;
}
.avatar {
float: none;
}
}
}
.btn-group {
.commit-title {
margin: 0;
}
> a {
color: $gl-text-color-secondary;
}
.avatar-image-container {
text-decoration: none;
}
svg path {
fill: $gl-text-color-secondary;
}
.icon-play {
height: 13px;
width: 12px;
}
.dropdown {
outline: none;
}
}
.external-url,
.dropdown-new {
color: $gl-text-color-secondary;
}
.commit-title {
margin: 0;
}
.dropdown-menu {
.avatar-image-container {
text-decoration: none;
}
.fa {
margin-right: 6px;
color: $gl-text-color-secondary;
.icon-play {
height: 13px;
width: 12px;
}
}
.build-link,
.branch-name {
color: $gl-text-color;
}
.external-url,
.dropdown-new {
color: $gl-text-color-secondary;
}
.stop-env-link,
.external-url {
color: $gl-text-color-secondary;
.dropdown-menu {
.fa {
margin-right: 6px;
color: $gl-text-color-secondary;
}
}
.stop-env-icon {
font-size: 14px;
.build-link,
.branch-name {
color: $gl-text-color;
}
}
.deployment {
.build-column {
.stop-env-link,
.external-url {
color: $gl-text-color-secondary;
.stop-env-icon {
font-size: 14px;
}
}
.deployment .build-column {
.build-link {
color: $gl-text-color;
}
......@@ -129,34 +114,32 @@
float: none;
}
}
}
.folder-icon {
margin-right: 3px;
color: $gl-text-color-secondary;
display: inline-block;
.fa:nth-child(1) {
.folder-icon {
margin-right: 3px;
color: $gl-text-color-secondary;
display: inline-block;
.fa:nth-child(1) {
margin-right: 3px;
}
}
}
.folder-name {
cursor: pointer;
color: $gl-text-color-secondary;
display: inline-block;
}
}
.folder-name {
cursor: pointer;
color: $gl-text-color-secondary;
display: inline-block;
}
.table.ci-table.environments {
.icon-container {
width: 20px;
text-align: center;
}
.icon-container {
width: 20px;
text-align: center;
}
.branch-commit {
.commit-id {
margin-right: 0;
.branch-commit {
.commit-id {
margin-right: 0;
}
}
}
}
......@@ -155,7 +155,7 @@
@media (max-width: $screen-xs-max) {
.event-item {
padding-left: $gl-padding;
padding-left: 0;
.event-title {
white-space: normal;
......@@ -169,8 +169,7 @@
.event-body {
margin: 0;
border-left: 2px solid $events-body-border;
padding-left: 10px;
padding-left: 0;
}
.event-item-timestamp {
......
......@@ -28,7 +28,7 @@
background-color: $gl-success;
}
.accept_merge_request {
.accept-merge-request {
&.ci-pending,
&.ci-running {
@include btn-blue;
......@@ -41,6 +41,12 @@
@include btn-red;
}
}
.dropdown-toggle {
.fa {
color: inherit;
}
}
}
.accept-control {
......
......@@ -105,6 +105,7 @@
@media (max-width: $screen-md-max) {
.content-list {
&.pipelines,
&.environments-container,
&.builds-content-list {
width: 100%;
overflow: auto;
......
......@@ -279,7 +279,7 @@ table.u2f-registrations {
}
.user-callout {
margin: 24px auto 0;
margin: 0 auto;
.bordered-box {
border: 1px solid $border-color;
......@@ -287,6 +287,7 @@ table.u2f-registrations {
}
.landing {
margin-top: $gl-padding;
margin-bottom: $gl-padding;
.close {
......
......@@ -178,3 +178,29 @@
margin-left: $btn-side-margin;
}
}
.repo-charts {
.sub-header {
margin: 20px 0;
}
.sub-header-block.border-top {
margin-top: 20px;
padding: 0;
border-top: 1px solid $white-dark;
border-bottom: none;
}
.commit-stats li {
font-size: 16px;
}
.tree-ref-header {
margin-bottom: 20px;
h4 {
margin: 0;
line-height: 36px;
}
}
}
......@@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base
end
end
def authenticate_user!(*args)
if redirect_to_home_page_url?
return redirect_to current_application_settings.home_page_url
end
super(*args)
end
def log_exception(exception)
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
......@@ -130,10 +122,6 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
# Enabling HSTS for non-standard ports would send clients to the wrong port
if Gitlab.config.gitlab.https && Gitlab.config.gitlab.port == 443
headers['Strict-Transport-Security'] = 'max-age=31536000'
end
end
def validate_user_service_ticket!
......@@ -287,19 +275,6 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
return false unless current_application_settings.home_page_url.present?
home_page_url = current_application_settings.home_page_url.chomp('/')
root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
return false if root_urls.include?(home_page_url)
current_user.nil? && root_path == request.path
end
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
......
module Ci
class ProjectsController < ::ApplicationController
before_action :project
before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index]
skip_before_action :authenticate_user!, only: [:badge]
protect_from_forgery
def index
redirect_to root_path
end
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.namespace, project)
end
# Project status badge
# Image with build status for sha or ref
#
# This action in DEPRECATED, this is here only for backwards compatibility
# with projects migrated from GitLab CI.
#
def badge
return render_404 unless @project
image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type: "image/svg+xml"
end
protected
def project
@project ||= Project.find_by(ci_id: params[:id].to_i)
end
def no_cache
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
def authorize_read_project!
return access_denied! unless can?(current_user, :read_project, project)
end
end
end
......@@ -4,10 +4,9 @@ module CreatesCommit
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables
start_branch = @mr_target_branch unless initial_commit?
commit_params = @commit_params.merge(
start_project: @mr_target_project,
start_branch: start_branch,
start_branch: @mr_target_branch,
target_branch: @mr_source_branch
)
......@@ -17,12 +16,16 @@ module CreatesCommit
if result[:status] == :success
update_flash_notice(success_notice)
success_path = final_success_path(success_path)
respond_to do |format|
format.html { redirect_to final_success_path(success_path) }
format.json { render json: { message: "success", filePath: final_success_path(success_path) } }
format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: success_path } }
end
else
flash[:alert] = result[:message]
failure_path = failure_path.call if failure_path.respond_to?(:call)
respond_to do |format|
format.html do
if failure_view
......@@ -58,9 +61,13 @@ module CreatesCommit
end
def final_success_path(success_path)
return success_path unless create_merge_request?
if create_merge_request?
merge_request_exists? ? existing_merge_request_path : new_merge_request_path
else
success_path = success_path.call if success_path.respond_to?(:call)
merge_request_exists? ? existing_merge_request_path : new_merge_request_path
success_path
end
end
def new_merge_request_path
......@@ -92,47 +99,26 @@ module CreatesCommit
end
def create_merge_request?
# XXX: Even if the field is set, if we're checking the same branch
# Even if the field is set, if we're checking the same branch
# as the target branch in the same project,
# we don't want to create a merge request.
params[:create_merge_request].present? &&
(different_project? || @ref != @target_branch)
(different_project? || @mr_target_branch != @mr_source_branch)
end
# TODO: We should really clean this up
def set_commit_variables
@mr_source_project =
if can?(current_user, :push_code, @project)
# Edit file in this project
@project
else
# Merge request from fork to this project
current_user.fork_of(@project)
end
if can?(current_user, :push_code, @project)
@mr_source_project = @project
@target_branch ||= @ref
else
@mr_source_project = current_user.fork_of(@project)
@target_branch ||= @mr_source_project.repository.next_branch('patch')
end
# Merge request to this project
@mr_target_project = @project
@mr_target_branch = @ref || @target_branch
@mr_source_branch = guess_mr_source_branch
end
def initial_commit?
@mr_target_branch.nil? ||
!@mr_target_project.repository.branch_exists?(@mr_target_branch)
end
@mr_target_branch ||= @ref || @target_branch
def guess_mr_source_branch
# XXX: Happens when viewing a commit without a branch. In this case,
# @target_branch would be the default branch for @mr_source_project,
# however we want a generated new branch here. Thus we can't use
# @target_branch, but should pass nil to indicate that we want a new
# branch instead of @target_branch.
return if
create_merge_request? &&
# XXX: Don't understand why rubocop prefers this indention
@mr_source_project.repository.branch_exists?(@target_branch)
@target_branch
@mr_source_branch = @target_branch
end
end
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
@group_members = current_user.group_members.includes(source: :route).page(params[:page])
@group_members = current_user.group_members.includes(source: :route).joins(:group)
@group_members = @group_members.merge(Group.search(params[:filter_groups])) if params[:filter_groups].present?
@group_members = @group_members.merge(Group.sort(@sort = params[:sort]))
@group_members = @group_members.page(params[:page])
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/groups/_groups", locals: { group_members: @group_members })
}
end
end
end
end
class Explore::GroupsController < Explore::ApplicationController
def index
@groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.search(params[:filter_groups]) if params[:filter_groups].present?
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page])
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("explore/groups/_groups", locals: { groups: @groups })
}
end
end
end
end
......@@ -5,7 +5,7 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
InvalidPathError = Class.new(StandardError)
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
......@@ -24,7 +24,7 @@ class Projects::BlobController < Projects::ApplicationController
def create
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)),
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) },
failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end
......@@ -40,7 +40,7 @@ class Projects::BlobController < Projects::ApplicationController
def update
@path = params[:file_path] if params[:file_path].present?
create_commit(Files::UpdateService, success_path: after_edit_path,
create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
......@@ -62,7 +62,7 @@ class Projects::BlobController < Projects::ApplicationController
def destroy
create_commit(Files::DestroyService, success_notice: "The file has been successfully deleted.",
success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
success_path: -> { namespace_project_tree_path(@project.namespace, @project, @target_branch) },
failure_view: :show,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
......
......@@ -51,23 +51,35 @@ class Projects::CommitController < Projects::ApplicationController
def revert
assign_change_commit_vars
return render_404 if @target_branch.blank?
return render_404 if @start_branch.blank?
@target_branch = create_new_branch? ? @commit.revert_branch_name : @start_branch
@mr_target_branch = @start_branch
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.",
success_path: successful_change_path, failure_path: failed_change_path)
success_path: -> { successful_change_path }, failure_path: failed_change_path)
end
def cherry_pick
assign_change_commit_vars
return render_404 if @target_branch.blank?
return render_404 if @start_branch.blank?
@target_branch = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch
@mr_target_branch = @start_branch
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
success_path: successful_change_path, failure_path: failed_change_path)
success_path: -> { successful_change_path }, failure_path: failed_change_path)
end
private
def create_new_branch?
params[:create_merge_request].present? || !can?(current_user, :push_code, @project)
end
def successful_change_path
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch)
end
......@@ -78,7 +90,7 @@ class Projects::CommitController < Projects::ApplicationController
def referenced_merge_request_url
if merge_request = @commit.merged_merge_request(current_user)
namespace_project_merge_request_url(@project.namespace, @project, merge_request)
namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request)
end
end
......@@ -94,7 +106,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts)
@notes_count = commit.notes.count
@environment = EnvironmentsFinder.new(@project, current_user, commit: @commit).execute.last
end
......@@ -118,11 +130,7 @@ class Projects::CommitController < Projects::ApplicationController
end
def assign_change_commit_vars
@commit = project.commit(params[:id])
@target_branch = params[:target_branch]
@commit_params = {
commit: @commit,
create_merge_request: params[:create_merge_request].present? || different_project?
}
@start_branch = params[:start_branch]
@commit_params = { commit: @commit }
end
end
......@@ -17,6 +17,25 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
redirect_to action: 'charts'
end
def languages
redirect_to action: 'charts'
end
def charts
get_commits
get_languages
end
def ci
redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project)
end
private
def get_commits
@commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
......@@ -24,15 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController
@commits_per_month = @commits_graph.commits_per_month
end
def ci
@charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(project)
@charts[:month] = Ci::Charts::MonthChart.new(project)
@charts[:year] = Ci::Charts::YearChart.new(project)
@charts[:build_times] = Ci::Charts::BuildTime.new(project)
end
def languages
def get_languages
@languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
total = @languages.map(&:last).sum
......@@ -52,8 +63,6 @@ class Projects::GraphsController < Projects::ApplicationController
end
end
private
def fetch_graph
@commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true)
@log = []
......
......@@ -10,11 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check,
:ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
:ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines]
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_pipeline_succeeds, :merge_check]
before_action :define_commit_vars, only: [:diffs]
before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
......@@ -245,9 +245,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do
define_pipelines_vars
render json: PipelineSerializer
render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
}
end
end
end
......@@ -322,12 +324,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge_check
@merge_request.check_if_can_be_merged
@pipelines = @merge_request.all_pipelines
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
def cancel_merge_when_build_succeeds
unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
def cancel_merge_when_pipeline_succeeds
unless @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
return access_denied!
end
......@@ -339,9 +342,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
# Disable the CI check if merge_when_build_succeeds is enabled since we have
# Disable the CI check if merge_when_pipeline_succeeds is enabled since we have
# to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?)
@status = :failed
return
end
......@@ -353,7 +356,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present?
if params[:merge_when_pipeline_succeeds].present?
unless @merge_request.head_pipeline
@status = :failed
return
......@@ -364,7 +367,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
@status = :merge_when_pipeline_succeeds
elsif @merge_request.head_pipeline.success?
# This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time
......@@ -381,8 +384,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge_widget_refresh
@status =
if merge_request.merge_when_build_succeeds
:merge_when_build_succeeds
if merge_request.merge_when_pipeline_succeeds
:merge_when_pipeline_succeeds
else
# Only MRs that can be merged end in this action
# MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
......@@ -444,6 +447,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ci_status
pipeline = @merge_request.head_pipeline
@pipelines = @merge_request.all_pipelines
if pipeline
status = pipeline.status
......@@ -462,7 +466,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha),
status: status,
coverage: coverage,
pipeline: pipeline.try(:id)
pipeline: pipeline.try(:id),
has_ci: @merge_request.has_ci?
}
render json: response
......@@ -672,8 +677,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.ensure_ref_fetched
end
def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? &&
def merge_when_pipeline_succeeds_active?
params[:merge_when_pipeline_succeeds].present? &&
@merge_request.head_pipeline && @merge_request.head_pipeline.active?
end
......
......@@ -148,17 +148,10 @@ class Projects::NotesController < Projects::ApplicationController
def note_json(note)
attrs = {
award: false,
id: note.id
}
if note.is_a?(AwardEmoji)
attrs.merge!(
valid: note.valid?,
award: true,
name: note.name
)
elsif note.persisted?
if note.persisted?
Banzai::NoteRenderer.render([note], @project, current_user)
attrs.merge!(
......@@ -198,7 +191,7 @@ class Projects::NotesController < Projects::ApplicationController
)
end
attrs[:commands_changes] = note.commands_changes unless attrs[:award]
attrs[:commands_changes] = note.commands_changes
attrs
end
......
class Projects::PipelinesController < Projects::ApplicationController
before_action :pipeline, except: [:index, :new, :create]
before_action :pipeline, except: [:index, :new, :create, :charts]
before_action :commit, only: [:show, :builds]
before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :builds_enabled, only: :charts
def index
@scope = params[:scope]
......@@ -92,6 +93,14 @@ class Projects::PipelinesController < Projects::ApplicationController
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
end
def charts
@charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(project)
@charts[:month] = Ci::Charts::MonthChart.new(project)
@charts[:year] = Ci::Charts::YearChart.new(project)
@charts[:build_times] = Ci::Charts::BuildTime.new(project)
end
private
def create_params
......
......@@ -314,7 +314,7 @@ class ProjectsController < Projects::ApplicationController
:name,
:namespace_id,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_build_succeeds,
:only_allow_merge_if_pipeline_succeeds,
:path,
:public_builds,
:request_access_enabled,
......
......@@ -8,7 +8,9 @@
# `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController
skip_before_action :authenticate_user!, only: [:index]
before_action :redirect_to_custom_dashboard, only: [:index]
before_action :redirect_unlogged_user, if: -> { current_user.nil? }
before_action :redirect_logged_user, if: -> { current_user.present? }
def index
super
......@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController
private
def redirect_to_custom_dashboard
return redirect_to new_user_session_path unless current_user
def redirect_unlogged_user
if redirect_to_home_page_url?
redirect_to(current_application_settings.home_page_url)
else
redirect_to(new_user_session_path)
end
end
def redirect_logged_user
case current_user.dashboard
when 'stars'
flash.keep
redirect_to starred_dashboard_projects_path
redirect_to(starred_dashboard_projects_path)
when 'project_activity'
redirect_to activity_dashboard_path
redirect_to(activity_dashboard_path)
when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred')
redirect_to(activity_dashboard_path(filter: 'starred'))
when 'groups'
redirect_to dashboard_groups_path
redirect_to(dashboard_groups_path)
when 'todos'
redirect_to dashboard_todos_path
else
return
redirect_to(dashboard_todos_path)
end
end
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
return false unless current_application_settings.home_page_url.present?
home_page_url = current_application_settings.home_page_url.chomp('/')
root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
root_urls.exclude?(home_page_url)
end
end
......@@ -15,4 +15,11 @@ module BuildsHelper
log_state: @build.trace_with_state[:state].to_s
}
end
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
description: namespace_project_build_url(@project.namespace, @project, @build)
}
end
end
......@@ -19,7 +19,7 @@ module ButtonHelper
title = data[:title] || 'Copy to clipboard'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard'),
icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}",
data: data,
type: :button,
......
......@@ -9,12 +9,20 @@ module ExploreHelper
}
options = exist_opts.merge(options)
path = request.path
path << "?#{options.to_param}"
path
request_path_with_options(options)
end
def filter_groups_path(options = {})
request_path_with_options(options)
end
def explore_controller?
controller.class.name.split("::").first == "Explore"
end
private
def request_path_with_options(options = {})
request.path + "?#{options.to_param}"
end
end
module IssuablesHelper
def sidebar_gutter_toggle_icon
sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
end
def sidebar_gutter_collapsed_class
......
......@@ -146,7 +146,7 @@ module MergeRequestsHelper
def merge_params(merge_request)
{
merge_when_build_succeeds: true,
merge_when_pipeline_succeeds: true,
should_remove_source_branch: true,
sha: merge_request.diff_head_sha
}.merge(merge_params_ee(merge_request))
......
......@@ -97,7 +97,7 @@ module MilestonesHelper
def milestone_date_range(milestone)
if milestone.start_date && milestone.due_date
"#{milestone.start_date.to_s(:medium)} - #{milestone.due_date.to_s(:medium)}"
"#{milestone.start_date.to_s(:medium)}#{milestone.due_date.to_s(:medium)}"
elsif milestone.due_date
if milestone.due_date.past?
"expired on #{milestone.due_date.to_s(:medium)}"
......
module RssHelper
def rss_url_options
{ format: :atom, private_token: current_user.try(:private_token) }
end
end
......@@ -179,6 +179,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'],
gravatar_enabled: Settings.gravatar['enabled'],
......@@ -277,6 +278,22 @@ class ApplicationSetting < ActiveRecord::Base
self.repository_storages = [value]
end
def default_project_visibility=(level)
super(Gitlab::VisibilityLevel.level_value(level))
end
def default_snippet_visibility=(level)
super(Gitlab::VisibilityLevel.level_value(level))
end
def default_group_visibility=(level)
super(Gitlab::VisibilityLevel.level_value(level))
end
def restricted_visibility_levels=(levels)
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
......
......@@ -55,15 +55,6 @@ module Ci
pending.unstarted.order('created_at ASC').first
end
def create_from(build)
new_build = build.dup
new_build.status = 'pending'
new_build.runner_id = nil
new_build.trigger_request_id = nil
new_build.token = nil
new_build.save
end
def retry(build, current_user)
Ci::RetryBuildService
.new(build.project, current_user)
......
......@@ -93,7 +93,7 @@ class Group < Namespace
end
def visibility_level_field
visibility_level
:visibility_level
end
def visibility_level_allowed_by_projects
......
......@@ -91,17 +91,13 @@ class MergeRequest < ActiveRecord::Base
around_transition do |merge_request, transition, block|
Gitlab::Timeless.timeless(merge_request, &block)
end
after_transition unchecked: :cannot_be_merged do |merge_request, transition|
TodoService.new.merge_request_became_unmergeable(merge_request)
end
end
validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?, unless: :importing?
validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing?
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork?
......@@ -440,7 +436,7 @@ class MergeRequest < ActiveRecord::Base
true
end
def can_cancel_merge_when_build_succeeds?(current_user)
def can_cancel_merge_when_pipeline_succeeds?(current_user)
can_be_merged_by?(current_user) || self.author == current_user
end
......@@ -648,10 +644,10 @@ class MergeRequest < ActiveRecord::Base
message.join("\n\n")
end
def reset_merge_when_build_succeeds
return unless merge_when_build_succeeds?
def reset_merge_when_pipeline_succeeds
return unless merge_when_pipeline_succeeds?
self.merge_when_build_succeeds = false
self.merge_when_pipeline_succeeds = false
self.merge_user = nil
if merge_params
merge_params.delete('should_remove_source_branch')
......@@ -688,7 +684,10 @@ class MergeRequest < ActiveRecord::Base
end
def has_ci?
source_project.try(:ci_service) && commits.any?
has_ci_integration = source_project.try(:ci_service)
uses_gitlab_ci = all_pipelines.any?
(has_ci_integration || uses_gitlab_ci) && commits.any?
end
def branch_missing?
......@@ -710,7 +709,7 @@ class MergeRequest < ActiveRecord::Base
end
def mergeable_ci_state?
return true unless project.only_allow_merge_if_build_succeeds?
return true unless project.only_allow_merge_if_pipeline_succeeds?
!head_pipeline || head_pipeline.success? || head_pipeline.skipped?
end
......
......@@ -231,10 +231,6 @@ class Note < ActiveRecord::Base
note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
end
def award_emoji_name
note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
end
def to_ability_name
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
end
......
......@@ -19,7 +19,7 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper
class BoardLimitExceeded < StandardError; end
BoardLimitExceeded = Class.new(StandardError)
NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
......@@ -113,6 +113,7 @@ class Project < ActiveRecord::Base
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy
has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
has_one :mock_ci_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
......@@ -334,7 +335,7 @@ class Project < ActiveRecord::Base
end
def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
where(visibility_level: Gitlab::VisibilityLevel.string_options[level])
end
def search_by_title(query)
......@@ -1003,7 +1004,7 @@ class Project < ActiveRecord::Base
end
def visibility_level_field
visibility_level
:visibility_level
end
def archive!
......
......@@ -33,8 +33,15 @@ class ProjectGroupLink < ActiveRecord::Base
private
def different_group
if self.group && self.project && self.project.group == self.group
errors.add(:base, "Project cannot be shared with the project it is in.")
return unless self.group && self.project
project_group = self.project.group
return unless project_group
group_ids = project_group.ancestors.map(&:id).push(project_group.id)
if group_ids.include?(self.group.id)
errors.add(:base, "Project cannot be shared with the group it is in or one of its ancestors.")
end
end
......
......@@ -94,7 +94,12 @@ class KubernetesService < DeploymentService
{ key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true }
]
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true } if ca_pem.present?
if ca_pem.present?
variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
end
variables
end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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