Commit 73c7dc10 authored by Felipe Artur's avatar Felipe Artur

CE upstream

parents 7ffe6cab 99fceff4
...@@ -3,4 +3,4 @@ lib/gitlab/sanitizers/svg/whitelist.rb ...@@ -3,4 +3,4 @@ lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb lib/gitlab/diff/position_tracer.rb
app/controllers/projects/approver_groups_controller.rb app/controllers/projects/approver_groups_controller.rb
app/controllers/projects/approvers_controller.rb app/controllers/projects/approvers_controller.rb
app/policies/project_policy.rb app/policies/project_policy.rb
\ No newline at end of file
...@@ -2,6 +2,21 @@ ...@@ -2,6 +2,21 @@
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.16.4 (2017-02-02)
- Support non-ASCII characters in GFM autocomplete. !8729
- Fix search bar search param encoding. !8753
- Fix project name label's for reference in project settings. !8795
- Fix filtering with multiple words. !8830
- Fixed services form cancel not redirecting back the integrations settings view. !8843
- Fix filtering usernames with multiple words. !8851
- Improve performance of slash commands. !8876
- Remove old project members when retrying an export.
- Fix permalink discussion note being collapsed.
- Add project ID index to `project_authorizations` table to optimize queries.
- Check public snippets for spam.
- 19164 Add settings dropdown to mobile screens.
## 8.16.3 (2017-01-27) ## 8.16.3 (2017-01-27)
- Add caching of droplab ajax requests. !8725 - Add caching of droplab ajax requests. !8725
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
- [Issue weight](#issue-weight) - [Issue weight](#issue-weight)
- [Regression issues](#regression-issues) - [Regression issues](#regression-issues)
- [Technical debt](#technical-debt) - [Technical debt](#technical-debt)
- [Stewardship][#stewardship]
- [Merge requests](#merge-requests) - [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines) - [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Contribution acceptance criteria](#contribution-acceptance-criteria)
...@@ -230,6 +231,21 @@ for a release by the appropriate person. ...@@ -230,6 +231,21 @@ for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is Make sure to mention the merge request that the `technical debt` issue is
associated with in the description of the issue. associated with in the description of the issue.
### Stewardship
For issues related to the open source stewardship of GitLab,
there is the ~"stewardship" label.
This label is to be used for issues in which the stewardship of GitLab
is a topic of discussion. For instance if GitLab Inc. is planning to remove
features from GitLab CE to make exclusive in GitLab EE, related issues
would be labelled with ~"stewardship".
A recent example of this was the issue for
[bringing the time tracking API to GitLab CE][time-tracking-issue].
[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
......
...@@ -33,7 +33,7 @@ core team members will mention this person. ...@@ -33,7 +33,7 @@ core team members will mention this person.
### Merge request coaching ### Merge request coaching
Several people from the [GitLab team][team] are helping community members to get Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done]. their contributions accepted by meeting our [Definition of done](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done).
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/. What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
...@@ -79,47 +79,47 @@ not be merged into any stable branches. ...@@ -79,47 +79,47 @@ not be merged into any stable branches.
### Improperly formatted issue ### Improperly formatted issue
Thanks for the issue report. Please reformat your issue to conform to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. Please reformat your issue to conform to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Issue report for old version ### Issue report for old version
Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Support requests and configuration questions ### Support requests and configuration questions
Thanks for your interest in GitLab. We don't use the issue tracker for support Thanks for your interest in GitLab. We don't use the issue tracker for support
requests and configuration questions. Please check our requests and configuration questions. Please check our
\[getting help\]\(https://about.gitlab.com/getting-help/) page to see all of the available [getting help](https://about.gitlab.com/getting-help/) page to see all of the available
support options. Also, have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) support options. Also, have a look at the [contribution guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md)
for more information. for more information.
### Code format ### Code format
Please use ``` to format console output, logs, and code as it's very hard to read otherwise. Please use \`\`\` to format console output, logs, and code as it's very hard to read otherwise.
### Issue fixed in newer version ### Issue fixed in newer version
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please [upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Improperly formatted merge request ### Improperly formatted merge request
Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines). Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines).
### Inactivity close of an issue ### Inactivity close of an issue
It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Inactivity close of a merge request ### Inactivity close of a merge request
This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request. This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request.
### Accepting merge requests ### Accepting merge requests
Is there an issue on the Is there an issue on the
\[issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues) that is [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
similar to this? Could you please link it here? similar to this? Could you please link it here?
Please be aware that new functionality that is not marked Please be aware that new functionality that is not marked
\[accepting merge requests\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests) [accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
might not make it into GitLab. might not make it into GitLab.
### Only accepting merge requests with green tests ### Only accepting merge requests with green tests
...@@ -134,7 +134,7 @@ rebase with master to see if that solves the issue. ...@@ -134,7 +134,7 @@ rebase with master to see if that solves the issue.
We are currently in the process of closing down the issue tracker on GitHub, to We are currently in the process of closing down the issue tracker on GitHub, to
prevent duplication with the GitLab.com issue tracker. prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues). still an issue I encourage you to open it on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues).
[team]: https://about.gitlab.com/team/ [team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria [contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
......
...@@ -17,7 +17,7 @@ require('./components/boards_selector'); ...@@ -17,7 +17,7 @@ require('./components/boards_selector');
require('./components/board_sidebar'); require('./components/board_sidebar');
require('./components/new_list_dropdown'); require('./components/new_list_dropdown');
require('./components/modal/index'); require('./components/modal/index');
require('./vue_resource_interceptor'); require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
......
/* global Vue */
const userFilter = require('./filters/user');
const milestoneFilter = require('./filters/milestone');
const labelFilter = require('./filters/label');
module.exports = Vue.extend({
name: 'modal-filters',
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
destroyed() {
gl.issueBoards.ModalStore.setDefaultFilter();
},
components: {
userFilter,
milestoneFilter,
labelFilter,
},
template: `
<div class="modal-filters">
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module.exports = Vue.extend({
name: 'filter-label',
props: {
labelPath: {
type: String,
required: true,
},
},
mounted() {
new LabelsSelect(this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module.exports = Vue.extend({
name: 'filter-milestone',
props: {
milestonePath: {
type: String,
required: true,
},
},
mounted() {
new MilestoneSelect(null, this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module.exports = Vue.extend({
name: 'filter-user',
props: {
toggleClassName: {
type: String,
required: true,
},
dropdownClassName: {
type: String,
required: false,
default: '',
},
toggleLabel: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
nullUser: {
type: Boolean,
required: false,
default: false,
},
projectId: {
type: Number,
required: true,
},
},
mounted() {
new UsersSelect(null, this.$refs.dropdown);
},
computed: {
currentUsername() {
return gon.current_username;
},
dropdownTitle() {
return `Filter by ${this.toggleLabel.toLowerCase()}`;
},
inputPlaceholder() {
return `Search ${this.toggleLabel.toLowerCase()}`;
},
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* global Vue */ /* global Vue */
require('./tabs'); require('./tabs');
const modalFilters = require('./filters');
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalHeader = Vue.extend({ gl.issueBoards.ModalHeader = Vue.extend({
mixins: [gl.issueBoards.ModalMixins], mixins: [gl.issueBoards.ModalMixins],
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
data() { data() {
return ModalStore.store; return ModalStore.store;
}, },
...@@ -31,6 +45,7 @@ require('./tabs'); ...@@ -31,6 +45,7 @@ require('./tabs');
}, },
components: { components: {
'modal-tabs': gl.issueBoards.ModalTabs, 'modal-tabs': gl.issueBoards.ModalTabs,
modalFilters,
}, },
template: ` template: `
<div> <div>
...@@ -51,6 +66,11 @@ require('./tabs'); ...@@ -51,6 +66,11 @@ require('./tabs');
<div <div
class="add-issues-search append-bottom-10" class="add-issues-search append-bottom-10"
v-if="showSearch"> v-if="showSearch">
<modal-filters
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input <input
placeholder="Search issues..." placeholder="Search issues..."
class="form-control" class="form-control"
......
...@@ -27,6 +27,18 @@ require('./empty_state'); ...@@ -27,6 +27,18 @@ require('./empty_state');
type: String, type: String,
required: true, required: true,
}, },
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
}, },
data() { data() {
return ModalStore.store; return ModalStore.store;
...@@ -52,17 +64,27 @@ require('./empty_state'); ...@@ -52,17 +64,27 @@ require('./empty_state');
this.issuesCount = false; this.issuesCount = false;
} }
}, },
filter: {
handler() {
this.loadIssues(true);
},
deep: true,
},
}, },
methods: { methods: {
searchOperation: _.debounce(function searchOperationDebounce() { searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true); this.loadIssues(true);
}, 500), }, 500),
loadIssues(clearIssues = false) { loadIssues(clearIssues = false) {
return gl.boardService.getBacklog({ if (!this.showAddIssuesModal) return false;
const queryData = Object.assign({}, this.filter, {
search: this.searchTerm, search: this.searchTerm,
page: this.page, page: this.page,
per: this.perPage, per: this.perPage,
}).then((res) => { });
return gl.boardService.getBacklog(queryData).then((res) => {
const data = res.json(); const data = res.json();
if (clearIssues) { if (clearIssues) {
...@@ -112,8 +134,13 @@ require('./empty_state'); ...@@ -112,8 +134,13 @@ require('./empty_state');
class="add-issues-modal" class="add-issues-modal"
v-if="showAddIssuesModal"> v-if="showAddIssuesModal">
<div class="add-issues-container"> <div class="add-issues-container">
<modal-header></modal-header> <modal-header
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-header>
<modal-list <modal-list
:image="blankStateImage"
:issue-link-base="issueLinkBase" :issue-link-base="issueLinkBase"
:root-path="rootPath" :root-path="rootPath"
v-if="!loading && showList"></modal-list> v-if="!loading && showList"></modal-list>
......
...@@ -14,6 +14,10 @@ ...@@ -14,6 +14,10 @@
type: String, type: String,
required: true, required: true,
}, },
image: {
type: String,
required: true,
},
}, },
data() { data() {
return ModalStore.store; return ModalStore.store;
...@@ -110,6 +114,19 @@ ...@@ -110,6 +114,19 @@
<section <section
class="add-issues-list add-issues-list-columns" class="add-issues-list add-issues-list-columns"
ref="list"> ref="list">
<div
class="empty-state add-issues-empty-state-filter text-center"
v-if="issuesCount > 0 && issues.length === 0">
<div
class="svg-content"
v-html="image">
</div>
<div class="text-content">
<h4>
There are no issues to show.
</h4>
</div>
</div>
<div <div
v-for="group in groupedIssues" v-for="group in groupedIssues"
class="add-issues-list-column"> class="add-issues-list-column">
......
...@@ -18,6 +18,17 @@ ...@@ -18,6 +18,17 @@
page: 1, page: 1,
perPage: 50, perPage: 50,
}; };
this.setDefaultFilter();
}
setDefaultFilter() {
this.store.filter = {
author_id: '',
assignee_id: '',
milestone_title: '',
label_name: [],
};
} }
selectedCount() { selectedCount() {
......
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next(function (response) {
Vue.activeResources -= 1;
});
});
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
require('./pipelines_table');
/**
* Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
*/
$(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
gl.commits.PipelinesTableBundle.$destroy(true);
}
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
});
/* globals Vue */
/* eslint-disable no-unused-vars, no-param-reassign */
/**
* Pipelines service.
*
* Used to fetch the data used to render the pipelines table.
* Uses Vue.Resource
*/
class PipelinesService {
constructor(endpoint) {
this.pipelines = Vue.resource(endpoint);
}
/**
* Given the root param provided when the class is initialized, will
* make a GET request.
*
* @return {Promise}
*/
all() {
return this.pipelines.get();
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesService = PipelinesService;
/* eslint-disable no-underscore-dangle*/
/**
* Pipelines' Store for commits view.
*
* Used to store the Pipelines rendered in the commit view in the pipelines table.
*/
class PipelinesStore {
constructor() {
this.state = {};
this.state.pipelines = [];
}
storePipelines(pipelines = []) {
this.state.pipelines = pipelines;
return pipelines;
}
/**
* Once the data is received we will start the time ago loops.
*
* Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
* update the time to show how long as passed.
*
*/
startTimeAgoLoops() {
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000);
};
startTimeLoops();
const removeIntervals = () => clearInterval(this.timeLoopInterval);
const startIntervals = () => startTimeLoops();
gl.VueRealtimeListener(removeIntervals, startIntervals);
}
}
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesStore = PipelinesStore;
/* eslint-disable no-new, no-param-reassign */
/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table');
require('../../vue_realtime_listener/index');
require('./pipelines_service');
require('./pipelines_store');
/**
*
* Uses `pipelines-table-component` to render Pipelines table with an API call.
* Endpoint is provided in HTML and passed as `endpoint`.
* We need a store to store the received environemnts.
* We need a service to communicate with the server.
*
* Necessary SVG in the table are provided as props. This should be refactored
* as soon as we have Webpack and can load them directly into JS files.
*/
(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', {
components: {
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
},
/**
* Accesses the DOM to provide the needed data.
* Returns the necessary props to render `pipelines-table-component` component.
*
* @return {Object}
*/
data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const svgsData = document.querySelector('.pipeline-svgs').dataset;
const store = new gl.commits.pipelines.PipelinesStore();
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
return {
endpoint: pipelinesTableData.endpoint,
svgs: svgsObject,
store,
state: store.state,
isLoading: false,
};
},
/**
* When the component is created the service to fetch the data will be
* initialized with the correct endpoint.
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
created() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
return pipelinesService.all()
.then(response => response.json())
.then((json) => {
this.store.storePipelines(json);
this.store.startTimeAgoLoops.call(this, Vue);
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
});
},
template: `
<div>
<div class="pipelines realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</div>
<div class="table-holder pipelines"
v-if="!isLoading && state.pipelines.length > 0">
<pipelines-table-component
:pipelines="state.pipelines"
:svgs="svgs">
</pipelines-table-component>
</div>
</div>
`,
});
})();
...@@ -162,11 +162,6 @@ ...@@ -162,11 +162,6 @@
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
case 'projects:commit:pipelines':
new gl.MiniPipelineGraph({
container: '.js-pipeline-table',
});
break;
case 'projects:commits:show': case 'projects:commits:show':
case 'projects:activity': case 'projects:activity':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -269,7 +264,7 @@ ...@@ -269,7 +264,7 @@
new gl.ProtectedBranchCreate(); new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList(); new gl.ProtectedBranchEditList();
break; break;
case 'projects:variables:index': case 'projects:ci_cd:show':
new gl.ProjectVariables(); new gl.ProjectVariables();
break; break;
case 'ci:lints:create': case 'ci:lints:create':
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
window.Vue = require('vue'); window.Vue = require('vue');
window.timeago = require('vendor/timeago'); window.timeago = require('vendor/timeago');
require('../../lib/utils/text_utility'); require('../../lib/utils/text_utility');
require('../../vue_common_component/commit'); require('../../vue_shared/components/commit');
require('./environment_actions'); require('./environment_actions');
require('./environment_external_url'); require('./environment_external_url');
require('./environment_stop'); require('./environment_stop');
......
window.Vue = require('vue'); window.Vue = require('vue');
require('./stores/environments_store'); require('./stores/environments_store');
require('./components/environment'); require('./components/environment');
require('./vue_resource_interceptor'); require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
...@@ -8,7 +8,7 @@ require('./filtered_search_dropdown'); ...@@ -8,7 +8,7 @@ require('./filtered_search_dropdown');
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabAjaxFilter: { droplabAjaxFilter: {
endpoint: '/autocomplete/users.json', endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search', searchKey: 'search',
params: { params: {
per_page: 20, per_page: 20,
......
...@@ -4,10 +4,17 @@ ...@@ -4,10 +4,17 @@
(function() { (function() {
this.LabelsSelect = (function() { this.LabelsSelect = (function() {
function LabelsSelect() { function LabelsSelect(els) {
var _this; var _this, $els;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) {
$els = $(els);
if (!els) {
$els = $('.js-label-select');
}
$els.each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer;
$dropdown = $(dropdown); $dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter'); $dropdownContainer = $dropdown.closest('.labels-filter');
...@@ -324,7 +331,7 @@ ...@@ -324,7 +331,7 @@
multiSelect: $dropdown.hasClass('js-multiselect'), multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e, isMarking) { clicked: function(label, $el, e, isMarking) {
var isIssueIndex, isMRIndex, page; var isIssueIndex, isMRIndex, page, boardsModel;
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
...@@ -346,22 +353,31 @@ ...@@ -346,22 +353,31 @@
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
!$dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.ModalStore.store.filter;
}
if (boardsModel) {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; boardsModel['label_name'] = [];
} }
else if ($el.hasClass('is-active')) { else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); boardsModel['label_name'].push(label.title);
} }
else { else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; var filters = boardsModel['label_name'];
filters = filters.filter(function (filteredLabel) { filters = filters.filter(function (filteredLabel) {
return filteredLabel !== label.title; return filteredLabel !== label.title;
}); });
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; boardsModel['label_name'] = filters;
} }
gl.issueBoards.BoardsStore.updateFiltersUrl(); if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault(); e.preventDefault();
return; return;
} }
......
...@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort. ...@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.
File.basename(file, '.js').sub(/^mode-/, '') File.basename(file, '.js').sub(/^mode-/, '')
end end
%> %>
// Lazy-load configuration when ace.edit is called
(function() { (function() {
window.gon = window.gon || {}; var basePath;
var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace'; var ace = window.ace;
ace.config.set('basePath', basePath); var edit = ace.edit;
ace.edit = function() {
window.gon = window.gon || {};
basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
ace.config.set('basePath', basePath);
// configure paths for all worker modules // configure paths for all worker modules
<% ace_workers.each do |worker| %> <% ace_workers.each do |worker| %>
ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js'); ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/<%= File.basename(asset_path("ace/worker-#{worker}.js")) %>');
<% end %> <% end %>
// configure paths for all mode modules // configure paths for all mode modules
<% ace_modes.each do |mode| %> <% ace_modes.each do |mode| %>
ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js'); ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/<%= File.basename(asset_path("ace/mode-#{mode}.js")) %>');
<% end %> <% end %>
// restore original method
ace.edit = edit;
return ace.edit.apply(ace, arguments);
};
})(); })();
...@@ -230,5 +230,16 @@ ...@@ -230,5 +230,16 @@
return upperCaseHeaders; return upperCaseHeaders;
}; };
/**
* 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;
}, {});
})(window); })(window);
}).call(this); }).call(this);
...@@ -61,7 +61,6 @@ require('./flash'); ...@@ -61,7 +61,6 @@ require('./flash');
constructor({ action, setUrl, stubLocation } = {}) { constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false; this.diffsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false; this.commitsLoaded = false;
this.fixedLayoutPref = null; this.fixedLayoutPref = null;
...@@ -116,10 +115,6 @@ require('./flash'); ...@@ -116,10 +115,6 @@ require('./flash');
$.scrollTo('.merge-request-details .merge-request-tabs', { $.scrollTo('.merge-request-details .merge-request-tabs', {
offset: -navBarHeight, offset: -navBarHeight,
}); });
} else if (action === 'pipelines') {
this.loadPipelines($target.attr('href'));
this.expandView();
this.resetViewContainer();
} else { } else {
this.expandView(); this.expandView();
this.resetViewContainer(); this.resetViewContainer();
...@@ -244,25 +239,6 @@ require('./flash'); ...@@ -244,25 +239,6 @@ require('./flash');
}); });
} }
loadPipelines(source) {
if (this.pipelinesLoaded) {
return;
}
this.ajaxGet({
url: `${source}.json`,
success: (data) => {
$('#pipelines').html(data.html);
gl.utils.localTimeAgo($('.js-timeago', '#pipelines'));
this.pipelinesLoaded = true;
this.scrollToElement('#pipelines');
new gl.MiniPipelineGraph({
container: '.js-pipeline-table',
});
},
});
}
// Show or hide the loading spinner // Show or hide the loading spinner
// //
// status - Boolean, true to show, false to hide // status - Boolean, true to show, false to hide
......
...@@ -5,13 +5,20 @@ ...@@ -5,13 +5,20 @@
(function() { (function() {
this.MilestoneSelect = (function() { this.MilestoneSelect = (function() {
function MilestoneSelect(currentProject) { function MilestoneSelect(currentProject, els) {
var _this; var _this, $els;
if (currentProject != null) { if (currentProject != null) {
_this = this; _this = this;
this.currentProject = JSON.parse(currentProject); this.currentProject = JSON.parse(currentProject);
} }
$('.js-milestone-select').each(function(i, dropdown) {
$els = $(els);
if (!els) {
$els = $('.js-milestone-select');
}
$els.each(function(i, dropdown) {
var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove; var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
$dropdown = $(dropdown); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
...@@ -108,7 +115,7 @@ ...@@ -108,7 +115,7 @@
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(selected, $el, e) { clicked: function(selected, $el, e) {
var data, isIssueIndex, isMRIndex, page; var data, isIssueIndex, isMRIndex, page, boardsStore;
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index'); isMRIndex = (page === page && page === 'projects:merge_requests:index');
...@@ -116,9 +123,19 @@ ...@@ -116,9 +123,19 @@
e.preventDefault(); e.preventDefault();
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
gl.issueBoards.BoardsStore.updateFiltersUrl(); !$dropdown.closest('.add-issues-modal').length) {
boardsStore = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
boardsStore = gl.issueBoards.ModalStore.store.filter;
}
if (boardsStore) {
boardsStore[$dropdown.data('field-name')] = selected.name;
if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault(); e.preventDefault();
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
if (selected.name != null) { if (selected.name != null) {
......
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
slice = [].slice; slice = [].slice;
this.UsersSelect = (function() { this.UsersSelect = (function() {
function UsersSelect(currentUser) { function UsersSelect(currentUser, els) {
var $els;
this.users = bind(this.users, this); this.users = bind(this.users, this);
this.user = bind(this.user, this); this.user = bind(this.user, this);
this.usersPath = "/autocomplete/users.json"; this.usersPath = "/autocomplete/users.json";
...@@ -20,7 +21,14 @@ ...@@ -20,7 +21,14 @@
this.currentUser = JSON.parse(currentUser); this.currentUser = JSON.parse(currentUser);
} }
} }
$('.js-user-search').each((function(_this) {
$els = $(els);
if (!els) {
$els = $('.js-user-search');
}
$els.each((function(_this) {
return function(i, dropdown) { return function(i, dropdown) {
var options = {}; var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove; var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
...@@ -193,7 +201,9 @@ ...@@ -193,7 +201,9 @@
selectedId = user.id; selectedId = user.id;
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if ($el.closest('.add-issues-modal').length) {
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
} else if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
selectedId = user.id; selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
......
/* eslint-disable no-param-reassign */
/* global Vue, VueResource, gl */ /* global Vue, VueResource, gl */
window.Vue = require('vue'); window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
require('../vue_common_component/commit'); require('../lib/utils/common_utils');
require('../vue_pagination/index'); require('../vue_shared/vue_resource_interceptor');
require('../boards/vue_resource_interceptor');
require('./status');
require('./store');
require('./pipeline_url');
require('./stage');
require('./stages');
require('./pipeline_actions');
require('./time_ago');
require('./pipelines'); require('./pipelines');
(() => { $(() => new Vue({
const project = document.querySelector('.pipelines'); el: document.querySelector('.vue-pipelines-index'),
const entry = document.querySelector('.vue-pipelines-index');
const svgs = document.querySelector('.pipeline-svgs');
if (!entry) return null; data() {
return new Vue({ const project = document.querySelector('.pipelines');
el: entry, const svgs = document.querySelector('.pipeline-svgs').dataset;
data: {
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgs);
return {
scope: project.dataset.url, scope: project.dataset.url,
store: new gl.PipelineStore(), store: new gl.PipelineStore(),
svgs: svgs.dataset, svgs: svgsObject,
}, };
components: { },
'vue-pipelines': gl.VuePipelines, components: {
}, 'vue-pipelines': gl.VuePipelines,
template: ` },
<vue-pipelines template: `
:scope='scope' <vue-pipelines
:store='store' :scope='scope'
:svgs='svgs' :store='store'
> :svgs='svgs'
</vue-pipelines> >
`, </vue-pipelines>
}); `,
})(); }));
...@@ -50,9 +50,9 @@ ...@@ -50,9 +50,9 @@
<button <button
v-if='artifacts' v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download" class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
data-toggle="dropdown"
title="Artifacts" title="Artifacts"
data-placement="top" data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts" aria-label="Artifacts"
> >
<i class="fa fa-download" aria-hidden="true"></i> <i class="fa fa-download" aria-hidden="true"></i>
...@@ -81,8 +81,7 @@ ...@@ -81,8 +81,7 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
:href='pipeline.retry_path' :href='pipeline.retry_path'
aria-label="Retry" aria-label="Retry">
>
<i class="fa fa-repeat" aria-hidden="true"></i> <i class="fa fa-repeat" aria-hidden="true"></i>
</a> </a>
<a <a
...@@ -94,8 +93,7 @@ ...@@ -94,8 +93,7 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
:href='pipeline.cancel_path' :href='pipeline.cancel_path'
aria-label="Cancel" aria-label="Cancel">
>
<i class="fa fa-remove" aria-hidden="true"></i> <i class="fa fa-remove" aria-hidden="true"></i>
</a> </a>
</div> </div>
......
/* global Vue, gl */ /* global Vue, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
window.Vue = require('vue');
require('../vue_shared/components/table_pagination');
require('./store');
require('../vue_shared/components/pipelines_table');
((gl) => { ((gl) => {
gl.VuePipelines = Vue.extend({ gl.VuePipelines = Vue.extend({
components: { components: {
runningPipeline: gl.VueRunningPipeline, 'gl-pagination': gl.VueGlPagination,
pipelineActions: gl.VuePipelineActions, 'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
stages: gl.VueStages,
commit: gl.CommitComponent,
pipelineUrl: gl.VuePipelineUrl,
pipelineHead: gl.VuePipelineHead,
glPagination: gl.VueGlPagination,
statusScope: gl.VueStatusScope,
timeAgo: gl.VueTimeAgo,
}, },
data() { data() {
return { return {
pipelines: [], pipelines: [],
...@@ -38,87 +38,29 @@ ...@@ -38,87 +38,29 @@
change(pagenum, apiScope) { change(pagenum, apiScope) {
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
}, },
author(pipeline) {
if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' };
if (pipeline.commit.author) return pipeline.commit.author;
return {
avatar_url: pipeline.commit.author_gravatar_url,
web_url: `mailto:${pipeline.commit.author_email}`,
username: pipeline.commit.author_name,
};
},
ref(pipeline) {
const { ref } = pipeline;
return { name: ref.name, tag: ref.tag, ref_url: ref.path };
},
commitTitle(pipeline) {
return pipeline.commit ? pipeline.commit.title : '';
},
commitSha(pipeline) {
return pipeline.commit ? pipeline.commit.short_id : '';
},
commitUrl(pipeline) {
return pipeline.commit ? pipeline.commit.commit_path : '';
},
match(string) {
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
},
}, },
template: ` template: `
<div> <div>
<div class="pipelines realtime-loading" v-if='pipelines.length < 1'> <div class="pipelines realtime-loading" v-if='pageRequest'>
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
</div> </div>
<div class="table-holder" v-if='pipelines.length'>
<table class="table ci-table"> <div class="blank-state blank-state-no-icon"
<thead> v-if="!pageRequest && pipelines.length === 0">
<tr> <h2 class="blank-state-title js-blank-state-title">
<th class="pipeline-status">Status</th> No pipelines to show
<th class="pipeline-info">Pipeline</th> </h2>
<th class="pipeline-commit">Commit</th>
<th class="pipeline-stages">Stages</th>
<th class="pipeline-date"></th>
<th class="pipeline-actions hidden-xs"></th>
</tr>
</thead>
<tbody>
<tr class="commit" v-for='pipeline in pipelines'>
<status-scope
:pipeline='pipeline'
:match='match'
:svgs='svgs'
>
</status-scope>
<pipeline-url :pipeline='pipeline'></pipeline-url>
<td>
<commit
:commit-icon-svg='svgs.commitIconSvg'
:author='author(pipeline)'
:tag="pipeline.ref.tag"
:title='commitTitle(pipeline)'
:commit-ref='ref(pipeline)'
:short-sha='commitSha(pipeline)'
:commit-url='commitUrl(pipeline)'
>
</commit>
</td>
<stages
:pipeline='pipeline'
:svgs='svgs'
:match='match'
>
</stages>
<time-ago :pipeline='pipeline' :svgs='svgs'></time-ago>
<pipeline-actions :pipeline='pipeline' :svgs='svgs'></pipeline-actions>
</tr>
</tbody>
</table>
</div> </div>
<div class="pipelines realtime-loading" v-if='pageRequest'>
<i class="fa fa-spinner fa-spin"></i> <div class="table-holder" v-if='!pageRequest && pipelines.length'>
<pipelines-table-component
:pipelines='pipelines'
:svgs='svgs'>
</pipelines-table-component>
</div> </div>
<gl-pagination <gl-pagination
v-if='pageInfo.total > pageInfo.perPage' v-if='!pageRequest && pipelines.length && pageInfo.total > pageInfo.perPage'
:pagenum='pagenum' :pagenum='pagenum'
:change='change' :change='change'
:count='count.all' :count='count.all'
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
required: true, required: true,
}, },
svgs: { svgs: {
type: DOMStringMap, type: Object,
required: true, required: true,
}, },
match: { match: {
......
/* global Vue, gl */
/* eslint-disable no-param-reassign */
((gl) => {
gl.VueStages = Vue.extend({
components: {
'vue-stage': gl.VueStage,
},
props: ['pipeline', 'svgs', 'match'],
template: `
<td class="stage-cell">
<div
class="stage-container dropdown js-mini-pipeline-graph"
v-for='stage in pipeline.details.stages'
>
<vue-stage :stage='stage' :svgs='svgs' :match='match'></vue-stage>
</div>
</td>
`,
});
})(window.gl || (window.gl = {}));
...@@ -20,6 +20,7 @@ require('../vue_realtime_listener'); ...@@ -20,6 +20,7 @@ require('../vue_realtime_listener');
gl.PipelineStore = class { gl.PipelineStore = class {
fetchDataLoop(Vue, pageNum, url, apiScope) { fetchDataLoop(Vue, pageNum, url, apiScope) {
this.pageRequest = true;
const updatePipelineNums = (count) => { const updatePipelineNums = (count) => {
const { all } = count; const { all } = count;
const running = count.running_or_pending; const running = count.running_or_pending;
...@@ -41,16 +42,18 @@ require('../vue_realtime_listener'); ...@@ -41,16 +42,18 @@ require('../vue_realtime_listener');
this.pageRequest = false; this.pageRequest = false;
}, () => { }, () => {
this.pageRequest = false; this.pageRequest = false;
return new Flash('Something went wrong on our end.'); return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
}); });
goFetch(); goFetch();
const startTimeLoops = () => { const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => { this.timeLoopInterval = setInterval(() => {
this.$children this.$children[0].$children.reduce((acc, component) => {
.filter(e => e.$options._componentTag === 'time-ago') const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
.forEach(e => e.changeTime()); acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000); }, 10000);
}; };
......
/* global Vue, gl */ /* global Vue, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
window.Vue = require('vue');
require('../lib/utils/datetime_utility');
((gl) => { ((gl) => {
gl.VueTimeAgo = Vue.extend({ gl.VueTimeAgo = Vue.extend({
data() { data() {
......
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* eslint-disable no-param-reassign */
/* global Vue */
require('./pipelines_table_row');
/**
* Pipelines Table Component.
*
* Given an array of objects, renders a table.
*/
(() => {
window.gl = window.gl || {};
gl.pipelines = gl.pipelines || {};
gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', {
props: {
pipelines: {
type: Array,
required: true,
default: () => ([]),
},
/**
* TODO: Remove this when we have webpack.
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
},
components: {
'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent,
},
template: `
<table class="table ci-table">
<thead>
<tr>
<th class="js-pipeline-status pipeline-status">Status</th>
<th class="js-pipeline-info pipeline-info">Pipeline</th>
<th class="js-pipeline-commit pipeline-commit">Commit</th>
<th class="js-pipeline-stages pipeline-stages">Stages</th>
<th class="js-pipeline-date pipeline-date"></th>
<th class="js-pipeline-actions pipeline-actions hidden-xs"></th>
</tr>
</thead>
<tbody>
<template v-for="model in pipelines"
v-bind:model="model">
<tr is="pipelines-table-row-component"
:pipeline="model"
:svgs="svgs"></tr>
</template>
</tbody>
</table>
`,
});
})();
/* eslint-disable no-param-reassign */
/* global Vue */
require('../../vue_pipelines_index/status');
require('../../vue_pipelines_index/pipeline_url');
require('../../vue_pipelines_index/stage');
require('../../vue_pipelines_index/pipeline_actions');
require('../../vue_pipelines_index/time_ago');
require('./commit');
/**
* Pipeline table row.
*
* Given the received object renders a table row in the pipelines' table.
*/
(() => {
window.gl = window.gl || {};
gl.pipelines = gl.pipelines || {};
gl.pipelines.PipelinesTableRowComponent = Vue.component('pipelines-table-row-component', {
props: {
pipeline: {
type: Object,
required: true,
default: () => ({}),
},
/**
* TODO: Remove this when we have webpack;
*/
svgs: {
type: Object,
required: true,
default: () => ({}),
},
},
components: {
'commit-component': gl.CommitComponent,
'pipeline-actions': gl.VuePipelineActions,
'dropdown-stage': gl.VueStage,
'pipeline-url': gl.VuePipelineUrl,
'status-scope': gl.VueStatusScope,
'time-ago': gl.VueTimeAgo,
},
computed: {
/**
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
* This field needs a lot of verification, because of different possible cases:
*
* 1. person who is an author of a commit might be a GitLab user
* 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
* 3. If GitLab user does not have avatar he/she might have a Gravatar
* 4. If committer is not a GitLab User he/she can have a Gravatar
* 5. We do not have consistent API object in this case
* 6. We should improve API and the code
*
* @returns {Object|Undefined}
*/
commitAuthor() {
let commitAuthorInformation;
// 1. person who is an author of a commit might be a GitLab user
if (this.pipeline &&
this.pipeline.commit &&
this.pipeline.commit.author) {
// 2. if person who is an author of a commit is a GitLab user
// he/she can have a GitLab avatar
if (this.pipeline.commit.author.avatar_url) {
commitAuthorInformation = this.pipeline.commit.author;
// 3. If GitLab user does not have avatar he/she might have a Gravatar
} else if (this.pipeline.commit.author_gravatar_url) {
commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
avatar_url: this.pipeline.commit.author_gravatar_url,
});
}
}
// 4. If committer is not a GitLab User he/she can have a Gravatar
if (this.pipeline &&
this.pipeline.commit) {
commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url,
web_url: `mailto:${this.pipeline.commit.author_email}`,
username: this.pipeline.commit.author_name,
};
}
return commitAuthorInformation;
},
/**
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTag() {
if (this.pipeline.ref &&
this.pipeline.ref.tag) {
return this.pipeline.ref.tag;
}
return undefined;
},
/**
* If provided, returns the commit ref.
* Needed to render the commit component column.
*
* Matched `url` prop sent in the API to `path` prop needed
* in the commit component.
*
* @returns {Object|Undefined}
*/
commitRef() {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'url') {
accumulator.path = this.pipeline.ref[prop];
} else {
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
}, {});
}
return undefined;
},
/**
* If provided, returns the commit url.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitUrl() {
if (this.pipeline.commit &&
this.pipeline.commit.commit_path) {
return this.pipeline.commit.commit_path;
}
return undefined;
},
/**
* If provided, returns the commit short sha.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitShortSha() {
if (this.pipeline.commit &&
this.pipeline.commit.short_id) {
return this.pipeline.commit.short_id;
}
return undefined;
},
/**
* If provided, returns the commit title.
* Needed to render the commit component column.
*
* @returns {String|Undefined}
*/
commitTitle() {
if (this.pipeline.commit &&
this.pipeline.commit.title) {
return this.pipeline.commit.title;
}
return undefined;
},
},
methods: {
/**
* FIXME: This should not be in this component but in the components that
* need this function.
*
* Used to render SVGs in the following components:
* - status-scope
* - dropdown-stage
*
* @param {String} string
* @return {String}
*/
match(string) {
return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
},
},
template: `
<tr class="commit">
<status-scope
:pipeline="pipeline"
:svgs="svgs"
:match="match">
</status-scope>
<pipeline-url :pipeline="pipeline"></pipeline-url>
<td>
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
:commit-icon-svg="svgs.commitIconSvg">
</commit-component>
</td>
<td class="stage-cell">
<div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages">
<dropdown-stage
:stage="stage"
:svgs="svgs"
:match="match">
</dropdown-stage>
</div>
</td>
<time-ago :pipeline="pipeline" :svgs="svgs"></time-ago>
<pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions>
</tr>
`,
});
})();
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars,
no-param-reassign, no-plusplus */
/* global Vue */ /* global Vue */
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next((response) => { next((response) => {
if (typeof response.data === 'string') { if (typeof response.data === 'string') {
response.data = JSON.parse(response.data); // eslint-disable-line response.data = JSON.parse(response.data);
} }
Vue.activeResources--; // eslint-disable-line Vue.activeResources--;
}); });
}); });
Vue.http.interceptors.push((request, next) => {
// needed in order to not break the tests.
if ($.rails) {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
}
next();
});
...@@ -71,6 +71,27 @@ ...@@ -71,6 +71,27 @@
transition: $unfoldedTransitions; transition: $unfoldedTransitions;
} }
@mixin disableAllAnimation {
/*CSS transitions*/
-o-transition-property: none !important;
-moz-transition-property: none !important;
-ms-transition-property: none !important;
-webkit-transition-property: none !important;
transition-property: none !important;
/*CSS transforms*/
-o-transform: none !important;
-moz-transform: none !important;
-ms-transform: none !important;
-webkit-transform: none !important;
transform: none !important;
/*CSS animations*/
-webkit-animation: none !important;
-moz-animation: none !important;
-o-animation: none !important;
-ms-animation: none !important;
animation: none !important;
}
@function unfoldTransition ($transition) { @function unfoldTransition ($transition) {
// Default values // Default values
$property: all; $property: all;
...@@ -116,11 +137,13 @@ a { ...@@ -116,11 +137,13 @@ a {
@include transition(background-color, color, border); @include transition(background-color, color, border);
} }
.tree-table td,
.well-list > li {
@include transition(background-color, border-color);
}
.stage-nav-item { .stage-nav-item {
@include transition(background-color, box-shadow); @include transition(background-color, box-shadow);
} }
.nav-sidebar a,
.dropdown-menu a,
.dropdown-menu button,
.dropdown-menu-nav a {
transition: none;
}
...@@ -253,6 +253,8 @@ li.note { ...@@ -253,6 +253,8 @@ li.note {
.progress { .progress {
margin-bottom: 0; margin-bottom: 0;
margin-top: 4px; margin-top: 4px;
box-shadow: none;
background-color: $border-gray-light;
} }
} }
......
...@@ -159,6 +159,7 @@ ...@@ -159,6 +159,7 @@
.cur { .cur {
.avatar { .avatar {
border: 1px solid $white-light; border: 1px solid $white-light;
@include disableAllAnimation;
} }
} }
} }
...@@ -6,8 +6,22 @@ ...@@ -6,8 +6,22 @@
.pagination { .pagination {
padding: 0; padding: 0;
a {
cursor: pointer;
}
.separator,
.separator:hover {
a {
cursor: default;
background-color: $gray-light;
padding: $gl-vert-padding;
}
}
} }
.gap, .gap,
.gap:hover { .gap:hover {
background-color: $gray-light; background-color: $gray-light;
......
...@@ -423,6 +423,13 @@ ...@@ -423,6 +423,13 @@
flex: 1; flex: 1;
margin-top: 0; margin-top: 0;
&.add-issues-empty-state-filter {
-webkit-flex-direction: column;
flex-direction: column;
-webkit-justify-content: center;
justify-content: center;
}
> .row { > .row {
width: 100%; width: 100%;
margin: auto 0; margin: auto 0;
...@@ -450,6 +457,14 @@ ...@@ -450,6 +457,14 @@
.add-issues-search { .add-issues-search {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
.form-control {
margin-left: auto;
@media (min-width: $screen-sm-min) {
max-width: 200px;
}
}
} }
.add-issues-list-column { .add-issues-list-column {
...@@ -520,3 +535,24 @@ ...@@ -520,3 +535,24 @@
line-height: 15px; line-height: 15px;
border-radius: 50%; border-radius: 50%;
} }
.modal-filters {
display: flex;
> .dropdown {
display: none;
margin-right: 10px;
@media (min-width: $screen-sm-min) {
display: block;
}
}
.dropdown-menu-toggle {
width: 100px;
@media (min-width: $screen-md-min) {
width: 140px;
}
}
}
...@@ -159,7 +159,6 @@ ...@@ -159,7 +159,6 @@
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid $white-normal;
padding: 10px 15px; padding: 10px 15px;
margin: 10px 0; margin: 10px 0;
background: $gray-light; background: $gray-light;
......
...@@ -147,6 +147,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -147,6 +147,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:user_default_external, :user_default_external,
:user_oauth_applications, :user_oauth_applications,
:version_check_enabled, :version_check_enabled,
:terminal_max_session_time,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
import_sources: [], import_sources: [],
......
...@@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController
format.json do format.json do
render json: PipelineSerializer render json: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines) .represent(@pipelines)
end end
end end
......
...@@ -219,19 +219,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -219,19 +219,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
format.json do format.json do
render json: { render json: PipelineSerializer
html: view_to_html_string('projects/merge_requests/show/_pipelines'), .new(project: @project, user: @current_user)
pipelines: PipelineSerializer .represent(@pipelines)
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
}
end end
end end
end end
def new def new
define_new_vars respond_to do |format|
format.html { define_new_vars }
format.json do
render json: { pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines) }
end
end
end end
def new_diffs def new_diffs
......
...@@ -2,20 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -2,20 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline! before_action :authorize_admin_pipeline!
def show def show
@ref = params[:ref] || @project.default_branch || 'master' redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params)
@badges = [Gitlab::Badge::Build::Status,
Gitlab::Badge::Coverage::Report]
@badges.map! do |badge|
badge.new(@project, @ref).metadata
end
end end
def update def update
if @project.update_attributes(update_params) if @project.update_attributes(update_params)
flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated." flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated."
redirect_to namespace_project_pipelines_settings_path(@project.namespace, @project) redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
else else
render 'show' render 'show'
end end
......
...@@ -5,11 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -5,11 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
@project_runners = project.runners.ordered redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
@assignable_runners = current_user.ci_authorized_runners.
assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end end
def edit def edit
...@@ -53,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -53,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners def toggle_shared_runners
project.toggle!(:shared_runners_enabled) project.toggle!(:shared_runners_enabled)
redirect_to namespace_project_runners_path(project.namespace, project) redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end end
protected protected
......
module Projects
module Settings
class CiCdController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
define_runners_variables
define_secret_variables
define_triggers_variables
define_badges_variables
end
private
def define_runners_variables
@project_runners = @project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners.
assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
def define_secret_variables
@variable = Ci::Variable.new
end
def define_triggers_variables
@triggers = @project.triggers
@trigger = Ci::Trigger.new
end
def define_badges_variables
@ref = params[:ref] || @project.default_branch || 'master'
@badges = [Gitlab::Badge::Build::Status,
Gitlab::Badge::Coverage::Report]
@badges.map! do |badge|
badge.new(@project, @ref).metadata
end
end
end
end
end
...@@ -4,8 +4,7 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -4,8 +4,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
@triggers = project.triggers redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
@trigger = Ci::Trigger.new
end end
def create def create
...@@ -13,17 +12,18 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -13,17 +12,18 @@ class Projects::TriggersController < Projects::ApplicationController
@trigger.save @trigger.save
if @trigger.valid? if @trigger.valid?
redirect_to namespace_project_triggers_path(@project.namespace, @project) redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Trigger was created successfully.'
else else
@triggers = project.triggers.select(&:persisted?) @triggers = project.triggers.select(&:persisted?)
render :index render action: "show"
end end
end end
def destroy def destroy
trigger.destroy trigger.destroy
flash[:alert] = "Trigger removed"
redirect_to namespace_project_triggers_path(@project.namespace, @project) redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end end
private private
......
...@@ -4,7 +4,7 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -4,7 +4,7 @@ class Projects::VariablesController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
@variable = Ci::Variable.new redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end end
def show def show
...@@ -25,9 +25,10 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -25,9 +25,10 @@ class Projects::VariablesController < Projects::ApplicationController
@variable = Ci::Variable.new(project_params) @variable = Ci::Variable.new(project_params)
if @variable.valid? && @project.variables << @variable if @variable.valid? && @project.variables << @variable
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.' flash[:notice] = 'Variables were successfully updated.'
redirect_to namespace_project_settings_ci_cd_path(project.namespace, project)
else else
render action: "index" render "show"
end end
end end
...@@ -35,7 +36,7 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -35,7 +36,7 @@ class Projects::VariablesController < Projects::ApplicationController
@key = @project.variables.find(params[:id]) @key = @project.variables.find(params[:id])
@key.destroy @key.destroy
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully removed.' redirect_to namespace_project_settings_ci_cd_path(project.namespace, project), notice: 'Variable was successfully removed.'
end end
private private
......
...@@ -211,8 +211,12 @@ module GitlabRoutingHelper ...@@ -211,8 +211,12 @@ module GitlabRoutingHelper
def project_settings_integrations_path(project, *args) def project_settings_integrations_path(project, *args)
namespace_project_settings_integrations_path(project.namespace, project, *args) namespace_project_settings_integrations_path(project.namespace, project, *args)
end end
def project_settings_members_path(project, *args) def project_settings_members_path(project, *args)
namespace_project_settings_members_path(project.namespace, project, *args) namespace_project_settings_members_path(project.namespace, project, *args)
end end
def project_settings_ci_cd_path(project, *args)
namespace_project_settings_ci_cd_path(project.namespace, project, *args)
end
end end
...@@ -124,6 +124,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -124,6 +124,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { only_integer: true, greater_than: :housekeeping_full_repack_period } numericality: { only_integer: true, greater_than: :housekeeping_full_repack_period }
validates :terminal_max_session_time,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
...@@ -217,7 +221,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -217,7 +221,8 @@ class ApplicationSetting < ActiveRecord::Base
signin_enabled: Settings.gitlab['signin_enabled'], signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
two_factor_grace_period: 48, two_factor_grace_period: 48,
user_default_external: false user_default_external: false,
terminal_max_session_time: 0
} }
end end
......
...@@ -55,6 +55,7 @@ class Project < ActiveRecord::Base ...@@ -55,6 +55,7 @@ class Project < ActiveRecord::Base
after_destroy :remove_pages after_destroy :remove_pages
# update visibility_level of forks
after_update :update_forks_visibility_level after_update :update_forks_visibility_level
after_update :remove_mirror_repository_reference, after_update :remove_mirror_repository_reference,
if: ->(project) { project.mirror? && project.import_url_updated? } if: ->(project) { project.mirror? && project.import_url_updated? }
......
class KubernetesService < DeploymentService class KubernetesService < DeploymentService
include Gitlab::CurrentSettings
include Gitlab::Kubernetes include Gitlab::Kubernetes
include ReactiveCaching include ReactiveCaching
...@@ -110,7 +111,7 @@ class KubernetesService < DeploymentService ...@@ -110,7 +111,7 @@ class KubernetesService < DeploymentService
pods = data.fetch(:pods, nil) pods = data.fetch(:pods, nil)
filter_pods(pods, app: environment.slug). filter_pods(pods, app: environment.slug).
flat_map { |pod| terminals_for_pod(api_url, namespace, pod) }. flat_map { |pod| terminals_for_pod(api_url, namespace, pod) }.
map { |terminal| add_terminal_auth(terminal, token, ca_pem) } each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end end
end end
...@@ -170,4 +171,12 @@ class KubernetesService < DeploymentService ...@@ -170,4 +171,12 @@ class KubernetesService < DeploymentService
url.to_s url.to_s
end end
def terminal_auth
{
token: token,
ca_pem: ca_pem,
max_session_time: current_application_settings.terminal_max_session_time
}
end
end end
...@@ -103,6 +103,9 @@ class User < ActiveRecord::Base ...@@ -103,6 +103,9 @@ class User < ActiveRecord::Base
has_many :protected_branch_merge_access_levels, dependent: :destroy, class_name: ProtectedBranch::MergeAccessLevel has_many :protected_branch_merge_access_levels, dependent: :destroy, class_name: ProtectedBranch::MergeAccessLevel
has_many :protected_branch_push_access_levels, dependent: :destroy, class_name: ProtectedBranch::PushAccessLevel has_many :protected_branch_push_access_levels, dependent: :destroy, class_name: ProtectedBranch::PushAccessLevel
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
# #
# Validations # Validations
# #
......
...@@ -26,7 +26,7 @@ module Projects ...@@ -26,7 +26,7 @@ module Projects
end end
def project_tree_saver def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared) Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared)
end end
def uploads_saver def uploads_saver
......
...@@ -118,16 +118,18 @@ module SystemNoteService ...@@ -118,16 +118,18 @@ module SystemNoteService
# #
# Example Note text: # Example Note text:
# #
# "Changed estimate of this issue to 3d 5h" # "removed time estimate"
#
# "changed time estimate to 3d 5h"
# #
# Returns the created Note object # Returns the created Note object
def change_time_estimate(noteable, project, author) def change_time_estimate(noteable, project, author)
parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate) parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
body = if noteable.time_estimate == 0 body = if noteable.time_estimate == 0
"Removed time estimate on this #{noteable.human_class_name}" "removed time estimate"
else else
"Changed time estimate of this #{noteable.human_class_name} to #{parsed_time}" "changed time estimate to #{parsed_time}"
end end
create_note(noteable: noteable, project: project, author: author, note: body) create_note(noteable: noteable, project: project, author: author, note: body)
...@@ -142,7 +144,9 @@ module SystemNoteService ...@@ -142,7 +144,9 @@ module SystemNoteService
# #
# Example Note text: # Example Note text:
# #
# "Added 2h 30m of time spent on this issue" # "removed time spent"
#
# "added 2h 30m of time spent"
# #
# Returns the created Note object # Returns the created Note object
...@@ -150,11 +154,11 @@ module SystemNoteService ...@@ -150,11 +154,11 @@ module SystemNoteService
time_spent = noteable.time_spent time_spent = noteable.time_spent
if time_spent == :reset if time_spent == :reset
body = "Removed time spent on this #{noteable.human_class_name}" body = "removed time spent"
else else
parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
action = time_spent > 0 ? 'Added' : 'Subtracted' action = time_spent > 0 ? 'added' : 'subtracted'
body = "#{action} #{parsed_time} of time spent on this #{noteable.human_class_name}" body = "#{action} #{parsed_time} of time spent"
end end
create_note(noteable: noteable, project: project, author: author, note: body) create_note(noteable: noteable, project: project, author: author, note: body)
...@@ -221,7 +225,7 @@ module SystemNoteService ...@@ -221,7 +225,7 @@ module SystemNoteService
end end
def discussion_continued_in_issue(discussion, project, author, issue) def discussion_continued_in_issue(discussion, project, author, issue)
body = "Added #{issue.to_reference} to continue this discussion" body = "created #{issue.to_reference} to continue this discussion"
note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body) note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
note_attributes[:type] = note_attributes.delete(:note_type) note_attributes[:type] = note_attributes.delete(:note_type)
...@@ -260,7 +264,7 @@ module SystemNoteService ...@@ -260,7 +264,7 @@ module SystemNoteService
# #
# Example Note text: # Example Note text:
# #
# "made the issue confidential" # "made the issue confidential"
# #
# Returns the created Note object # Returns the created Note object
def change_issue_confidentiality(issue, project, author) def change_issue_confidentiality(issue, project, author)
...@@ -381,6 +385,7 @@ module SystemNoteService ...@@ -381,6 +385,7 @@ module SystemNoteService
# Returns Boolean # Returns Boolean
def cross_reference_disallowed?(noteable, mentioner) def cross_reference_disallowed?(noteable, mentioner)
return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active? return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
return true if noteable.is_a?(Issuable) && (noteable.try(:closed?) || noteable.try(:merged?))
return false unless mentioner.is_a?(MergeRequest) return false unless mentioner.is_a?(MergeRequest)
return false unless noteable.is_a?(Commit) return false unless noteable.is_a?(Commit)
......
...@@ -573,5 +573,15 @@ ...@@ -573,5 +573,15 @@
.help-block .help-block
Number of Git pushes after which 'git gc' is run. Number of Git pushes after which 'git gc' is run.
%fieldset
%legend Web terminal
.form-group
= f.label :terminal_max_session_time, 'Max session time', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :terminal_max_session_time, class: 'form-control'
.help-block
Maximum time for web terminal websocket connection (in seconds).
Set to 0 for unlimited time.
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
= icon("search", class: "search-icon") = icon("search", class: "search-icon")
.dropdown .dropdown
- toggle_text = 'Search for Namespace' - toggle_text = 'Namespace'
- if params[:namespace_id].present? - if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id]) - namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.path}" - toggle_text = "#{namespace.kind}: #{namespace.path}"
...@@ -37,8 +37,10 @@ ...@@ -37,8 +37,10 @@
= dropdown_filter("Search for Namespace") = dropdown_filter("Search for Namespace")
= dropdown_content = dropdown_content
= dropdown_loading = dropdown_loading
= render 'shared/projects/dropdown'
= button_tag "Search", class: "btn btn-primary btn-search" = link_to new_project_path, class: 'btn btn-new' do
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
%ul.nav-links %ul.nav-links
- opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
...@@ -56,11 +58,6 @@ ...@@ -56,11 +58,6 @@
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public Public
.nav-controls
= render 'shared/projects/dropdown'
= link_to new_project_path, class: 'btn btn-new' do
New Project
.projects-list-holder .projects-list-holder
- if @projects.any? - if @projects.any?
%ul.projects-list.content-list %ul.projects-list.content-list
......
...@@ -18,20 +18,8 @@ ...@@ -18,20 +18,8 @@
Protected Branches Protected Branches
- if @project.feature_available?(:builds, current_user) - if @project.feature_available?(:builds, current_user)
= nav_link(controller: :runners) do = nav_link(controller: :ci_cd) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
%span
Runners
= nav_link(controller: :variables) do
= link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
%span
Variables
= nav_link(controller: :triggers) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
%span
Triggers
= nav_link(controller: :pipelines_settings) do
= link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
%span %span
CI/CD Pipelines CI/CD Pipelines
= nav_link(controller: :push_rules) do = nav_link(controller: :push_rules) do
......
...@@ -31,5 +31,8 @@ ...@@ -31,5 +31,8 @@
= render "projects/boards/components/sidebar" = render "projects/boards/components/sidebar"
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'), %board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
"new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project), "new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project),
"milestone-path" => milestones_filter_dropdown_path,
"label-path" => labels_filter_path,
":issue-link-base" => "issueLinkBase", ":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath" } ":root-path" => "rootPath",
":project-id" => @project.try(:id) }
%div #commit-pipeline-table-view{ data: { endpoint: endpoint } }
- if pipelines.blank? .pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"),
%div "icon_status_canceled" => custom_icon("icon_status_canceled"),
.nothing-here-block No pipelines to show "icon_status_running" => custom_icon("icon_status_running"),
- else "icon_status_skipped" => custom_icon("icon_status_skipped"),
.table-holder.pipelines "icon_status_created" => custom_icon("icon_status_created"),
%table.table.ci-table.js-pipeline-table "icon_status_pending" => custom_icon("icon_status_pending"),
%thead "icon_status_success" => custom_icon("icon_status_success"),
%th.pipeline-status Status "icon_status_failed" => custom_icon("icon_status_failed"),
%th.pipeline-info Pipeline "icon_status_warning" => custom_icon("icon_status_warning"),
%th.pipeline-commit Commit "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
%th.pipeline-stages Stages "stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
%th.pipeline-date "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
%th.pipeline-actions "stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
= render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"),
"stage_icon_status_success" => custom_icon("icon_status_success_borderless"),
"stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"),
"stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"),
"icon_play" => custom_icon("icon_play"),
"icon_timer" => custom_icon("icon_timer"),
"icon_status_manual" => custom_icon("icon_status_manual"),
} }
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('commit_pipelines')
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
= render 'commit_box' = render 'commit_box'
= render 'ci_menu' = render 'ci_menu'
= render 'pipelines_list', pipelines: @pipelines = render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)
...@@ -144,6 +144,7 @@ ...@@ -144,6 +144,7 @@
%hr %hr
= link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
.row.prepend-top-default .row.prepend-top-default
%hr %hr
.row.prepend-top-default .row.prepend-top-default
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
- if @pipelines.any? - if @pipelines.any?
#pipelines.pipelines.tab-pane #pipelines.pipelines.tab-pane
= render "projects/merge_requests/show/pipelines" = render "projects/merge_requests/show/pipelines", endpoint: link_to(url_for(params))
.mr-loading-status .mr-loading-status
= spinner = spinner
......
...@@ -94,7 +94,8 @@ ...@@ -94,7 +94,8 @@
#commits.commits.tab-pane #commits.commits.tab-pane
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane #pipelines.pipelines.tab-pane
-# This tab is always loaded via AJAX - if @pipelines.any?
= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
#diffs.diffs.tab-pane #diffs.diffs.tab-pane
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
......
= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true = render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
...@@ -40,31 +40,27 @@ ...@@ -40,31 +40,27 @@
= link_to ci_lint_path, class: 'btn btn-default' do = link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint %span CI Lint
.content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } } .content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } }
- if @pipelines.blank? .pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"),
%div "icon_status_canceled" => custom_icon("icon_status_canceled"),
.nothing-here-block No pipelines to show "icon_status_running" => custom_icon("icon_status_running"),
- else "icon_status_skipped" => custom_icon("icon_status_skipped"),
.pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"), "icon_status_created" => custom_icon("icon_status_created"),
"icon_status_canceled" => custom_icon("icon_status_canceled"), "icon_status_pending" => custom_icon("icon_status_pending"),
"icon_status_running" => custom_icon("icon_status_running"), "icon_status_success" => custom_icon("icon_status_success"),
"icon_status_skipped" => custom_icon("icon_status_skipped"), "icon_status_failed" => custom_icon("icon_status_failed"),
"icon_status_created" => custom_icon("icon_status_created"), "icon_status_warning" => custom_icon("icon_status_warning"),
"icon_status_pending" => custom_icon("icon_status_pending"), "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
"icon_status_success" => custom_icon("icon_status_success"), "stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
"icon_status_failed" => custom_icon("icon_status_failed"), "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
"icon_status_warning" => custom_icon("icon_status_warning"), "stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"),
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"), "stage_icon_status_success" => custom_icon("icon_status_success_borderless"),
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"),
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"), "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"),
"stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), "icon_play" => custom_icon("icon_play"),
"stage_icon_status_success" => custom_icon("icon_status_success_borderless"), "icon_timer" => custom_icon("icon_timer"),
"stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), "icon_status_manual" => custom_icon("icon_status_manual"),
"stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), } }
"icon_play" => custom_icon("icon_play"),
"icon_timer" => custom_icon("icon_timer"),
"icon_status_manual" => custom_icon("icon_status_manual"),
} }
.vue-pipelines-index .vue-pipelines-index
......
- page_title "CI/CD Pipelines"
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
= page_title CI/CD Pipelines
.col-lg-9 .col-lg-9
= form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f| = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
%fieldset.builds-feature %fieldset.builds-feature
...@@ -95,4 +93,4 @@ ...@@ -95,4 +93,4 @@
%hr %hr
.row.prepend-top-default .row.prepend-top-default
= render partial: 'badge', collection: @badges = render partial: 'projects/pipelines_settings/badge', collection: @badges
- page_title "Runners"
.light.prepend-top-default .light.prepend-top-default
%p %p
A 'Runner' is a process which runs a job. A 'Runner' is a process which runs a job.
...@@ -22,6 +20,6 @@ ...@@ -22,6 +20,6 @@
%p.lead To start serving your jobs you can either add specific Runners to your project or use shared Runners %p.lead To start serving your jobs you can either add specific Runners to your project or use shared Runners
.row .row
.col-sm-6 .col-sm-6
= render 'specific_runners' = render 'projects/runners/specific_runners'
.col-sm-6 .col-sm-6
= render 'shared_runners' = render 'projects/runners/shared_runners'
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
- else - else
%h4.underlined-title Available shared Runners : #{@shared_runners_count} %h4.underlined-title Available shared Runners : #{@shared_runners_count}
%ul.bordered-list.available-shared-runners %ul.bordered-list.available-shared-runners
= render partial: 'runner', collection: @shared_runners, as: :runner = render partial: 'projects/runners/runner', collection: @shared_runners, as: :runner
- if @shared_runners_count > 10 - if @shared_runners_count > 10
.light .light
and #{@shared_runners_count - 10} more... and #{@shared_runners_count - 10} more...
...@@ -20,10 +20,10 @@ ...@@ -20,10 +20,10 @@
- if @project_runners.any? - if @project_runners.any?
%h4.underlined-title Runners activated for this project %h4.underlined-title Runners activated for this project
%ul.bordered-list.activated-specific-runners %ul.bordered-list.activated-specific-runners
= render partial: 'runner', collection: @project_runners, as: :runner = render partial: 'projects/runners/runner', collection: @project_runners, as: :runner
- if @assignable_runners.any? - if @assignable_runners.any?
%h4.underlined-title Available specific runners %h4.underlined-title Available specific runners
%ul.bordered-list.available-specific-runners %ul.bordered-list.available-specific-runners
= render partial: 'runner', collection: @assignable_runners, as: :runner = render partial: 'projects/runners/runner', collection: @assignable_runners, as: :runner
= paginate @assignable_runners, theme: "gitlab" = paginate @assignable_runners, theme: "gitlab"
- page_title "CI/CD Pipelines"
= render 'projects/runners/index'
= render 'projects/variables/index'
= render 'projects/triggers/index'
= render 'projects/pipelines_settings/show'
- return unless current_user
.hidden-xs .hidden-xs
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do
......
- page_title "Triggers"
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-3 .col-lg-3
%h4.prepend-top-0 %h4.prepend-top-0
= page_title Triggers
%p.prepend-top-20 %p.prepend-top-20
Triggers can force a specific branch or tag to get rebuilt with an API call. Triggers can force a specific branch or tag to get rebuilt with an API call.
%p.append-bottom-0 %p.append-bottom-0
...@@ -25,12 +23,12 @@ ...@@ -25,12 +23,12 @@
%th %th
%strong Last used %strong Last used
%th %th
= render partial: 'trigger', collection: @triggers, as: :trigger = render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else - else
%p.settings-message.text-center.append-bottom-default %p.settings-message.text-center.append-bottom-default
No triggers have been created yet. Add one using the button below. No triggers have been created yet. Add one using the button below.
= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f| = form_for @trigger, url: url_for(controller: '/projects/triggers', action: 'create') do |f|
= f.submit "Add trigger", class: 'btn btn-success' = f.submit "Add trigger", class: 'btn btn-success'
.panel-footer .panel-footer
......
- page_title "Variables"
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-3 .col-lg-3
= render "content" = render "projects/variables/content"
.col-lg-9 .col-lg-9
%h5.prepend-top-0 %h5.prepend-top-0
Add a variable Add a variable
= render "form", btn_text: "Add new variable" = render "projects/variables/form", btn_text: "Add new variable"
%hr %hr
%h5.prepend-top-0 %h5.prepend-top-0
Your variables (#{@project.variables.size}) Your variables (#{@project.variables.size})
...@@ -14,5 +12,5 @@ ...@@ -14,5 +12,5 @@
%p.settings-message.text-center.append-bottom-0 %p.settings-message.text-center.append-bottom-0
No variables found, add one with the form above. No variables found, add one with the form above.
- else - else
= render "table" = render "projects/variables/table"
%button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values %button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- personal = params[:personal] - personal = params[:personal]
- archived = params[:archived] - archived = params[:archived]
- namespace_id = params[:namespace_id] - namespace_id = params[:namespace_id]
.dropdown.inline .dropdown
- toggle_text = projects_sort_options_hash[@sort] - toggle_text = projects_sort_options_hash[@sort]
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
......
- return unless current_user
.hidden-xs .hidden-xs
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
...@@ -5,29 +7,27 @@ ...@@ -5,29 +7,27 @@
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
Delete Delete
- if current_user = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do New snippet
New snippet
- if @snippet.submittable_as_spam? && current_user.admin? - if @snippet.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if current_user .visible-xs-block.dropdown
.visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } Options
Options = icon('caret-down')
= icon('caret-down') .dropdown-menu.dropdown-menu-full-width
.dropdown-menu.dropdown-menu-full-width %ul
%ul %li
= link_to new_snippet_path, title: "New snippet" do
New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li %li
= link_to new_snippet_path, title: "New snippet" do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
New snippet Delete
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :update_personal_snippet, @snippet)
%li %li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to edit_snippet_path(@snippet) do
Delete Edit
- if can?(current_user, :update_personal_snippet, @snippet) - if @snippet.submittable_as_spam? && current_user.admin?
%li %li
= link_to edit_snippet_path(@snippet) do = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
---
title: Unify projects search by removing /projects/:search endpoint
merge_request: 8877
author:
--- ---
title: 19164 Add settings dropdown to mobile screens title: Remove hover animation from row elements
merge_request: merge_request:
author: author:
---
title: Fixes hover cursor on pipeline pagenation
merge_request: 9003
author:
---
title: Support non-ASCII characters in GFM autocomplete
merge_request: 8729
author:
--- ---
title: Check public snippets for spam title: 27240 Make progress bars consistent
merge_request: merge_request:
author: author:
---
title: Fix project name label's for reference in project settings
merge_request: 8795
author:
---
title: Fix filtered search user autocomplete for gitlab instances that are hosted
on a subdirectory
merge_request: 8891
author:
---
title: Fixes flickering of avatar border in mention dropdown
merge_request: 8950
author:
---
title: 'API: Fix file downloading'
merge_request: Robert Schilling
author: 8267
---
title: 'API: Remove deprecated ''expires_at'' from project snippets'
merge_request: 8723
author: Robert Schilling
---
title: use babel to transpile all non-vendor javascript assets regardless of file
extension
merge_request: 8988
author:
---
title: Don't delete assigned MRs/issues when user is deleted
merge_request:
author:
---
title: Use vue.js Pipelines table in commit and merge request view
merge_request: 8844
author:
---
title: Fixed services form cancel not redirecting back the integrations settings view
merge_request: 8843
author:
---
title: Fix filtering usernames with multiple words
merge_request: 8851
author:
---
title: Preserve backward compatibility CI/CD and disallow setting `coverage` regexp in global context
merge_request: 8981
author:
---
title: Add ability to export project inherited group members to Import/Export
merge_request: 8923
author:
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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