Commit 5bf2ab73 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into orderable-issues

parents 32538def df63d9db
...@@ -300,7 +300,7 @@ bundler:audit: ...@@ -300,7 +300,7 @@ bundler:audit:
- master@gitlab/gitlabhq - master@gitlab/gitlabhq
- master@gitlab/gitlab-ee - master@gitlab/gitlab-ee
script: script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941 CVE-2016-6316 CVE-2016-6317" - "bundle exec bundle-audit check --update"
migration paths: migration paths:
stage: test stage: test
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. 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) ## 8.17.1 (2017-02-28)
- Replace setInterval with setTimeout to prevent highly frequent requests. !9271 (Takuya Noguchi) - Replace setInterval with setTimeout to prevent highly frequent requests. !9271 (Takuya Noguchi)
......
## 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 --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contributor license agreement](#contributor-license-agreement)
- [Contribute to GitLab](#contribute-to-gitlab) - [Contribute to GitLab](#contribute-to-gitlab)
- [Contributor license agreement](#contributor-license-agreement) - [Security vulnerability disclosure](#security-vulnerability-disclosure)
- [Security vulnerability disclosure](#security-vulnerability-disclosure) - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests) - [Helping others](#helping-others)
- [Helping others](#helping-others) - [I want to contribute!](#i-want-to-contribute)
- [I want to contribute!](#i-want-to-contribute) - [Implement design & UI elements](#implement-design-ui-elements)
- [Implement design & UI elements](#implement-design-ui-elements) - [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- [Issue tracker](#issue-tracker) - [Retrospective](#retrospective)
- [Feature proposals](#feature-proposals) - [Kickoff](#kickoff)
- [Issue tracker guidelines](#issue-tracker-guidelines) - [Issue tracker](#issue-tracker)
- [Issue weight](#issue-weight) - [Feature proposals](#feature-proposals)
- [Regression issues](#regression-issues) - [Issue tracker guidelines](#issue-tracker-guidelines)
- [Technical debt](#technical-debt) - [Issue weight](#issue-weight)
- [Stewardship](#stewardship) - [Regression issues](#regression-issues)
- [Merge requests](#merge-requests) - [Technical debt](#technical-debt)
- [Merge request guidelines](#merge-request-guidelines) - [Stewardship](#stewardship)
- [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Merge requests](#merge-requests)
- [Changes for Stable Releases](#changes-for-stable-releases) - [Merge request guidelines](#merge-request-guidelines)
- [Definition of done](#definition-of-done) - [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Style guides](#style-guides) - [Changes for Stable Releases](#changes-for-stable-releases)
- [Code of conduct](#code-of-conduct) - [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 --> <!-- 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 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. 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). ...@@ -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/) - [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 ## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to Please report suspected security vulnerabilities in private to
......
...@@ -236,7 +236,6 @@ gem 'gemojione', '~> 3.0' ...@@ -236,7 +236,6 @@ gem 'gemojione', '~> 3.0'
gem 'gon', '~> 6.1.0' gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0' gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'request_store', '~> 1.3' gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
...@@ -329,8 +328,6 @@ group :test do ...@@ -329,8 +328,6 @@ group :test do
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
end end
gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.6.2' gem 'octokit', '~> 4.6.2'
gem 'mail_room', '~> 0.9.1' gem 'mail_room', '~> 0.9.1'
...@@ -352,3 +349,6 @@ gem 'health_check', '~> 2.2.0' ...@@ -352,3 +349,6 @@ gem 'health_check', '~> 2.2.0'
# System information # System information
gem 'vmstat', '~> 2.3.0' gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6' gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client
gem 'gitaly', '~> 0.2.1'
...@@ -245,6 +245,9 @@ GEM ...@@ -245,6 +245,9 @@ GEM
json json
get_process_mem (0.2.0) get_process_mem (0.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.2.1)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0) escape_utils (~> 1.1.0)
...@@ -296,6 +299,7 @@ GEM ...@@ -296,6 +299,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
retriable (~> 1.4) retriable (~> 1.4)
signet (~> 0.6) signet (~> 0.6)
google-protobuf (3.2.0)
googleauth (0.5.1) googleauth (0.5.1)
faraday (~> 0.9) faraday (~> 0.9)
jwt (~> 1.4) jwt (~> 1.4)
...@@ -317,6 +321,9 @@ GEM ...@@ -317,6 +321,9 @@ GEM
grape-entity (0.6.0) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grpc (1.1.2)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7) haml (4.0.7)
tilt tilt
haml_lint (0.21.0) haml_lint (0.21.0)
...@@ -367,8 +374,6 @@ GEM ...@@ -367,8 +374,6 @@ GEM
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.6) json (1.8.6)
json-schema (2.6.2) json-schema (2.6.2)
addressable (~> 2.3.8) addressable (~> 2.3.8)
...@@ -427,7 +432,6 @@ GEM ...@@ -427,7 +432,6 @@ GEM
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
netrc (0.11.0) netrc (0.11.0)
newrelic_rpm (3.16.0.318)
nokogiri (1.6.8.1) nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0) mini_portile2 (~> 2.1.0)
numerizer (0.1.1) numerizer (0.1.1)
...@@ -660,7 +664,7 @@ GEM ...@@ -660,7 +664,7 @@ GEM
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
rubypants (0.2.0) rubypants (0.2.0)
rubyzip (1.2.0) rubyzip (1.2.1)
rufus-scheduler (3.1.10) rufus-scheduler (3.1.10)
rugged (0.24.0) rugged (0.24.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
...@@ -878,6 +882,7 @@ DEPENDENCIES ...@@ -878,6 +882,7 @@ DEPENDENCIES
fuubar (~> 2.0.0) fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0) gemojione (~> 3.0)
gitaly (~> 0.2.1)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1) gitlab-markup (~> 1.5.1)
...@@ -899,7 +904,6 @@ DEPENDENCIES ...@@ -899,7 +904,6 @@ DEPENDENCIES
jira-ruby (~> 1.1.2) jira-ruby (~> 1.1.2)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0) jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2) json-schema (~> 2.6.2)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 0.17.0) kaminari (~> 0.17.0)
...@@ -915,7 +919,6 @@ DEPENDENCIES ...@@ -915,7 +919,6 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.16)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.6.2) octokit (~> 4.6.2)
...@@ -1011,4 +1014,4 @@ DEPENDENCIES ...@@ -1011,4 +1014,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.14.4 1.14.5
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
window.$ = window.jQuery = require('jquery'); window.$ = window.jQuery = require('jquery');
require('jquery-ujs'); require('jquery-ujs');
require('vendor/jquery.endless-scroll'); require('vendor/jquery.endless-scroll');
require('vendor/jquery.highlight');
require('vendor/jquery.waitforimages'); require('vendor/jquery.waitforimages');
require('vendor/jquery.caret'); require('vendor/jquery.caret');
require('vendor/jquery.atwho'); require('vendor/jquery.atwho');
...@@ -18,15 +17,11 @@ window.Cookies = require('js-cookie'); ...@@ -18,15 +17,11 @@ window.Cookies = require('js-cookie');
require('./autosave'); require('./autosave');
require('bootstrap/js/affix'); require('bootstrap/js/affix');
require('bootstrap/js/alert'); require('bootstrap/js/alert');
require('bootstrap/js/button');
require('bootstrap/js/collapse');
require('bootstrap/js/dropdown'); require('bootstrap/js/dropdown');
require('bootstrap/js/modal'); require('bootstrap/js/modal');
require('bootstrap/js/scrollspy');
require('bootstrap/js/tab'); require('bootstrap/js/tab');
require('bootstrap/js/transition'); require('bootstrap/js/transition');
require('bootstrap/js/tooltip'); require('bootstrap/js/tooltip');
require('bootstrap/js/popover');
require('select2/select2.js'); require('select2/select2.js');
window.Pikaday = require('pikaday'); window.Pikaday = require('pikaday');
window._ = require('underscore'); window._ = require('underscore');
...@@ -236,6 +231,10 @@ require('es6-promise').polyfill(); ...@@ -236,6 +231,10 @@ require('es6-promise').polyfill();
var bootstrapBreakpoint = bp.getBreakpointSize(); var bootstrapBreakpoint = bp.getBreakpointSize();
var fitSidebarForSize; var fitSidebarForSize;
$(document).on('scroll', function() {
$('.has-tooltip').tooltip('hide');
});
// Set the default path for all cookies to GitLab's root directory // Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/'; Cookies.defaults.path = gon.relative_url_root || '/';
......
...@@ -3,7 +3,7 @@ require('./issue_card_inner'); ...@@ -3,7 +3,7 @@ require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
module.exports = { export default {
name: 'BoardsIssueCard', name: 'BoardsIssueCard',
template: ` template: `
<li class="card" <li class="card"
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
const boardCard = require('./board_card'); import boardNewIssue from './board_new_issue';
require('./board_new_issue'); import boardCard from './board_card';
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -15,7 +15,7 @@ require('./board_new_issue'); ...@@ -15,7 +15,7 @@ require('./board_new_issue');
template: '#js-board-list-template', template: '#js-board-list-template',
components: { components: {
boardCard, boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue boardNewIssue,
}, },
props: { props: {
disabled: Boolean, disabled: Boolean,
...@@ -76,6 +76,12 @@ require('./board_new_issue'); ...@@ -76,6 +76,12 @@ require('./board_new_issue');
}); });
} }
}, },
toggleForm() {
this.showIssueForm = !this.showIssueForm;
},
},
created() {
gl.IssueBoardsApp.$on(`hide-issue-form-${this.list.id}`, this.toggleForm);
}, },
mounted () { mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
...@@ -113,6 +119,9 @@ require('./board_new_issue'); ...@@ -113,6 +119,9 @@ require('./board_new_issue');
this.loadNextPage(); 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'); ...@@ -39,15 +39,10 @@ const PipelineStore = require('./pipelines_store');
*/ */
data() { data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const svgsData = document.querySelector('.pipeline-svgs').dataset;
const store = new PipelineStore(); const store = new PipelineStore();
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
return { return {
endpoint: pipelinesTableData.endpoint, endpoint: pipelinesTableData.endpoint,
svgs: svgsObject,
store, store,
state: store.state, state: store.state,
isLoading: false, isLoading: false,
...@@ -69,7 +64,9 @@ const PipelineStore = require('./pipelines_store'); ...@@ -69,7 +64,9 @@ const PipelineStore = require('./pipelines_store');
return pipelinesService.all() return pipelinesService.all()
.then(response => response.json()) .then(response => response.json())
.then((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; this.isLoading = false;
}) })
.catch(() => { .catch(() => {
...@@ -99,10 +96,7 @@ const PipelineStore = require('./pipelines_store'); ...@@ -99,10 +96,7 @@ const PipelineStore = require('./pipelines_store');
<div class="table-holder pipelines" <div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0"> v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component <pipelines-table-component :pipelines="state.pipelines"/>
:pipelines="state.pipelines"
:svgs="svgs">
</pipelines-table-component>
</div> </div>
</div> </div>
`, `,
......
...@@ -25,6 +25,9 @@ require('./lib/utils/common_utils'); ...@@ -25,6 +25,9 @@ require('./lib/utils/common_utils');
}, },
}, },
ReferenceFilter: { ReferenceFilter: {
'.tooltip'(el, text) {
return '';
},
'a.gfm:not([data-link=true])'(el, text) { 'a.gfm:not([data-link=true])'(el, text) {
return el.dataset.original || text; return el.dataset.original || text;
}, },
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */ import Vue from 'vue';
import iconCommit from '../svg/icon_commit.svg';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -9,6 +10,11 @@ ...@@ -9,6 +10,11 @@
items: Array, items: Array,
stage: Object, stage: Object,
}, },
data() {
return { iconCommit };
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -31,7 +37,7 @@ ...@@ -31,7 +37,7 @@
</h5> </h5>
<span> <span>
First 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> <a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
pushed by pushed by
<a :href="commit.author.webUrl" class="commit-author-link"> <a :href="commit.author.webUrl" class="commit-author-link">
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */ import Vue from 'vue';
import iconBranch from '../svg/icon_branch.svg';
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -9,6 +10,9 @@ ...@@ -9,6 +10,9 @@
items: Array, items: Array,
stage: Object, stage: Object,
}, },
data() {
return { iconBranch };
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -22,7 +26,7 @@ ...@@ -22,7 +26,7 @@
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i> <i class="fa fa-code-fork"></i>
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a> <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> <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
</h5> </h5>
<span> <span>
......
/* eslint-disable no-param-reassign */ /* 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) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -9,6 +11,9 @@ ...@@ -9,6 +11,9 @@
items: Array, items: Array,
stage: Object, stage: Object,
}, },
data() {
return { iconBuildStatus, iconBranch };
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -18,13 +23,13 @@ ...@@ -18,13 +23,13 @@
<li v-for="build in items" class="stage-event-item item-build-component"> <li v-for="build in items" class="stage-event-item item-build-component">
<div class="item-details"> <div class="item-details">
<h5 class="item-title"> <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> <a :href="build.url" class="item-build-name">{{ build.name }}</a>
&middot; &middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i> <i class="fa fa-code-fork"></i>
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a> <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> <a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
</h5> </h5>
<span> <span>
......
...@@ -4,9 +4,6 @@ ...@@ -4,9 +4,6 @@
window.Vue = require('vue'); window.Vue = require('vue');
window.Cookies = require('js-cookie'); 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_code_component');
require('./components/stage_issue_component'); require('./components/stage_issue_component');
require('./components/stage_plan_component'); require('./components/stage_plan_component');
......
...@@ -75,8 +75,11 @@ const DEFAULT_EVENT_OBJECTS = require('./default_event_objects'); ...@@ -75,8 +75,11 @@ const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item); const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
eventItem.totalTime = eventItem.total_time; 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.created_at) eventItem.createdAt = eventItem.created_at;
if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha; 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'); ...@@ -25,6 +25,10 @@ require('./lib/utils/url_utility');
isBound = true; isBound = true;
} }
if (gl.utils.getLocationHash()) {
this.highlightSelectedLine();
}
this.openAnchoredDiff(); this.openAnchoredDiff();
} }
...@@ -78,7 +82,7 @@ require('./lib/utils/url_utility'); ...@@ -78,7 +82,7 @@ require('./lib/utils/url_utility');
if (nothingHereBlock.length) { if (nothingHereBlock.length) {
const clickTarget = $('.js-file-title, .click-to-expand', diffFile); const clickTarget = $('.js-file-title, .click-to-expand', diffFile);
diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => { diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
this.highlighSelectedLine(); this.highlightSelectedLine();
if (cb) cb(); if (cb) cb();
}); });
} else if (cb) { } else if (cb) {
...@@ -94,7 +98,7 @@ require('./lib/utils/url_utility'); ...@@ -94,7 +98,7 @@ require('./lib/utils/url_utility');
} else { } else {
window.location.hash = hash; window.location.hash = hash;
} }
this.highlighSelectedLine(); this.highlightSelectedLine();
} }
diffViewType() { diffViewType() {
...@@ -108,7 +112,7 @@ require('./lib/utils/url_utility'); ...@@ -108,7 +112,7 @@ require('./lib/utils/url_utility');
return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10)); return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10));
} }
highlighSelectedLine() { highlightSelectedLine() {
const hash = gl.utils.getLocationHash(); const hash = gl.utils.getLocationHash();
const $diffFiles = $('.diff-file'); const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll'); $diffFiles.find('.hll').removeClass('hll');
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
/* global Labels */ /* global Labels */
/* global Shortcuts */ /* global Shortcuts */
import GroupsList from './groups_list';
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout'); const UserCallout = require('./user_callout');
...@@ -96,6 +98,10 @@ const UserCallout = require('./user_callout'); ...@@ -96,6 +98,10 @@ const UserCallout = require('./user_callout');
case 'dashboard:todos:index': case 'dashboard:todos:index':
new gl.Todos(); new gl.Todos();
break; break;
case 'dashboard:groups:index':
case 'explore:groups:index':
new GroupsList();
break;
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
case 'projects:milestones:update': case 'projects:milestones:update':
......
...@@ -35,9 +35,6 @@ module.exports = Vue.component('environment-component', { ...@@ -35,9 +35,6 @@ module.exports = Vue.component('environment-component', {
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsData.newEnvironmentPath, newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath, helpPagePath: environmentsData.helpPagePath,
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties, // Pagination Properties,
paginationInformation: {}, paginationInformation: {},
...@@ -78,7 +75,7 @@ module.exports = Vue.component('environment-component', { ...@@ -78,7 +75,7 @@ module.exports = Vue.component('environment-component', {
this.isLoading = true; this.isLoading = true;
return service.all() return service.get()
.then(resp => ({ .then(resp => ({
headers: resp.headers, headers: resp.headers,
body: resp.json(), body: resp.json(),
...@@ -176,11 +173,7 @@ module.exports = Vue.component('environment-component', { ...@@ -176,11 +173,7 @@ module.exports = Vue.component('environment-component', {
<environment-table <environment-table
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"/>
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg">
</environment-table>
</div> </div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
const Vue = require('vue'); const Vue = require('vue');
const playIconSvg = require('icons/_icon_play.svg');
module.exports = Vue.component('actions-component', { module.exports = Vue.component('actions-component', {
props: { props: {
...@@ -7,11 +8,10 @@ module.exports = Vue.component('actions-component', { ...@@ -7,11 +8,10 @@ module.exports = Vue.component('actions-component', {
required: false, required: false,
default: () => [], default: () => [],
}, },
},
playIconSvg: { data() {
type: String, return { playIconSvg };
required: false,
},
}, },
template: ` template: `
...@@ -28,9 +28,7 @@ module.exports = Vue.component('actions-component', { ...@@ -28,9 +28,7 @@ module.exports = Vue.component('actions-component', {
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
class="js-manual-action-link"> class="js-manual-action-link">
${playIconSvg}
<span class="js-action-play-icon-container" v-html="playIconSvg"></span>
<span> <span>
{{action.name}} {{action.name}}
</span> </span>
......
...@@ -46,21 +46,6 @@ module.exports = Vue.component('environment-item', { ...@@ -46,21 +46,6 @@ module.exports = Vue.component('environment-item', {
required: false, required: false,
default: false, default: false,
}, },
commitIconSvg: {
type: String,
required: false,
},
playIconSvg: {
type: String,
required: false,
},
terminalIconSvg: {
type: String,
required: false,
},
}, },
computed: { computed: {
...@@ -487,9 +472,7 @@ module.exports = Vue.component('environment-item', { ...@@ -487,9 +472,7 @@ module.exports = Vue.component('environment-item', {
:commit-url="commitUrl" :commit-url="commitUrl"
:short-sha="commitShortSha" :short-sha="commitShortSha"
:title="commitTitle" :title="commitTitle"
:author="commitAuthor" :author="commitAuthor"/>
:commit-icon-svg="commitIconSvg">
</commit-component>
</div> </div>
<p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title"> <p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title">
No deployments yet No deployments yet
...@@ -506,27 +489,20 @@ module.exports = Vue.component('environment-item', { ...@@ -506,27 +489,20 @@ module.exports = Vue.component('environment-item', {
<td class="environments-actions"> <td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group"> <div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment" <actions-component v-if="hasManualActions && canCreateDeployment"
:play-icon-svg="playIconSvg" :actions="manualActions"/>
:actions="manualActions">
</actions-component>
<external-url-component v-if="externalURL && canReadEnvironment" <external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"> :external-url="externalURL"/>
</external-url-component>
<stop-component v-if="hasStopAction && canCreateDeployment" <stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"> :stop-url="model.stop_path"/>
</stop-component>
<terminal-button-component v-if="model && model.terminal_path" <terminal-button-component v-if="model && model.terminal_path"
:terminal-icon-svg="terminalIconSvg" :terminal-path="model.terminal_path"/>
:terminal-path="model.terminal_path">
</terminal-button-component>
<rollback-component v-if="canRetry && canCreateDeployment" <rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment" :is-last-deployment="isLastDeployment"
:retry-url="retryUrl"> :retry-url="retryUrl"/>
</rollback-component>
</div> </div>
</td> </td>
</tr> </tr>
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
* Used in environments table. * Used in environments table.
*/ */
const Vue = require('vue'); const Vue = require('vue');
const terminalIconSvg = require('icons/_icon_terminal.svg');
module.exports = Vue.component('terminal-button-component', { module.exports = Vue.component('terminal-button-component', {
props: { props: {
...@@ -10,16 +11,16 @@ module.exports = Vue.component('terminal-button-component', { ...@@ -10,16 +11,16 @@ module.exports = Vue.component('terminal-button-component', {
type: String, type: String,
default: '', default: '',
}, },
terminalIconSvg: { },
type: String,
default: '', data() {
}, return { terminalIconSvg };
}, },
template: ` template: `
<a class="btn terminal-button" <a class="btn terminal-button"
:href="terminalPath"> :href="terminalPath">
<span class="js-terminal-icon-container" v-html="terminalIconSvg"></span> ${terminalIconSvg}
</a> </a>
`, `,
}); });
...@@ -28,21 +28,6 @@ module.exports = Vue.component('environment-table-component', { ...@@ -28,21 +28,6 @@ module.exports = Vue.component('environment-table-component', {
required: false, required: false,
default: false, default: false,
}, },
commitIconSvg: {
type: String,
required: false,
},
playIconSvg: {
type: String,
required: false,
},
terminalIconSvg: {
type: String,
required: false,
},
}, },
template: ` template: `
...@@ -63,10 +48,7 @@ module.exports = Vue.component('environment-table-component', { ...@@ -63,10 +48,7 @@ module.exports = Vue.component('environment-table-component', {
<tr is="environment-item" <tr is="environment-item"
:model="model" :model="model"
:can-create-deployment="canCreateDeployment" :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment" :can-read-environment="canReadEnvironment"></tr>
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"></tr>
</template> </template>
</tbody> </tbody>
</table> </table>
......
...@@ -92,7 +92,7 @@ module.exports = Vue.component('environment-folder-view', { ...@@ -92,7 +92,7 @@ module.exports = Vue.component('environment-folder-view', {
this.isLoading = true; this.isLoading = true;
return service.all() return service.get()
.then(resp => ({ .then(resp => ({
headers: resp.headers, headers: resp.headers,
body: resp.json(), body: resp.json(),
......
...@@ -5,7 +5,7 @@ class EnvironmentsService { ...@@ -5,7 +5,7 @@ class EnvironmentsService {
this.environments = Vue.resource(endpoint); this.environments = Vue.resource(endpoint);
} }
all() { get() {
return this.environments.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 */ /* global Vue */
import stopwatchSvg from 'icons/_icon_stopwatch.svg';
require('../../../lib/utils/pretty_time'); require('../../../lib/utils/pretty_time');
(() => { (() => {
...@@ -11,7 +13,6 @@ require('../../../lib/utils/pretty_time'); ...@@ -11,7 +13,6 @@ require('../../../lib/utils/pretty_time');
'showNoTimeTrackingState', 'showNoTimeTrackingState',
'timeSpentHumanReadable', 'timeSpentHumanReadable',
'timeEstimateHumanReadable', 'timeEstimateHumanReadable',
'stopwatchSvg',
], ],
methods: { methods: {
abbreviateTime(timeStr) { abbreviateTime(timeStr) {
...@@ -20,7 +21,7 @@ require('../../../lib/utils/pretty_time'); ...@@ -20,7 +21,7 @@ require('../../../lib/utils/pretty_time');
}, },
template: ` template: `
<div class='sidebar-collapsed-icon'> <div class='sidebar-collapsed-icon'>
<div v-html='stopwatchSvg'></div> ${stopwatchSvg}
<div class='time-tracking-collapsed-summary'> <div class='time-tracking-collapsed-summary'>
<div class='compare' v-if='showComparisonState'> <div class='compare' v-if='showComparisonState'>
<span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span> <span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
......
...@@ -15,7 +15,6 @@ require('./comparison_pane'); ...@@ -15,7 +15,6 @@ require('./comparison_pane');
'time_spent', 'time_spent',
'human_time_estimate', 'human_time_estimate',
'human_time_spent', 'human_time_spent',
'stopwatchSvg',
'docsUrl', 'docsUrl',
], ],
data() { data() {
...@@ -71,20 +70,19 @@ require('./comparison_pane'); ...@@ -71,20 +70,19 @@ require('./comparison_pane');
:show-spent-only-state='showSpentOnlyState' :show-spent-only-state='showSpentOnlyState'
:show-estimate-only-state='showEstimateOnlyState' :show-estimate-only-state='showEstimateOnlyState'
:time-spent-human-readable='timeSpentHumanReadable' :time-spent-human-readable='timeSpentHumanReadable'
:time-estimate-human-readable='timeEstimateHumanReadable' :time-estimate-human-readable='timeEstimateHumanReadable'>
:stopwatch-svg='stopwatchSvg'>
</time-tracking-collapsed-state> </time-tracking-collapsed-state>
<div class='title hide-collapsed'> <div class='title hide-collapsed'>
Time tracking Time tracking
<div class='help-button pull-right' <div class='help-button pull-right'
v-if='!showHelpState' v-if='!showHelpState'
@click='toggleHelpState(true)'> @click='toggleHelpState(true)'>
<i class='fa fa-question-circle'></i> <i class='fa fa-question-circle' aria-hidden='true'></i>
</div> </div>
<div class='close-help-button pull-right' <div class='close-help-button pull-right'
v-if='showHelpState' v-if='showHelpState'
@click='toggleHelpState(false)'> @click='toggleHelpState(false)'>
<i class='fa fa-close'></i> <i class='fa fa-close' aria-hidden='true'></i>
</div> </div>
</div> </div>
<div class='time-tracking-content hide-collapsed'> <div class='time-tracking-content hide-collapsed'>
......
...@@ -39,8 +39,9 @@ require('../../subbable_resource'); ...@@ -39,8 +39,9 @@ require('../../subbable_resource');
listenForSlashCommands() { listenForSlashCommands() {
$(document).on('ajax:success', '.gfm-form', (e, data) => { $(document).on('ajax:success', '.gfm-form', (e, data) => {
const subscribedCommands = ['spend_time', 'time_estimate']; 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) { if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) {
this.fetchIssuable(); this.fetchIssuable();
} }
......
...@@ -246,17 +246,6 @@ ...@@ -246,17 +246,6 @@
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10), 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. * Updates the search parameter of a URL given the parameter and values provided.
* *
......
...@@ -65,9 +65,10 @@ require('vendor/latinise'); ...@@ -65,9 +65,10 @@ require('vendor/latinise');
} }
}; };
gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) { 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; removedLastNewLine = false;
removedFirstNewLine = false; removedFirstNewLine = false;
currentLineEmpty = false;
// Remove the first newline // Remove the first newline
if (selected.indexOf('\n') === 0) { if (selected.indexOf('\n') === 0) {
...@@ -82,7 +83,17 @@ require('vendor/latinise'); ...@@ -82,7 +83,17 @@ require('vendor/latinise');
} }
selectedSplit = selected.split('\n'); 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 (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
if (blockTag != null) { if (blockTag != null) {
...@@ -142,9 +153,8 @@ require('vendor/latinise'); ...@@ -142,9 +153,8 @@ require('vendor/latinise');
} }
}; };
gl.text.updateText = function(textArea, tag, blockTag, wrap) { gl.text.updateText = function(textArea, tag, blockTag, wrap) {
var $textArea, oldVal, selected, text; var $textArea, selected, text;
$textArea = $(textArea); $textArea = $(textArea);
oldVal = $textArea.val();
textArea = $textArea.get(0); textArea = $textArea.get(0);
text = $textArea.val(); text = $textArea.val();
selected = this.selectedText(text, textArea); selected = this.selectedText(text, textArea);
......
...@@ -129,8 +129,9 @@ require('./smart_interval'); ...@@ -129,8 +129,9 @@ require('./smart_interval');
}; };
MergeRequestWidget.prototype.getMergeStatus = function() { MergeRequestWidget.prototype.getMergeStatus = function() {
return $.get(this.opts.merge_check_url, function(data) { return $.get(this.opts.merge_check_url, (data) => {
var $html = $(data); var $html = $(data);
this.updateMergeButton(this.status, this.hasCi, $html);
$('.mr-widget-body').replaceWith($html.find('.mr-widget-body')); $('.mr-widget-body').replaceWith($html.find('.mr-widget-body'));
$('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer')); $('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer'));
}); });
...@@ -154,9 +155,9 @@ require('./smart_interval'); ...@@ -154,9 +155,9 @@ require('./smart_interval');
return $.getJSON(this.opts.ci_status_url, (function(_this) { return $.getJSON(this.opts.ci_status_url, (function(_this) {
return function(data) { return function(data) {
var message, status, title; var message, status, title;
if (!data.status) { _this.status = data.status;
return; _this.hasCi = data.has_ci;
} _this.updateMergeButton(_this.status, _this.hasCi);
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (data.status !== _this.opts.ci_status || if (data.status !== _this.opts.ci_status ||
data.sha !== _this.opts.ci_sha || data.sha !== _this.opts.ci_sha ||
...@@ -232,36 +233,45 @@ require('./smart_interval'); ...@@ -232,36 +233,45 @@ require('./smart_interval');
return; return;
} }
$('.ci_widget').hide(); $('.ci_widget').hide();
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; $('.ci_widget.ci-' + state).show();
if (indexOf.call(allowed_states, state) !== -1) {
$('.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) { switch (state) {
case "failed": case "failed":
case "canceled": case "canceled":
case "not_found": case "not_found":
this.setMergeButtonClass('btn-danger'); stateClass = 'btn-danger';
break; break;
case "running": case "running":
this.setMergeButtonClass('btn-info'); stateClass = 'btn-info';
break; break;
case "success": case "success":
case "success_with_warnings": case "success_with_warnings":
this.setMergeButtonClass('btn-create'); stateClass = 'btn-create';
} }
} else { } else {
$('.ci_widget.ci-error').show(); $('.ci_widget.ci-error').show();
this.setMergeButtonClass('btn-danger'); stateClass = 'btn-danger';
} }
};
MergeRequestWidget.prototype.showCICoverage = function(coverage) { this.setMergeButtonClass(stateClass, $html);
var text;
text = 'Coverage ' + coverage + '%';
return $('.ci_widget:visible .ci-coverage').text(text);
}; };
MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { MergeRequestWidget.prototype.setMergeButtonClass = function(css_class, $html = $('.mr-state-widget')) {
return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class); return $html.find('.js-merge-button').removeClass('btn-danger btn-info btn-create').addClass(css_class);
}; };
MergeRequestWidget.prototype.updatePipelineUrls = function(id) { MergeRequestWidget.prototype.updatePipelineUrls = function(id) {
......
...@@ -15,15 +15,15 @@ ...@@ -15,15 +15,15 @@
}); });
$(document) $(document)
.off('click', '.accept_merge_request') .off('click', '.accept-merge-request')
.on('click', '.accept_merge_request', () => { .on('click', '.accept-merge-request', () => {
$('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress'); $('.js-merge-button, .js-merge-when-pipeline-succeeds-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
}); });
$(document) $(document)
.off('click', '.merge_when_build_succeeds') .off('click', '.merge-when-pipeline-succeeds')
.on('click', '.merge_when_build_succeeds', () => { .on('click', '.merge-when-pipeline-succeeds', () => {
$('#merge_when_build_succeeds').val('1'); $('#merge_when_pipeline_succeeds').val('1');
}); });
$(document) $(document)
......
...@@ -246,12 +246,21 @@ require('./task_list'); ...@@ -246,12 +246,21 @@ require('./task_list');
}; };
Notes.prototype.handleCreateChanges = function(note) { Notes.prototype.handleCreateChanges = function(note) {
var votesBlock;
if (typeof note === 'undefined') { if (typeof note === 'undefined') {
return; return;
} }
if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) { if (note.commands_changes) {
$.get(mrRefreshWidgetUrl); 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'); ...@@ -262,26 +271,16 @@ require('./task_list');
*/ */
Notes.prototype.renderNote = function(note) { Notes.prototype.renderNote = function(note) {
var $notesList, votesBlock; var $notesList;
if (!note.valid) { if (!note.valid) {
if (note.award) { if (note.errors.commands_only) {
new Flash('You have already awarded this emoji!', 'alert', this.parentTimeline); new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
} this.refresh();
else {
if (note.errors.commands_only) {
new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
this.refresh();
}
} }
return; return;
} }
if (note.award) {
votesBlock = $('.js-awards-block').eq(0); if (this.isNewNote(note)) {
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)) {
this.note_ids.push(note.id); this.note_ids.push(note.id);
$notesList = $('ul.main-notes-list'); $notesList = $('ul.main-notes-list');
$notesList.append(note.html).syntaxHighlight(); $notesList.append(note.html).syntaxHighlight();
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
this.onPickImageClick = this.onPickImageClick.bind(this); this.onPickImageClick = this.onPickImageClick.bind(this);
this.fileInput = $(input); this.fileInput = $(input);
this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; 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.exportWidth = exportWidth;
this.exportHeight = exportHeight; this.exportHeight = exportHeight;
this.cropBoxWidth = cropBoxWidth; this.cropBoxWidth = cropBoxWidth;
......
...@@ -16,9 +16,6 @@ require('./shortcuts'); ...@@ -16,9 +16,6 @@ require('./shortcuts');
Mousetrap.bind('g p', function() { Mousetrap.bind('g p', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
}); });
Mousetrap.bind('g e', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
});
Mousetrap.bind('g f', function() { Mousetrap.bind('g f', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
}); });
...@@ -31,9 +28,6 @@ require('./shortcuts'); ...@@ -31,9 +28,6 @@ require('./shortcuts');
Mousetrap.bind('g n', function() { Mousetrap.bind('g n', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-network'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
}); });
Mousetrap.bind('g g', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs');
});
Mousetrap.bind('g i', function() { Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
}); });
......
...@@ -43,6 +43,8 @@ class UserCallout { ...@@ -43,6 +43,8 @@ class UserCallout {
this.userCalloutBody.append($template); this.userCalloutBody.append($template);
$template.find(closeButton).on('click', e => this.dismissCallout(e)); $template.find(closeButton).on('click', e => this.dismissCallout(e));
$template.find(userCalloutBtn).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 { ...@@ -50,7 +52,7 @@ class UserCallout {
Cookies.set(USER_CALLOUT_COOKIE, 'true'); Cookies.set(USER_CALLOUT_COOKIE, 'true');
const $currentTarget = $(e.currentTarget); const $currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('close-user-callout')) { if ($currentTarget.hasClass('close-user-callout')) {
this.userCalloutBody.empty(); this.userCalloutBody.remove();
} }
} }
} }
......
...@@ -11,15 +11,10 @@ $(() => new Vue({ ...@@ -11,15 +11,10 @@ $(() => new Vue({
data() { data() {
const project = document.querySelector('.pipelines'); 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 { return {
scope: project.dataset.url, scope: project.dataset.url,
store: new gl.PipelineStore(), store: new gl.PipelineStore(),
svgs: svgsObject,
}; };
}, },
components: { components: {
...@@ -27,10 +22,8 @@ $(() => new Vue({ ...@@ -27,10 +22,8 @@ $(() => new Vue({
}, },
template: ` template: `
<vue-pipelines <vue-pipelines
:scope='scope' :scope="scope"
:store='store' :store="store">
:svgs='svgs'
>
</vue-pipelines> </vue-pipelines>
`, `,
})); }));
/* global Vue, Flash, gl */ /* 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) => {
gl.VuePipelineActions = Vue.extend({ gl.VuePipelineActions = Vue.extend({
props: ['pipeline', 'svgs'], props: ['pipeline'],
computed: { computed: {
actions() { actions() {
return this.pipeline.details.manual_actions.length > 0; return this.pipeline.details.manual_actions.length > 0;
...@@ -31,6 +32,11 @@ ...@@ -31,6 +32,11 @@
} }
}, },
}, },
data() {
return { playIconSvg };
},
template: ` template: `
<td class="pipeline-actions"> <td class="pipeline-actions">
<div class="pull-right"> <div class="pull-right">
...@@ -42,7 +48,7 @@ ...@@ -42,7 +48,7 @@
title="Manual job" title="Manual job"
data-placement="top" data-placement="top"
aria-label="Manual job"> 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> <i class="fa fa-caret-down" aria-hidden="true"></i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
...@@ -50,8 +56,8 @@ ...@@ -50,8 +56,8 @@
<a <a
rel="nofollow" rel="nofollow"
data-method="post" data-method="post"
:href="action.path"> :href="action.path" >
<span v-html="svgs.iconPlay" aria-hidden="true"></span> <span v-html="playIconSvg" aria-hidden="true"></span>
<span>{{action.name}}</span> <span>{{action.name}}</span>
</a> </a>
</li> </li>
......
...@@ -27,7 +27,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s ...@@ -27,7 +27,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
pageRequest: false, pageRequest: false,
}; };
}, },
props: ['scope', 'store', 'svgs'], props: ['scope', 'store'],
created() { created() {
const pagenum = gl.utils.getParameterByName('page'); const pagenum = gl.utils.getParameterByName('page');
const scope = gl.utils.getParameterByName('scope'); const scope = gl.utils.getParameterByName('scope');
...@@ -45,18 +45,15 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s ...@@ -45,18 +45,15 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
methods: { 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. * @param {Number} pageNumber desired page to go to.
*
* Pagination component sends "null" when no scope is provided.
*
* @param {Number} pagenum
* @param {String} apiScope = 'all'
*/ */
change(pagenum, apiScope) { change(pageNumber) {
if (!apiScope) apiScope = 'all'; const param = gl.utils.setParamInURL('page', pageNumber);
gl.utils.visitUrl(`?scope=${apiScope}&page=${pagenum}`);
gl.utils.visitUrl(param);
return param;
}, },
}, },
template: ` template: `
...@@ -73,10 +70,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s ...@@ -73,10 +70,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
</div> </div>
<div class="table-holder" v-if='!pageRequest && pipelines.length'> <div class="table-holder" v-if='!pageRequest && pipelines.length'>
<pipelines-table-component <pipelines-table-component :pipelines='pipelines'/>
:pipelines='pipelines'
:svgs='svgs'>
</pipelines-table-component>
</div> </div>
<gl-pagination <gl-pagination
......
/* global Vue, Flash, gl */ /* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */ /* 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) => {
gl.VueStage = Vue.extend({ gl.VueStage = Vue.extend({
data() { 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 { return {
builds: '', builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>', spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
}; };
}, },
props: { props: {
stage: { stage: {
type: Object, type: Object,
required: true, required: true,
}, },
svgs: {
type: Object,
required: true,
},
match: {
type: Function,
required: true,
},
}, },
updated() { updated() {
...@@ -73,11 +88,6 @@ ...@@ -73,11 +88,6 @@
tooltip() { tooltip() {
return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`; 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() { triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
}, },
...@@ -91,8 +101,7 @@ ...@@ -91,8 +101,7 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
type="button" type="button"
:aria-label="stage.title" :aria-label="stage.title">
>
<span v-html="svg" aria-hidden="true"></span> <span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
</button> </button>
...@@ -101,8 +110,7 @@ ...@@ -101,8 +110,7 @@
<div <div
:class="dropdownClass" :class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu" class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner" v-html="buildsOrSpinner">
>
</div> </div>
</ul> </ul>
</div> </div>
......
/* global Vue, gl */ /* global Vue, gl */
/* eslint-disable no-param-reassign */ /* 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) => {
gl.VueStatusScope = Vue.extend({ gl.VueStatusScope = Vue.extend({
props: [ 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: { computed: {
cssClasses() { cssClasses() {
const cssObject = { 'ci-status': true }; const cssObject = { 'ci-status': true };
cssObject[`ci-${this.pipeline.details.status.group}`] = true; cssObject[`ci-${this.pipeline.details.status.group}`] = true;
return cssObject; return cssObject;
}, },
svg() {
return this.svgs[this.match(this.pipeline.details.status.icon)];
},
detailsPath() { detailsPath() {
const { status } = this.pipeline.details; const { status } = this.pipeline.details;
return status.has_details ? status.details_path : false; return status.has_details ? status.details_path : false;
}, },
content() {
return `${this.svg} ${this.pipeline.details.status.text}`;
},
}, },
template: ` template: `
<td class="commit-link"> <td class="commit-link">
<a <a
:class='cssClasses' :class="cssClasses"
:href='detailsPath' :href="detailsPath"
v-html='svg + pipeline.details.status.text' v-html="content">
>
</a> </a>
</td> </td>
`, `,
......
...@@ -4,14 +4,17 @@ ...@@ -4,14 +4,17 @@
window.Vue = require('vue'); window.Vue = require('vue');
require('../lib/utils/datetime_utility'); require('../lib/utils/datetime_utility');
const iconTimerSvg = require('../../../views/shared/icons/_icon_timer.svg');
((gl) => { ((gl) => {
gl.VueTimeAgo = Vue.extend({ gl.VueTimeAgo = Vue.extend({
data() { data() {
return { return {
currentTime: new Date(), currentTime: new Date(),
iconTimerSvg,
}; };
}, },
props: ['pipeline', 'svgs'], props: ['pipeline'],
computed: { computed: {
timeAgo() { timeAgo() {
return gl.utils.getTimeago(); return gl.utils.getTimeago();
...@@ -56,7 +59,7 @@ require('../lib/utils/datetime_utility'); ...@@ -56,7 +59,7 @@ require('../lib/utils/datetime_utility');
template: ` template: `
<td class="pipelines-time-ago"> <td class="pipelines-time-ago">
<p class="duration" v-if='duration'> <p class="duration" v-if='duration'>
<span v-html='svgs.iconTimer'></span> <span v-html="iconTimerSvg"></span>
{{duration}} {{duration}}
</p> </p>
<p class="finished-at" v-if='timeStopped'> <p class="finished-at" v-if='timeStopped'>
......
/* global Vue */ /* global Vue */
window.Vue = require('vue'); window.Vue = require('vue');
const commitIconSvg = require('icons/_icon_commit.svg');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
...@@ -69,11 +70,6 @@ window.Vue = require('vue'); ...@@ -69,11 +70,6 @@ window.Vue = require('vue');
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
commitIconSvg: {
type: String,
required: false,
},
}, },
computed: { computed: {
...@@ -116,6 +112,10 @@ window.Vue = require('vue'); ...@@ -116,6 +112,10 @@ window.Vue = require('vue');
}, },
}, },
data() {
return { commitIconSvg };
},
template: ` template: `
<div class="branch-commit"> <div class="branch-commit">
......
...@@ -21,14 +21,6 @@ require('./pipelines_table_row'); ...@@ -21,14 +21,6 @@ require('./pipelines_table_row');
default: () => ([]), default: () => ([]),
}, },
/**
* TODO: Remove this when we have webpack.
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
}, },
components: { components: {
...@@ -51,8 +43,7 @@ require('./pipelines_table_row'); ...@@ -51,8 +43,7 @@ require('./pipelines_table_row');
<template v-for="model in pipelines" <template v-for="model in pipelines"
v-bind:model="model"> v-bind:model="model">
<tr is="pipelines-table-row-component" <tr is="pipelines-table-row-component"
:pipeline="model" :pipeline="model"></tr>
:svgs="svgs"></tr>
</template> </template>
</tbody> </tbody>
</table> </table>
......
...@@ -25,14 +25,6 @@ require('./commit'); ...@@ -25,14 +25,6 @@ require('./commit');
default: () => ({}), default: () => ({}),
}, },
/**
* TODO: Remove this when we have webpack;
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
}, },
components: { components: {
...@@ -174,30 +166,9 @@ require('./commit'); ...@@ -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: ` template: `
<tr class="commit"> <tr class="commit">
<status-scope <status-scope :pipeline="pipeline"/>
:pipeline="pipeline"
:svgs="svgs"
:match="match">
</status-scope>
<pipeline-url :pipeline="pipeline"></pipeline-url> <pipeline-url :pipeline="pipeline"></pipeline-url>
...@@ -208,26 +179,20 @@ require('./commit'); ...@@ -208,26 +179,20 @@ require('./commit');
:commit-url="commitUrl" :commit-url="commitUrl"
:short-sha="commitShortSha" :short-sha="commitShortSha"
:title="commitTitle" :title="commitTitle"
:author="commitAuthor" :author="commitAuthor"/>
:commit-icon-svg="svgs.commitIconSvg">
</commit-component>
</td> </td>
<td class="stage-cell"> <td class="stage-cell">
<div class="stage-container dropdown js-mini-pipeline-graph" <div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0" v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages"> v-for="stage in pipeline.details.stages">
<dropdown-stage <dropdown-stage :stage="stage"/>
:stage="stage"
:svgs="svgs"
:match="match">
</dropdown-stage>
</div> </div>
</td> </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> </tr>
`, `,
}); });
......
...@@ -19,12 +19,11 @@ window.Vue = require('vue'); ...@@ -19,12 +19,11 @@ window.Vue = require('vue');
/** /**
This function will take the information given by the pagination component This function will take the information given by the pagination component
And make a new Turbolinks call
Here is an example `change` method: Here is an example `change` method:
change(pagenum, apiScope) { change(pagenum) {
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); gl.utils.visitUrl(`?page=${pagenum}`);
}, },
*/ */
...@@ -57,8 +56,6 @@ window.Vue = require('vue'); ...@@ -57,8 +56,6 @@ window.Vue = require('vue');
}, },
methods: { methods: {
changePage(e) { changePage(e) {
const apiScope = gl.utils.getParameterByName('scope');
const text = e.target.innerText; const text = e.target.innerText;
const { totalPages, nextPage, previousPage } = this.pageInfo; const { totalPages, nextPage, previousPage } = this.pageInfo;
...@@ -66,19 +63,19 @@ window.Vue = require('vue'); ...@@ -66,19 +63,19 @@ window.Vue = require('vue');
case SPREAD: case SPREAD:
break; break;
case LAST: case LAST:
this.change(totalPages, apiScope); this.change(totalPages);
break; break;
case NEXT: case NEXT:
this.change(nextPage, apiScope); this.change(nextPage);
break; break;
case PREV: case PREV:
this.change(previousPage, apiScope); this.change(previousPage);
break; break;
case FIRST: case FIRST:
this.change(1, apiScope); this.change(1);
break; break;
default: default:
this.change(+text, apiScope); this.change(+text);
break; break;
} }
}, },
......
.calender-block { .calender-block {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
border-top: 0;
direction: rtl; direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
......
...@@ -107,11 +107,12 @@ ...@@ -107,11 +107,12 @@
&.fa-spinner { &.fa-spinner {
font-size: 16px; font-size: 16px;
margin-top: -8px; margin-top: -3px;
} }
} }
.fa-chevron-down { .fa-chevron-down,
.fa-spinner {
position: absolute; position: absolute;
top: 11px; top: 11px;
right: 8px; right: 8px;
...@@ -192,6 +193,10 @@ ...@@ -192,6 +193,10 @@
&.is-focused { &.is-focused {
background-color: $dropdown-link-hover-bg; background-color: $dropdown-link-hover-bg;
text-decoration: none; text-decoration: none;
.badge {
background-color: darken($row-hover, 5%);
}
} }
&.dropdown-menu-empty-link { &.dropdown-menu-empty-link {
...@@ -228,6 +233,12 @@ ...@@ -228,6 +233,12 @@
padding: 5px 8px; padding: 5px 8px;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
.badge {
position: absolute;
right: 8px;
top: 5px;
}
} }
.dropdown-menu-drop-up { .dropdown-menu-drop-up {
......
...@@ -271,6 +271,7 @@ span.idiff { ...@@ -271,6 +271,7 @@ span.idiff {
font-size: 13px; font-size: 13px;
line-height: 28px; line-height: 28px;
display: inline-block; display: inline-block;
float: none;
} }
} }
} }
...@@ -149,14 +149,14 @@ header { ...@@ -149,14 +149,14 @@ header {
.header-logo { .header-logo {
display: inline-block; display: inline-block;
margin: 0 8px 0 3px; margin: 0 7px 0 2px;
position: relative; position: relative;
top: 7px; top: 10px;
transition-duration: .3s; transition-duration: .3s;
svg, svg,
img { img {
height: 36px; height: 28px;
} }
&:hover { &:hover {
......
...@@ -73,10 +73,6 @@ ...@@ -73,10 +73,6 @@
right: $gutter_collapsed_width; right: $gutter_collapsed_width;
} }
} }
&.with-overlay {
padding-right: $gutter_collapsed_width;
}
} }
.right-sidebar { .right-sidebar {
......
...@@ -21,6 +21,7 @@ $dark-highlight-color: $black; ...@@ -21,6 +21,7 @@ $dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41; $dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41; $dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5; $dark-over-bg: #9f9ab5;
$dark-expanded-bg: #3e3e3e;
$dark-c: #969896; $dark-c: #969896;
$dark-err: #c66; $dark-err: #c66;
$dark-k: #b294bb; $dark-k: #b294bb;
...@@ -155,6 +156,22 @@ $dark-il: #de935f; ...@@ -155,6 +156,22 @@ $dark-il: #de935f;
.line_content.match { .line_content.match {
@include dark-diff-match-line; @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 // highlight line via anchor
......
...@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); ...@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080; $monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792; $monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5; $monokai-over-bg: #9f9ab5;
$monokai-expanded-bg: #3e3e3e;
$monokai-new-bg: rgba(166, 226, 46, 0.1); $monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15); $monokai-new-idiff: rgba(166, 226, 46, 0.15);
...@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e; ...@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e;
.line_content.match { .line_content.match {
@include dark-diff-match-line; @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 // highlight line via anchor
......
...@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71; ...@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554; $solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652; $solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5; $solarized-dark-over-bg: #9f9ab5;
$solarized-dark-expanded-bg: #010d10;
$solarized-dark-c: #586e75; $solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1; $solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1; $solarized-dark-g: #93a1a1;
...@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198; ...@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198;
.line_content.match { .line_content.match {
@include dark-diff-match-line; @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 // highlight line via anchor
......
...@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186; ...@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5; $solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5; $solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc; $solarized-light-over-bg: #ded7fc;
$solarized-light-expanded-border: #d2cdbd;
$solarized-light-expanded-bg: #ece6d4;
$solarized-light-c: #93a1a1; $solarized-light-c: #93a1a1;
$solarized-light-err: #586e75; $solarized-light-err: #586e75;
$solarized-light-g: #586e75; $solarized-light-g: #586e75;
...@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198; ...@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198;
.line_content.match { .line_content.match {
@include matchLine; @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 // highlight line via anchor
......
...@@ -8,6 +8,8 @@ $white-highlight: #fafe3d; ...@@ -8,6 +8,8 @@ $white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7; $white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8; $white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc; $white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998; $white-c: #998;
$white-err: #a61717; $white-err: #a61717;
$white-err-bg: #e3d2d2; $white-err-bg: #e3d2d2;
...@@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5; ...@@ -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 { .line_content {
&.old { &.old {
background-color: $line-removed; background-color: $line-removed;
......
...@@ -133,8 +133,13 @@ ...@@ -133,8 +133,13 @@
width: 35px; width: 35px;
font-weight: normal; font-weight: normal;
&:hover { &[disabled] {
text-decoration: underline; cursor: default;
&:hover,
&:active {
text-decoration: none;
}
} }
} }
} }
......
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.event-item { .event-item {
padding-left: $gl-padding; padding-left: 0;
.event-title { .event-title {
white-space: normal; white-space: normal;
...@@ -169,8 +169,7 @@ ...@@ -169,8 +169,7 @@
.event-body { .event-body {
margin: 0; margin: 0;
border-left: 2px solid $events-body-border; padding-left: 0;
padding-left: 10px;
} }
.event-item-timestamp { .event-item-timestamp {
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
background-color: $gl-success; background-color: $gl-success;
} }
.accept_merge_request { .accept-merge-request {
&.ci-pending, &.ci-pending,
&.ci-running { &.ci-running {
@include btn-blue; @include btn-blue;
...@@ -42,6 +42,12 @@ ...@@ -42,6 +42,12 @@
@include btn-red; @include btn-red;
} }
} }
.dropdown-toggle {
.fa {
color: inherit;
}
}
} }
.accept-control { .accept-control {
......
...@@ -279,7 +279,7 @@ table.u2f-registrations { ...@@ -279,7 +279,7 @@ table.u2f-registrations {
} }
.user-callout { .user-callout {
margin: 24px auto 0; margin: 0 auto;
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $border-color;
...@@ -287,6 +287,7 @@ table.u2f-registrations { ...@@ -287,6 +287,7 @@ table.u2f-registrations {
} }
.landing { .landing {
margin-top: $gl-padding;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
.close { .close {
......
...@@ -178,3 +178,29 @@ ...@@ -178,3 +178,29 @@
margin-left: $btn-side-margin; 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 ...@@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base
end end
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) def log_exception(exception)
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" } application_trace.map!{ |t| " #{t}\n" }
...@@ -130,10 +122,6 @@ class ApplicationController < ActionController::Base ...@@ -130,10 +122,6 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block' headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge' headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff' 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 end
def validate_user_service_ticket! def validate_user_service_ticket!
...@@ -287,19 +275,6 @@ class ApplicationController < ActionController::Base ...@@ -287,19 +275,6 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current session[:skip_tfa] && session[:skip_tfa] > Time.current
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('/')]
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 # U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication. # to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html # 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 ...@@ -4,10 +4,9 @@ module CreatesCommit
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables set_commit_variables
start_branch = @mr_target_branch unless initial_commit?
commit_params = @commit_params.merge( commit_params = @commit_params.merge(
start_project: @mr_target_project, start_project: @mr_target_project,
start_branch: start_branch, start_branch: @mr_target_branch,
target_branch: @mr_source_branch target_branch: @mr_source_branch
) )
...@@ -17,12 +16,16 @@ module CreatesCommit ...@@ -17,12 +16,16 @@ module CreatesCommit
if result[:status] == :success if result[:status] == :success
update_flash_notice(success_notice) update_flash_notice(success_notice)
success_path = final_success_path(success_path)
respond_to do |format| respond_to do |format|
format.html { redirect_to final_success_path(success_path) } format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: final_success_path(success_path) } } format.json { render json: { message: "success", filePath: success_path } }
end end
else else
flash[:alert] = result[:message] flash[:alert] = result[:message]
failure_path = failure_path.call if failure_path.respond_to?(:call)
respond_to do |format| respond_to do |format|
format.html do format.html do
if failure_view if failure_view
...@@ -58,9 +61,13 @@ module CreatesCommit ...@@ -58,9 +61,13 @@ module CreatesCommit
end end
def final_success_path(success_path) 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 end
def new_merge_request_path def new_merge_request_path
...@@ -92,47 +99,26 @@ module CreatesCommit ...@@ -92,47 +99,26 @@ module CreatesCommit
end end
def create_merge_request? 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, # as the target branch in the same project,
# we don't want to create a merge request. # we don't want to create a merge request.
params[:create_merge_request].present? && params[:create_merge_request].present? &&
(different_project? || @ref != @target_branch) (different_project? || @mr_target_branch != @mr_source_branch)
end end
# TODO: We should really clean this up
def set_commit_variables def set_commit_variables
@mr_source_project = if can?(current_user, :push_code, @project)
if can?(current_user, :push_code, @project) @mr_source_project = @project
# Edit file in this project @target_branch ||= @ref
@project else
else @mr_source_project = current_user.fork_of(@project)
# Merge request from fork to this project @target_branch ||= @mr_source_project.repository.next_branch('patch')
current_user.fork_of(@project) end
end
# Merge request to this project # Merge request to this project
@mr_target_project = @project @mr_target_project = @project
@mr_target_branch = @ref || @target_branch @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
def guess_mr_source_branch @mr_source_branch = @target_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
end end
end end
class Dashboard::GroupsController < Dashboard::ApplicationController class Dashboard::GroupsController < Dashboard::ApplicationController
def index 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
end end
class Explore::GroupsController < Explore::ApplicationController class Explore::GroupsController < Explore::ApplicationController
def index def index
@groups = GroupsFinder.new.execute(current_user) @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.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]) @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
end end
...@@ -5,7 +5,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path # 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 :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -24,7 +24,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::BlobController < Projects::ApplicationController
def create def create
create_commit(Files::CreateService, success_notice: "The file has been successfully created.", 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_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end end
...@@ -40,7 +40,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -40,7 +40,7 @@ class Projects::BlobController < Projects::ApplicationController
def update def update
@path = params[:file_path] if params[:file_path].present? @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_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
...@@ -62,7 +62,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -62,7 +62,7 @@ class Projects::BlobController < Projects::ApplicationController
def destroy def destroy
create_commit(Files::DestroyService, success_notice: "The file has been successfully deleted.", 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_view: :show,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end end
......
...@@ -51,23 +51,35 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -51,23 +51,35 @@ class Projects::CommitController < Projects::ApplicationController
def revert def revert
assign_change_commit_vars 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.", 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 end
def cherry_pick def cherry_pick
assign_change_commit_vars 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.", 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 end
private private
def create_new_branch?
params[:create_merge_request].present? || !can?(current_user, :push_code, @project)
end
def successful_change_path def successful_change_path
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch) referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch)
end end
...@@ -78,7 +90,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -78,7 +90,7 @@ class Projects::CommitController < Projects::ApplicationController
def referenced_merge_request_url def referenced_merge_request_url
if merge_request = @commit.merged_merge_request(current_user) 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
end end
...@@ -94,7 +106,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -94,7 +106,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts) @diffs = commit.diffs(opts)
@notes_count = commit.notes.count @notes_count = commit.notes.count
@environment = EnvironmentsFinder.new(@project, current_user, commit: @commit).execute.last @environment = EnvironmentsFinder.new(@project, current_user, commit: @commit).execute.last
end end
...@@ -118,11 +130,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -118,11 +130,7 @@ class Projects::CommitController < Projects::ApplicationController
end end
def assign_change_commit_vars def assign_change_commit_vars
@commit = project.commit(params[:id]) @start_branch = params[:start_branch]
@target_branch = params[:target_branch] @commit_params = { commit: @commit }
@commit_params = {
commit: @commit,
create_merge_request: params[:create_merge_request].present? || different_project?
}
end end
end end
...@@ -17,6 +17,25 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -17,6 +17,25 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def commits 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 = @project.repository.commits(@ref, limit: 2000, skip_merges: true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits) @commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days @commits_per_week_days = @commits_graph.commits_per_week_days
...@@ -24,15 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -24,15 +43,7 @@ class Projects::GraphsController < Projects::ApplicationController
@commits_per_month = @commits_graph.commits_per_month @commits_per_month = @commits_graph.commits_per_month
end end
def ci def get_languages
@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
@languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
total = @languages.map(&:last).sum total = @languages.map(&:last).sum
...@@ -52,8 +63,6 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -52,8 +63,6 @@ class Projects::GraphsController < Projects::ApplicationController
end end
end end
private
def fetch_graph def fetch_graph
@commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true) @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true)
@log = [] @log = []
......
...@@ -10,11 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -10,11 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check, :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 :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_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_commit_vars, only: [:diffs]
before_action :define_diff_comment_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] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
...@@ -245,9 +245,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -245,9 +245,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do format.json do
define_pipelines_vars define_pipelines_vars
render json: PipelineSerializer render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
.represent(@pipelines) .represent(@pipelines)
}
end end
end end
end end
...@@ -322,12 +324,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -322,12 +324,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge_check def merge_check
@merge_request.check_if_can_be_merged @merge_request.check_if_can_be_merged
@pipelines = @merge_request.all_pipelines
render partial: "projects/merge_requests/widget/show.html.haml", layout: false render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end end
def cancel_merge_when_build_succeeds def cancel_merge_when_pipeline_succeeds
unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) unless @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
return access_denied! return access_denied!
end end
...@@ -339,9 +342,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -339,9 +342,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user) 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 # 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 @status = :failed
return return
end end
...@@ -353,7 +356,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -353,7 +356,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil) @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 unless @merge_request.head_pipeline
@status = :failed @status = :failed
return return
...@@ -364,7 +367,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -364,7 +367,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
.new(@project, current_user, merge_params) .new(@project, current_user, merge_params)
.execute(@merge_request) .execute(@merge_request)
@status = :merge_when_build_succeeds @status = :merge_when_pipeline_succeeds
elsif @merge_request.head_pipeline.success? elsif @merge_request.head_pipeline.success?
# This can be triggered when a user clicks the auto merge button while # This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time # the tests finish at about the same time
...@@ -381,8 +384,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -381,8 +384,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge_widget_refresh def merge_widget_refresh
@status = @status =
if merge_request.merge_when_build_succeeds if merge_request.merge_when_pipeline_succeeds
:merge_when_build_succeeds :merge_when_pipeline_succeeds
else else
# Only MRs that can be merged end in this action # 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 # 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 ...@@ -444,6 +447,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ci_status def ci_status
pipeline = @merge_request.head_pipeline pipeline = @merge_request.head_pipeline
@pipelines = @merge_request.all_pipelines
if pipeline if pipeline
status = pipeline.status status = pipeline.status
...@@ -462,7 +466,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -462,7 +466,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha), sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha),
status: status, status: status,
coverage: coverage, coverage: coverage,
pipeline: pipeline.try(:id) pipeline: pipeline.try(:id),
has_ci: @merge_request.has_ci?
} }
render json: response render json: response
...@@ -672,8 +677,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -672,8 +677,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.ensure_ref_fetched @merge_request.ensure_ref_fetched
end end
def merge_when_build_succeeds_active? def merge_when_pipeline_succeeds_active?
params[:merge_when_build_succeeds].present? && params[:merge_when_pipeline_succeeds].present? &&
@merge_request.head_pipeline && @merge_request.head_pipeline.active? @merge_request.head_pipeline && @merge_request.head_pipeline.active?
end end
......
...@@ -148,17 +148,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -148,17 +148,10 @@ class Projects::NotesController < Projects::ApplicationController
def note_json(note) def note_json(note)
attrs = { attrs = {
award: false,
id: note.id id: note.id
} }
if note.is_a?(AwardEmoji) if note.persisted?
attrs.merge!(
valid: note.valid?,
award: true,
name: note.name
)
elsif note.persisted?
Banzai::NoteRenderer.render([note], @project, current_user) Banzai::NoteRenderer.render([note], @project, current_user)
attrs.merge!( attrs.merge!(
...@@ -198,7 +191,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -198,7 +191,7 @@ class Projects::NotesController < Projects::ApplicationController
) )
end end
attrs[:commands_changes] = note.commands_changes unless attrs[:award] attrs[:commands_changes] = note.commands_changes
attrs attrs
end end
......
class Projects::PipelinesController < Projects::ApplicationController 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 :commit, only: [:show, :builds]
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :builds_enabled, only: :charts
def index def index
@scope = params[:scope] @scope = params[:scope]
...@@ -92,6 +93,14 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -92,6 +93,14 @@ class Projects::PipelinesController < Projects::ApplicationController
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
end 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 private
def create_params def create_params
......
...@@ -314,7 +314,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -314,7 +314,7 @@ class ProjectsController < Projects::ApplicationController
:name, :name,
:namespace_id, :namespace_id,
:only_allow_merge_if_all_discussions_are_resolved, :only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_build_succeeds, :only_allow_merge_if_pipeline_succeeds,
:path, :path,
:public_builds, :public_builds,
:request_access_enabled, :request_access_enabled,
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
# `DashboardController#show`, which is the default. # `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController class RootController < Dashboard::ProjectsController
skip_before_action :authenticate_user!, only: [:index] 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 def index
super super
...@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController ...@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController
private private
def redirect_to_custom_dashboard def redirect_unlogged_user
return redirect_to new_user_session_path unless current_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 case current_user.dashboard
when 'stars' when 'stars'
flash.keep flash.keep
redirect_to starred_dashboard_projects_path redirect_to(starred_dashboard_projects_path)
when 'project_activity' when 'project_activity'
redirect_to activity_dashboard_path redirect_to(activity_dashboard_path)
when 'starred_project_activity' when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred') redirect_to(activity_dashboard_path(filter: 'starred'))
when 'groups' when 'groups'
redirect_to dashboard_groups_path redirect_to(dashboard_groups_path)
when 'todos' when 'todos'
redirect_to dashboard_todos_path redirect_to(dashboard_todos_path)
else
return
end end
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 end
...@@ -19,7 +19,7 @@ module ButtonHelper ...@@ -19,7 +19,7 @@ module ButtonHelper
title = data[:title] || 'Copy to clipboard' title = data[:title] || 'Copy to clipboard'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button, content_tag :button,
icon('clipboard'), icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}", class: "btn #{css_class}",
data: data, data: data,
type: :button, type: :button,
......
...@@ -9,12 +9,20 @@ module ExploreHelper ...@@ -9,12 +9,20 @@ module ExploreHelper
} }
options = exist_opts.merge(options) options = exist_opts.merge(options)
path = request.path request_path_with_options(options)
path << "?#{options.to_param}" end
path
def filter_groups_path(options = {})
request_path_with_options(options)
end end
def explore_controller? def explore_controller?
controller.class.name.split("::").first == "Explore" controller.class.name.split("::").first == "Explore"
end end
private
def request_path_with_options(options = {})
request.path + "?#{options.to_param}"
end
end end
module IssuablesHelper module IssuablesHelper
def sidebar_gutter_toggle_icon 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 end
def sidebar_gutter_collapsed_class def sidebar_gutter_collapsed_class
......
...@@ -146,7 +146,7 @@ module MergeRequestsHelper ...@@ -146,7 +146,7 @@ module MergeRequestsHelper
def merge_params(merge_request) def merge_params(merge_request)
{ {
merge_when_build_succeeds: true, merge_when_pipeline_succeeds: true,
should_remove_source_branch: true, should_remove_source_branch: true,
sha: merge_request.diff_head_sha sha: merge_request.diff_head_sha
}.merge(merge_params_ee(merge_request)) }.merge(merge_params_ee(merge_request))
......
...@@ -97,7 +97,7 @@ module MilestonesHelper ...@@ -97,7 +97,7 @@ module MilestonesHelper
def milestone_date_range(milestone) def milestone_date_range(milestone)
if milestone.start_date && milestone.due_date 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 elsif milestone.due_date
if milestone.due_date.past? if milestone.due_date.past?
"expired on #{milestone.due_date.to_s(:medium)}" "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 ...@@ -179,6 +179,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'], default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], 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: [], disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'], domain_whitelist: Settings.gitlab['domain_whitelist'],
gravatar_enabled: Settings.gravatar['enabled'], gravatar_enabled: Settings.gravatar['enabled'],
...@@ -277,6 +278,22 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -277,6 +278,22 @@ class ApplicationSetting < ActiveRecord::Base
self.repository_storages = [value] self.repository_storages = [value]
end 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 # Choose one of the available repository storage options. Currently all have
# equal weighting. # equal weighting.
def pick_repository_storage def pick_repository_storage
......
...@@ -55,15 +55,6 @@ module Ci ...@@ -55,15 +55,6 @@ module Ci
pending.unstarted.order('created_at ASC').first pending.unstarted.order('created_at ASC').first
end 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) def retry(build, current_user)
Ci::RetryBuildService Ci::RetryBuildService
.new(build.project, current_user) .new(build.project, current_user)
......
...@@ -93,7 +93,7 @@ class Group < Namespace ...@@ -93,7 +93,7 @@ class Group < Namespace
end end
def visibility_level_field def visibility_level_field
visibility_level :visibility_level
end end
def visibility_level_allowed_by_projects def visibility_level_allowed_by_projects
......
...@@ -97,7 +97,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -97,7 +97,7 @@ class MergeRequest < ActiveRecord::Base
validates :source_branch, presence: true validates :source_branch, presence: true
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, 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_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork? validate :validate_fork, unless: :closed_without_fork?
...@@ -436,7 +436,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -436,7 +436,7 @@ class MergeRequest < ActiveRecord::Base
true true
end 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 can_be_merged_by?(current_user) || self.author == current_user
end end
...@@ -644,10 +644,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -644,10 +644,10 @@ class MergeRequest < ActiveRecord::Base
message.join("\n\n") message.join("\n\n")
end end
def reset_merge_when_build_succeeds def reset_merge_when_pipeline_succeeds
return unless merge_when_build_succeeds? return unless merge_when_pipeline_succeeds?
self.merge_when_build_succeeds = false self.merge_when_pipeline_succeeds = false
self.merge_user = nil self.merge_user = nil
if merge_params if merge_params
merge_params.delete('should_remove_source_branch') merge_params.delete('should_remove_source_branch')
...@@ -684,7 +684,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -684,7 +684,10 @@ class MergeRequest < ActiveRecord::Base
end end
def has_ci? 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 end
def branch_missing? def branch_missing?
...@@ -706,7 +709,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -706,7 +709,7 @@ class MergeRequest < ActiveRecord::Base
end end
def mergeable_ci_state? 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? !head_pipeline || head_pipeline.success? || head_pipeline.skipped?
end end
......
...@@ -231,10 +231,6 @@ class Note < ActiveRecord::Base ...@@ -231,10 +231,6 @@ class Note < ActiveRecord::Base
note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/ note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
end end
def award_emoji_name
note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
end
def to_ability_name def to_ability_name
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
end end
......
...@@ -19,7 +19,7 @@ class Project < ActiveRecord::Base ...@@ -19,7 +19,7 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
class BoardLimitExceeded < StandardError; end BoardLimitExceeded = Class.new(StandardError)
NUMBER_OF_PERMITTED_BOARDS = 1 NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
...@@ -113,6 +113,7 @@ class Project < ActiveRecord::Base ...@@ -113,6 +113,7 @@ class Project < ActiveRecord::Base
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy
has_one :kubernetes_service, dependent: :destroy, inverse_of: :project 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_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
...@@ -334,7 +335,7 @@ class Project < ActiveRecord::Base ...@@ -334,7 +335,7 @@ class Project < ActiveRecord::Base
end end
def search_by_visibility(level) def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase)) where(visibility_level: Gitlab::VisibilityLevel.string_options[level])
end end
def search_by_title(query) def search_by_title(query)
...@@ -1003,7 +1004,7 @@ class Project < ActiveRecord::Base ...@@ -1003,7 +1004,7 @@ class Project < ActiveRecord::Base
end end
def visibility_level_field def visibility_level_field
visibility_level :visibility_level
end end
def archive! def archive!
......
...@@ -7,7 +7,7 @@ class ProjectWiki ...@@ -7,7 +7,7 @@ class ProjectWiki
'AsciiDoc' => :asciidoc 'AsciiDoc' => :asciidoc
}.freeze unless defined?(MARKUPS) }.freeze unless defined?(MARKUPS)
class CouldNotCreateWikiError < StandardError; end CouldNotCreateWikiError = Class.new(StandardError)
# Returns a string describing what went wrong after # Returns a string describing what went wrong after
# an operation fails. # an operation fails.
......
...@@ -6,6 +6,7 @@ class Repository ...@@ -6,6 +6,7 @@ class Repository
attr_accessor :path_with_namespace, :project attr_accessor :path_with_namespace, :project
CommitError = Class.new(StandardError) CommitError = Class.new(StandardError)
CreateTreeError = Class.new(StandardError)
# Methods that cache data from the Git repository. # Methods that cache data from the Git repository.
# #
...@@ -862,17 +863,18 @@ class Repository ...@@ -862,17 +863,18 @@ class Repository
end end
def revert( def revert(
user, commit, branch_name, revert_tree_id = nil, user, commit, branch_name,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project)
revert_tree_id ||= check_revert_content(commit, branch_name)
return false unless revert_tree_id
GitOperationService.new(user, self).with_branch( GitOperationService.new(user, self).with_branch(
branch_name, branch_name,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_project: start_project) do |start_commit| start_project: start_project) do |start_commit|
revert_tree_id = check_revert_content(commit, start_commit.sha)
unless revert_tree_id
raise Repository::CreateTreeError.new('Failed to revert commit')
end
committer = user_to_committer(user) committer = user_to_committer(user)
Rugged::Commit.create(rugged, Rugged::Commit.create(rugged,
...@@ -885,17 +887,18 @@ class Repository ...@@ -885,17 +887,18 @@ class Repository
end end
def cherry_pick( def cherry_pick(
user, commit, branch_name, cherry_pick_tree_id = nil, user, commit, branch_name,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project)
cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name)
return false unless cherry_pick_tree_id
GitOperationService.new(user, self).with_branch( GitOperationService.new(user, self).with_branch(
branch_name, branch_name,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_project: start_project) do |start_commit| start_project: start_project) do |start_commit|
cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha)
unless cherry_pick_tree_id
raise Repository::CreateTreeError.new('Failed to cherry-pick commit')
end
committer = user_to_committer(user) committer = user_to_committer(user)
Rugged::Commit.create(rugged, Rugged::Commit.create(rugged,
...@@ -919,9 +922,8 @@ class Repository ...@@ -919,9 +922,8 @@ class Repository
end end
end end
def check_revert_content(target_commit, branch_name) def check_revert_content(target_commit, source_sha)
source_sha = commit(branch_name).sha args = [target_commit.sha, source_sha]
args = [target_commit.sha, source_sha]
args << { mainline: 1 } if target_commit.merge_commit? args << { mainline: 1 } if target_commit.merge_commit?
revert_index = rugged.revert_commit(*args) revert_index = rugged.revert_commit(*args)
...@@ -933,9 +935,8 @@ class Repository ...@@ -933,9 +935,8 @@ class Repository
tree_id tree_id
end end
def check_cherry_pick_content(target_commit, branch_name) def check_cherry_pick_content(target_commit, source_sha)
source_sha = commit(branch_name).sha args = [target_commit.sha, source_sha]
args = [target_commit.sha, source_sha]
args << 1 if target_commit.merge_commit? args << 1 if target_commit.merge_commit?
cherry_pick_index = rugged.cherrypick_commit(*args) cherry_pick_index = rugged.cherrypick_commit(*args)
...@@ -995,6 +996,8 @@ class Repository ...@@ -995,6 +996,8 @@ class Repository
end end
def with_repo_branch_commit(start_repository, start_branch_name) def with_repo_branch_commit(start_repository, start_branch_name)
return yield(nil) if start_repository.empty_repo?
branch_name_or_sha = branch_name_or_sha =
if start_repository == self if start_repository == self
start_branch_name start_branch_name
......
...@@ -120,7 +120,7 @@ class Snippet < ActiveRecord::Base ...@@ -120,7 +120,7 @@ class Snippet < ActiveRecord::Base
end end
def visibility_level_field def visibility_level_field
visibility_level :visibility_level
end end
def no_highlighting? def no_highlighting?
......
...@@ -346,7 +346,11 @@ class User < ActiveRecord::Base ...@@ -346,7 +346,11 @@ class User < ActiveRecord::Base
# Return (create if necessary) the ghost user. The ghost user # Return (create if necessary) the ghost user. The ghost user
# owns records previously belonging to deleted users. # owns records previously belonging to deleted users.
def ghost def ghost
User.find_by_ghost(true) || create_ghost_user unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
u.state = :blocked
u.name = 'Ghost User'
end
end end
end end
...@@ -1017,10 +1021,14 @@ class User < ActiveRecord::Base ...@@ -1017,10 +1021,14 @@ class User < ActiveRecord::Base
end end
end end
def self.create_ghost_user def self.unique_internal(scope, username, email_pattern, &b)
# Since we only want a single ghost user in an instance, we use an scope.first || create_unique_internal(scope, username, email_pattern, &b)
end
def self.create_unique_internal(scope, username, email_pattern, &creation_block)
# Since we only want a single one of these in an instance, we use an
# exclusive lease to ensure than this block is never run concurrently. # exclusive lease to ensure than this block is never run concurrently.
lease_key = "ghost_user_creation" lease_key = "user:unique_internal:#{username}"
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i) lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
until uuid = lease.try_obtain until uuid = lease.try_obtain
...@@ -1029,25 +1037,25 @@ class User < ActiveRecord::Base ...@@ -1029,25 +1037,25 @@ class User < ActiveRecord::Base
sleep(1) sleep(1)
end end
# Recheck if a ghost user is already present. One might have been # Recheck if the user is already present. One might have been
# added between the time we last checked (first line of this method) # added between the time we last checked (first line of this method)
# and the time we acquired the lock. # and the time we acquired the lock.
ghost_user = User.find_by_ghost(true) existing_user = uncached { scope.first }
return ghost_user if ghost_user.present? return existing_user if existing_user.present?
uniquify = Uniquify.new uniquify = Uniquify.new
username = uniquify.string("ghost") { |s| User.find_by_username(s) } username = uniquify.string(username) { |s| User.find_by_username(s) }
email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s| email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
User.find_by_email(s) User.find_by_email(s)
end end
bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' scope.create(
username: username,
User.create( password: Devise.friendly_token,
username: username, password: Devise.friendly_token, bio: bio, email: email,
email: email, name: "Ghost User", state: :blocked, ghost: true &creation_block
) )
ensure ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid) Gitlab::ExclusiveLease.cancel(lease_key, uuid)
......
...@@ -33,8 +33,6 @@ class GroupPolicy < BasePolicy ...@@ -33,8 +33,6 @@ class GroupPolicy < BasePolicy
if globally_viewable && @subject.request_access_enabled && !member if globally_viewable && @subject.request_access_enabled && !member
can! :request_access can! :request_access
end end
additional_rules!(master)
end end
def can_read_group? def can_read_group?
...@@ -45,8 +43,4 @@ class GroupPolicy < BasePolicy ...@@ -45,8 +43,4 @@ class GroupPolicy < BasePolicy
GroupProjectsFinder.new(@subject).execute(@user).any? GroupProjectsFinder.new(@subject).execute(@user).any?
end end
def additional_rules!(master)
# This is meant to be overriden in EE
end
end end
...@@ -6,7 +6,7 @@ class MergeRequestEntity < IssuableEntity ...@@ -6,7 +6,7 @@ class MergeRequestEntity < IssuableEntity
expose :merge_params expose :merge_params
expose :merge_status expose :merge_status
expose :merge_user_id expose :merge_user_id
expose :merge_when_build_succeeds expose :merge_when_pipeline_succeeds
expose :source_branch expose :source_branch
expose :source_project_id expose :source_project_id
expose :target_branch expose :target_branch
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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