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
lib/gitlab/diff/position_tracer.rb
app/controllers/projects/approver_groups_controller.rb
app/controllers/projects/approvers_controller.rb
app/policies/project_policy.rb
\ No newline at end of file
app/policies/project_policy.rb
......@@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own
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)
- Add caching of droplab ajax requests. !8725
......
......@@ -15,6 +15,7 @@
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Stewardship][#stewardship]
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
......@@ -230,6 +231,21 @@ for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is
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
We welcome merge requests with fixes and improvements to GitLab code, tests,
......
......@@ -33,7 +33,7 @@ core team members will mention this person.
### Merge request coaching
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/.
......@@ -79,47 +79,47 @@ not be merged into any stable branches.
### 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
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
Thanks for your interest in GitLab. We don't use the issue tracker for support
requests and configuration questions. Please check our
\[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)
[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)
for more information.
### 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
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
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
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
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
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?
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.
### Only accepting merge requests with green tests
......@@ -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
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
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/
[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');
require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/modal/index');
require('./vue_resource_interceptor');
require('../vue_shared/vue_resource_interceptor');
$(() => {
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 */
require('./tabs');
const modalFilters = require('./filters');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalHeader = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
},
......@@ -31,6 +45,7 @@ require('./tabs');
},
components: {
'modal-tabs': gl.issueBoards.ModalTabs,
modalFilters,
},
template: `
<div>
......@@ -51,6 +66,11 @@ require('./tabs');
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<modal-filters
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input
placeholder="Search issues..."
class="form-control"
......
......@@ -27,6 +27,18 @@ require('./empty_state');
type: String,
required: true,
},
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
......@@ -52,17 +64,27 @@ require('./empty_state');
this.issuesCount = false;
}
},
filter: {
handler() {
this.loadIssues(true);
},
deep: true,
},
},
methods: {
searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true);
}, 500),
loadIssues(clearIssues = false) {
return gl.boardService.getBacklog({
if (!this.showAddIssuesModal) return false;
const queryData = Object.assign({}, this.filter, {
search: this.searchTerm,
page: this.page,
per: this.perPage,
}).then((res) => {
});
return gl.boardService.getBacklog(queryData).then((res) => {
const data = res.json();
if (clearIssues) {
......@@ -112,8 +134,13 @@ require('./empty_state');
class="add-issues-modal"
v-if="showAddIssuesModal">
<div class="add-issues-container">
<modal-header></modal-header>
<modal-header
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-header>
<modal-list
:image="blankStateImage"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
v-if="!loading && showList"></modal-list>
......
......@@ -14,6 +14,10 @@
type: String,
required: true,
},
image: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
......@@ -110,6 +114,19 @@
<section
class="add-issues-list add-issues-list-columns"
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
v-for="group in groupedIssues"
class="add-issues-list-column">
......
......@@ -18,6 +18,17 @@
page: 1,
perPage: 50,
};
this.setDefaultFilter();
}
setDefaultFilter() {
this.store.filter = {
author_id: '',
assignee_id: '',
milestone_title: '',
label_name: [],
};
}
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 @@
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:commit:pipelines':
new gl.MiniPipelineGraph({
container: '.js-pipeline-table',
});
break;
case 'projects:commits:show':
case 'projects:activity':
shortcut_handler = new ShortcutsNavigation();
......@@ -269,7 +264,7 @@
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
break;
case 'projects:variables:index':
case 'projects:ci_cd:show':
new gl.ProjectVariables();
break;
case 'ci:lints:create':
......
......@@ -4,7 +4,7 @@
window.Vue = require('vue');
window.timeago = require('vendor/timeago');
require('../../lib/utils/text_utility');
require('../../vue_common_component/commit');
require('../../vue_shared/components/commit');
require('./environment_actions');
require('./environment_external_url');
require('./environment_stop');
......
window.Vue = require('vue');
require('./stores/environments_store');
require('./components/environment');
require('./vue_resource_interceptor');
require('../vue_shared/vue_resource_interceptor');
$(() => {
window.gl = window.gl || {};
......
......@@ -8,7 +8,7 @@ require('./filtered_search_dropdown');
super(droplab, dropdown, input, filter);
this.config = {
droplabAjaxFilter: {
endpoint: '/autocomplete/users.json',
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
......
......@@ -4,10 +4,17 @@
(function() {
this.LabelsSelect = (function() {
function LabelsSelect() {
var _this;
function LabelsSelect(els) {
var _this, $els;
_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;
$dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter');
......@@ -324,7 +331,7 @@
multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e, isMarking) {
var isIssueIndex, isMRIndex, page;
var isIssueIndex, isMRIndex, page, boardsModel;
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
......@@ -346,22 +353,31 @@
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) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
boardsModel['label_name'] = [];
}
else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title);
boardsModel['label_name'].push(label.title);
}
else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name'];
var filters = boardsModel['label_name'];
filters = filters.filter(function (filteredLabel) {
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();
return;
}
......
......@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.
File.basename(file, '.js').sub(/^mode-/, '')
end
%>
// Lazy-load configuration when ace.edit is called
(function() {
window.gon = window.gon || {};
var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
ace.config.set('basePath', basePath);
var basePath;
var ace = window.ace;
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.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 %>
// configure paths for all mode modules
// configure paths for all mode modules
<% 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 %>
// restore original method
ace.edit = edit;
return ace.edit.apply(ace, arguments);
};
})();
......@@ -230,5 +230,16 @@
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);
}).call(this);
......@@ -61,7 +61,6 @@ require('./flash');
constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false;
this.fixedLayoutPref = null;
......@@ -116,10 +115,6 @@ require('./flash');
$.scrollTo('.merge-request-details .merge-request-tabs', {
offset: -navBarHeight,
});
} else if (action === 'pipelines') {
this.loadPipelines($target.attr('href'));
this.expandView();
this.resetViewContainer();
} else {
this.expandView();
this.resetViewContainer();
......@@ -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
//
// status - Boolean, true to show, false to hide
......
......@@ -5,13 +5,20 @@
(function() {
this.MilestoneSelect = (function() {
function MilestoneSelect(currentProject) {
var _this;
function MilestoneSelect(currentProject, els) {
var _this, $els;
if (currentProject != null) {
_this = this;
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;
$dropdown = $(dropdown);
projectId = $dropdown.data('project-id');
......@@ -108,7 +115,7 @@
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(selected, $el, e) {
var data, isIssueIndex, isMRIndex, page;
var data, isIssueIndex, isMRIndex, page, boardsStore;
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index');
......@@ -116,9 +123,19 @@
e.preventDefault();
return;
}
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
gl.issueBoards.BoardsStore.updateFiltersUrl();
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
!$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();
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
if (selected.name != null) {
......
......@@ -8,7 +8,8 @@
slice = [].slice;
this.UsersSelect = (function() {
function UsersSelect(currentUser) {
function UsersSelect(currentUser, els) {
var $els;
this.users = bind(this.users, this);
this.user = bind(this.user, this);
this.usersPath = "/autocomplete/users.json";
......@@ -20,7 +21,14 @@
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) {
var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
......@@ -193,7 +201,9 @@
selectedId = user.id;
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;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl();
......
/* eslint-disable no-param-reassign */
/* global Vue, VueResource, gl */
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../vue_common_component/commit');
require('../vue_pagination/index');
require('../boards/vue_resource_interceptor');
require('./status');
require('./store');
require('./pipeline_url');
require('./stage');
require('./stages');
require('./pipeline_actions');
require('./time_ago');
require('../lib/utils/common_utils');
require('../vue_shared/vue_resource_interceptor');
require('./pipelines');
(() => {
const project = document.querySelector('.pipelines');
const entry = document.querySelector('.vue-pipelines-index');
const svgs = document.querySelector('.pipeline-svgs');
$(() => new Vue({
el: document.querySelector('.vue-pipelines-index'),
if (!entry) return null;
return new Vue({
el: entry,
data: {
data() {
const project = document.querySelector('.pipelines');
const svgs = document.querySelector('.pipeline-svgs').dataset;
// Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgs);
return {
scope: project.dataset.url,
store: new gl.PipelineStore(),
svgs: svgs.dataset,
},
components: {
'vue-pipelines': gl.VuePipelines,
},
template: `
<vue-pipelines
:scope='scope'
:store='store'
:svgs='svgs'
>
</vue-pipelines>
`,
});
})();
svgs: svgsObject,
};
},
components: {
'vue-pipelines': gl.VuePipelines,
},
template: `
<vue-pipelines
:scope='scope'
:store='store'
:svgs='svgs'
>
</vue-pipelines>
`,
}));
......@@ -50,9 +50,9 @@
<button
v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
data-toggle="dropdown"
title="Artifacts"
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts"
>
<i class="fa fa-download" aria-hidden="true"></i>
......@@ -81,8 +81,7 @@
data-placement="top"
data-toggle="dropdown"
:href='pipeline.retry_path'
aria-label="Retry"
>
aria-label="Retry">
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
<a
......@@ -94,8 +93,7 @@
data-placement="top"
data-toggle="dropdown"
:href='pipeline.cancel_path'
aria-label="Cancel"
>
aria-label="Cancel">
<i class="fa fa-remove" aria-hidden="true"></i>
</a>
</div>
......
/* global Vue, gl */
/* 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.VuePipelines = Vue.extend({
components: {
runningPipeline: gl.VueRunningPipeline,
pipelineActions: gl.VuePipelineActions,
stages: gl.VueStages,
commit: gl.CommitComponent,
pipelineUrl: gl.VuePipelineUrl,
pipelineHead: gl.VuePipelineHead,
glPagination: gl.VueGlPagination,
statusScope: gl.VueStatusScope,
timeAgo: gl.VueTimeAgo,
'gl-pagination': gl.VueGlPagination,
'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
},
data() {
return {
pipelines: [],
......@@ -38,87 +38,29 @@
change(pagenum, apiScope) {
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: `
<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>
</div>
<div class="table-holder" v-if='pipelines.length'>
<table class="table ci-table">
<thead>
<tr>
<th class="pipeline-status">Status</th>
<th class="pipeline-info">Pipeline</th>
<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 class="blank-state blank-state-no-icon"
v-if="!pageRequest && pipelines.length === 0">
<h2 class="blank-state-title js-blank-state-title">
No pipelines to show
</h2>
</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>
<gl-pagination
v-if='pageInfo.total > pageInfo.perPage'
v-if='!pageRequest && pipelines.length && pageInfo.total > pageInfo.perPage'
:pagenum='pagenum'
:change='change'
:count='count.all'
......
......@@ -15,7 +15,7 @@
required: true,
},
svgs: {
type: DOMStringMap,
type: Object,
required: true,
},
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');
gl.PipelineStore = class {
fetchDataLoop(Vue, pageNum, url, apiScope) {
this.pageRequest = true;
const updatePipelineNums = (count) => {
const { all } = count;
const running = count.running_or_pending;
......@@ -41,16 +42,18 @@ require('../vue_realtime_listener');
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();
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => {
this.$children
.filter(e => e.$options._componentTag === 'time-ago')
.forEach(e => e.changeTime());
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);
};
......
/* global Vue, gl */
/* eslint-disable no-param-reassign */
window.Vue = require('vue');
require('../lib/utils/datetime_utility');
((gl) => {
gl.VueTimeAgo = Vue.extend({
data() {
......
/* 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 */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next((response) => {
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 @@
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) {
// Default values
$property: all;
......@@ -116,11 +137,13 @@ a {
@include transition(background-color, color, border);
}
.tree-table td,
.well-list > li {
@include transition(background-color, border-color);
}
.stage-nav-item {
@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 {
.progress {
margin-bottom: 0;
margin-top: 4px;
box-shadow: none;
background-color: $border-gray-light;
}
}
......
......@@ -159,6 +159,7 @@
.cur {
.avatar {
border: 1px solid $white-light;
@include disableAllAnimation;
}
}
}
......@@ -6,8 +6,22 @@
.pagination {
padding: 0;
a {
cursor: pointer;
}
.separator,
.separator:hover {
a {
cursor: default;
background-color: $gray-light;
padding: $gl-vert-padding;
}
}
}
.gap,
.gap:hover {
background-color: $gray-light;
......
......@@ -423,6 +423,13 @@
flex: 1;
margin-top: 0;
&.add-issues-empty-state-filter {
-webkit-flex-direction: column;
flex-direction: column;
-webkit-justify-content: center;
justify-content: center;
}
> .row {
width: 100%;
margin: auto 0;
......@@ -450,6 +457,14 @@
.add-issues-search {
display: -webkit-flex;
display: flex;
.form-control {
margin-left: auto;
@media (min-width: $screen-sm-min) {
max-width: 200px;
}
}
}
.add-issues-list-column {
......@@ -520,3 +535,24 @@
line-height: 15px;
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 @@
.commit-row-description {
font-size: 14px;
border-left: 1px solid $white-normal;
padding: 10px 15px;
margin: 10px 0;
background: $gray-light;
......
......@@ -147,6 +147,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:user_default_external,
:user_oauth_applications,
:version_check_enabled,
:terminal_max_session_time,
disabled_oauth_sign_in_sources: [],
import_sources: [],
......
......@@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController
format.json do
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
end
end
......
......@@ -219,19 +219,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
format.json do
render json: {
html: view_to_html_string('projects/merge_requests/show/_pipelines'),
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
}
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
end
end
end
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
def new_diffs
......
......@@ -2,20 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
@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
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params)
end
def update
if @project.update_attributes(update_params)
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
render 'show'
end
......
......@@ -5,11 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
@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)
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
def edit
......@@ -53,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners
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
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
layout 'project_settings'
def index
@triggers = project.triggers
@trigger = Ci::Trigger.new
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
def create
......@@ -13,17 +12,18 @@ class Projects::TriggersController < Projects::ApplicationController
@trigger.save
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
@triggers = project.triggers.select(&:persisted?)
render :index
render action: "show"
end
end
def 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
private
......
......@@ -4,7 +4,7 @@ class Projects::VariablesController < Projects::ApplicationController
layout 'project_settings'
def index
@variable = Ci::Variable.new
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
def show
......@@ -25,9 +25,10 @@ class Projects::VariablesController < Projects::ApplicationController
@variable = Ci::Variable.new(project_params)
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
render action: "index"
render "show"
end
end
......@@ -35,7 +36,7 @@ class Projects::VariablesController < Projects::ApplicationController
@key = @project.variables.find(params[:id])
@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
private
......
......@@ -211,8 +211,12 @@ module GitlabRoutingHelper
def project_settings_integrations_path(project, *args)
namespace_project_settings_integrations_path(project.namespace, project, *args)
end
def project_settings_members_path(project, *args)
namespace_project_settings_members_path(project.namespace, project, *args)
end
def project_settings_ci_cd_path(project, *args)
namespace_project_settings_ci_cd_path(project.namespace, project, *args)
end
end
......@@ -124,6 +124,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
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|
unless value.nil?
value.each do |level|
......@@ -217,7 +221,8 @@ class ApplicationSetting < ActiveRecord::Base
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
two_factor_grace_period: 48,
user_default_external: false
user_default_external: false,
terminal_max_session_time: 0
}
end
......
......@@ -55,6 +55,7 @@ class Project < ActiveRecord::Base
after_destroy :remove_pages
# update visibility_level of forks
after_update :update_forks_visibility_level
after_update :remove_mirror_repository_reference,
if: ->(project) { project.mirror? && project.import_url_updated? }
......
class KubernetesService < DeploymentService
include Gitlab::CurrentSettings
include Gitlab::Kubernetes
include ReactiveCaching
......@@ -110,7 +111,7 @@ class KubernetesService < DeploymentService
pods = data.fetch(:pods, nil)
filter_pods(pods, app: environment.slug).
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
......@@ -170,4 +171,12 @@ class KubernetesService < DeploymentService
url.to_s
end
def terminal_auth
{
token: token,
ca_pem: ca_pem,
max_session_time: current_application_settings.terminal_max_session_time
}
end
end
......@@ -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_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
#
......
......@@ -26,7 +26,7 @@ module Projects
end
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
def uploads_saver
......
......@@ -118,16 +118,18 @@ module SystemNoteService
#
# 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
def change_time_estimate(noteable, project, author)
parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
body = if noteable.time_estimate == 0
"Removed time estimate on this #{noteable.human_class_name}"
"removed time estimate"
else
"Changed time estimate of this #{noteable.human_class_name} to #{parsed_time}"
"changed time estimate to #{parsed_time}"
end
create_note(noteable: noteable, project: project, author: author, note: body)
......@@ -142,7 +144,9 @@ module SystemNoteService
#
# 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
......@@ -150,11 +154,11 @@ module SystemNoteService
time_spent = noteable.time_spent
if time_spent == :reset
body = "Removed time spent on this #{noteable.human_class_name}"
body = "removed time spent"
else
parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
action = time_spent > 0 ? 'Added' : 'Subtracted'
body = "#{action} #{parsed_time} of time spent on this #{noteable.human_class_name}"
action = time_spent > 0 ? 'added' : 'subtracted'
body = "#{action} #{parsed_time} of time spent"
end
create_note(noteable: noteable, project: project, author: author, note: body)
......@@ -221,7 +225,7 @@ module SystemNoteService
end
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[:type] = note_attributes.delete(:note_type)
......@@ -260,7 +264,7 @@ module SystemNoteService
#
# Example Note text:
#
# "made the issue confidential"
# "made the issue confidential"
#
# Returns the created Note object
def change_issue_confidentiality(issue, project, author)
......@@ -381,6 +385,7 @@ module SystemNoteService
# Returns Boolean
def cross_reference_disallowed?(noteable, mentioner)
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 noteable.is_a?(Commit)
......
......@@ -573,5 +573,15 @@
.help-block
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
= f.submit 'Save', class: 'btn btn-save'
......@@ -27,7 +27,7 @@
= icon("search", class: "search-icon")
.dropdown
- toggle_text = 'Search for Namespace'
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.path}"
......@@ -37,8 +37,10 @@
= dropdown_filter("Search for Namespace")
= dropdown_content
= dropdown_loading
= button_tag "Search", class: "btn btn-primary btn-search"
= render 'shared/projects/dropdown'
= 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
- opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
......@@ -56,11 +58,6 @@
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public
.nav-controls
= render 'shared/projects/dropdown'
= link_to new_project_path, class: 'btn btn-new' do
New Project
.projects-list-holder
- if @projects.any?
%ul.projects-list.content-list
......
......@@ -18,20 +18,8 @@
Protected Branches
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' 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
= nav_link(controller: :ci_cd) do
= link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
%span
CI/CD Pipelines
= nav_link(controller: :push_rules) do
......
......@@ -31,5 +31,8 @@
= render "projects/boards/components/sidebar"
%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),
"milestone-path" => milestones_filter_dropdown_path,
"label-path" => labels_filter_path,
":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath" }
":root-path" => "rootPath",
":project-id" => @project.try(:id) }
%div
- if pipelines.blank?
%div
.nothing-here-block No pipelines to show
- else
.table-holder.pipelines
%table.table.ci-table.js-pipeline-table
%thead
%th.pipeline-status Status
%th.pipeline-info Pipeline
%th.pipeline-commit Commit
%th.pipeline-stages Stages
%th.pipeline-date
%th.pipeline-actions
= render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false
#commit-pipeline-table-view{ data: { endpoint: endpoint } }
.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"),
"icon_status_canceled" => custom_icon("icon_status_canceled"),
"icon_status_running" => custom_icon("icon_status_running"),
"icon_status_skipped" => custom_icon("icon_status_skipped"),
"icon_status_created" => custom_icon("icon_status_created"),
"icon_status_pending" => custom_icon("icon_status_pending"),
"icon_status_success" => custom_icon("icon_status_success"),
"icon_status_failed" => custom_icon("icon_status_failed"),
"icon_status_warning" => custom_icon("icon_status_warning"),
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
"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 @@
= render 'commit_box'
= 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 @@
%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"
= f.submit 'Save changes', class: "btn btn-save"
.row.prepend-top-default
%hr
.row.prepend-top-default
......
......@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX
- if @pipelines.any?
#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
= spinner
......
......@@ -94,7 +94,8 @@
#commits.commits.tab-pane
-# This tab is always loaded via AJAX
#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
-# 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 @@
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
.content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } }
- if @pipelines.blank?
%div
.nothing-here-block No pipelines to show
- else
.pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"),
"icon_status_canceled" => custom_icon("icon_status_canceled"),
"icon_status_running" => custom_icon("icon_status_running"),
"icon_status_skipped" => custom_icon("icon_status_skipped"),
"icon_status_created" => custom_icon("icon_status_created"),
"icon_status_pending" => custom_icon("icon_status_pending"),
"icon_status_success" => custom_icon("icon_status_success"),
"icon_status_failed" => custom_icon("icon_status_failed"),
"icon_status_warning" => custom_icon("icon_status_warning"),
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
"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"),
} }
.pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"),
"icon_status_canceled" => custom_icon("icon_status_canceled"),
"icon_status_running" => custom_icon("icon_status_running"),
"icon_status_skipped" => custom_icon("icon_status_skipped"),
"icon_status_created" => custom_icon("icon_status_created"),
"icon_status_pending" => custom_icon("icon_status_pending"),
"icon_status_success" => custom_icon("icon_status_success"),
"icon_status_failed" => custom_icon("icon_status_failed"),
"icon_status_warning" => custom_icon("icon_status_warning"),
"stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
"stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
"stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
"stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
"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"),
} }
.vue-pipelines-index
......
- page_title "CI/CD Pipelines"
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
CI/CD Pipelines
.col-lg-9
= form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
%fieldset.builds-feature
......@@ -95,4 +93,4 @@
%hr
.row.prepend-top-default
= render partial: 'badge', collection: @badges
= render partial: 'projects/pipelines_settings/badge', collection: @badges
- page_title "Runners"
.light.prepend-top-default
%p
A 'Runner' is a process which runs a job.
......@@ -22,6 +20,6 @@
%p.lead To start serving your jobs you can either add specific Runners to your project or use shared Runners
.row
.col-sm-6
= render 'specific_runners'
= render 'projects/runners/specific_runners'
.col-sm-6
= render 'shared_runners'
= render 'projects/runners/shared_runners'
......@@ -22,7 +22,7 @@
- else
%h4.underlined-title Available shared Runners : #{@shared_runners_count}
%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
.light
and #{@shared_runners_count - 10} more...
......@@ -20,10 +20,10 @@
- if @project_runners.any?
%h4.underlined-title Runners activated for this project
%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?
%h4.underlined-title 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"
- 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
- if can?(current_user, :update_project_snippet, @snippet)
= 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
.col-lg-3
%h4.prepend-top-0
= page_title
Triggers
%p.prepend-top-20
Triggers can force a specific branch or tag to get rebuilt with an API call.
%p.append-bottom-0
......@@ -25,12 +23,12 @@
%th
%strong Last used
%th
= render partial: 'trigger', collection: @triggers, as: :trigger
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
- else
%p.settings-message.text-center.append-bottom-default
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'
.panel-footer
......
- page_title "Variables"
.row.prepend-top-default.append-bottom-default
.col-lg-3
= render "content"
= render "projects/variables/content"
.col-lg-9
%h5.prepend-top-0
Add a variable
= render "form", btn_text: "Add new variable"
= render "projects/variables/form", btn_text: "Add new variable"
%hr
%h5.prepend-top-0
Your variables (#{@project.variables.size})
......@@ -14,5 +12,5 @@
%p.settings-message.text-center.append-bottom-0
No variables found, add one with the form above.
- else
= render "table"
= render "projects/variables/table"
%button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
......@@ -2,7 +2,7 @@
- personal = params[:personal]
- archived = params[:archived]
- namespace_id = params[:namespace_id]
.dropdown.inline
.dropdown
- toggle_text = projects_sort_options_hash[@sort]
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
......
- return unless current_user
.hidden-xs
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
......@@ -5,29 +7,27 @@
- 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
Delete
- if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
- 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'
- if current_user
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
%li
= link_to new_snippet_path, title: "New snippet" do
New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= link_to new_snippet_path, title: "New snippet" do
New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
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:
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:
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