Merge remote-tracking branch 'origin/master' into zj/gitlab-ce-zj-auto-devops-table
This commit is contained in:
commit
12ddc28f84
|
@ -40,6 +40,7 @@ stages:
|
|||
- test
|
||||
- post-test
|
||||
- pages
|
||||
- post-cleanup
|
||||
|
||||
# Predefined scopes
|
||||
.dedicated-runner: &dedicated-runner
|
||||
|
@ -153,8 +154,7 @@ stages:
|
|||
- master@gitlab/gitlabhq
|
||||
- master@gitlab/gitlab-ee
|
||||
|
||||
# Trigger a package build on omnibus-gitlab repository
|
||||
|
||||
# Trigger a package build in omnibus-gitlab repository
|
||||
build-package:
|
||||
image: ruby:2.3-alpine
|
||||
before_script: []
|
||||
|
@ -166,11 +166,47 @@ build-package:
|
|||
cache: {}
|
||||
when: manual
|
||||
script:
|
||||
- scripts/trigger-build
|
||||
- scripts/trigger-build-omnibus
|
||||
only:
|
||||
- //@gitlab-org/gitlab-ce
|
||||
- //@gitlab-org/gitlab-ee
|
||||
|
||||
# Review docs base
|
||||
.review-docs: &review-docs
|
||||
image: ruby:2.4-alpine
|
||||
before_script: []
|
||||
services: []
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
cache: {}
|
||||
when: manual
|
||||
only:
|
||||
- branches
|
||||
|
||||
# Trigger a docs build in gitlab-docs
|
||||
# Useful to preview the docs changes live
|
||||
review-docs-deploy:
|
||||
<<: *review-docs
|
||||
stage: build
|
||||
environment:
|
||||
name: review-docs/$CI_COMMIT_REF_NAME
|
||||
on_stop: review-docs-cleanup
|
||||
script:
|
||||
- gem install gitlab --no-doc
|
||||
- scripts/trigger-build-docs deploy
|
||||
|
||||
# Cleanup remote environment of gitlab-docs
|
||||
review-docs-cleanup:
|
||||
<<: *review-docs
|
||||
stage: post-cleanup
|
||||
environment:
|
||||
name: review-docs/$CI_COMMIT_REF_NAME
|
||||
action: stop
|
||||
script:
|
||||
- gem install gitlab --no-doc
|
||||
- scripts/trigger-build-docs cleanup
|
||||
|
||||
# Retrieve knapsack and rspec_flaky reports
|
||||
retrieve-tests-metadata:
|
||||
<<: *tests-metadata-state
|
||||
|
|
|
@ -5,7 +5,7 @@ By submitting code as an individual you agree to the
|
|||
By submitting code as an entity you agree to the
|
||||
[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
|
||||
|
||||
_This notice should stay as the first item in the CONTRIBUTING.MD file._
|
||||
_This notice should stay as the first item in the CONTRIBUTING.md file._
|
||||
|
||||
---
|
||||
|
||||
|
@ -21,7 +21,7 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
|
|||
- [Workflow labels](#workflow-labels)
|
||||
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
|
||||
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
|
||||
- [Team labels (~CI, ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-ci-discussion-edge-platform-etc)
|
||||
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-ci-discussion-edge-platform-etc)
|
||||
- [Priority labels (~Deliverable and ~Stretch)](#priority-labels-deliverable-and-stretch)
|
||||
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
|
||||
- [Implement design & UI elements](#implement-design--ui-elements)
|
||||
|
@ -115,7 +115,7 @@ Most issues will have labels for at least one of the following:
|
|||
|
||||
- Type: ~"feature proposal", ~bug, ~customer, etc.
|
||||
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
|
||||
- Team: ~CI, ~Discussion, ~Edge, ~Platform, etc.
|
||||
- Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.
|
||||
- Priority: ~Deliverable, ~Stretch
|
||||
|
||||
All labels, their meaning and priority are defined on the
|
||||
|
@ -157,13 +157,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api,
|
|||
|
||||
Subject labels are always all-lowercase.
|
||||
|
||||
### Team labels (~CI, ~Discussion, ~Edge, ~Platform, etc.)
|
||||
### Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)
|
||||
|
||||
Team labels specify what team is responsible for this issue.
|
||||
Assigning a team label makes sure issues get the attention of the appropriate
|
||||
people.
|
||||
|
||||
The current team labels are ~Build, ~CI, ~Discussion, ~Documentation, ~Edge,
|
||||
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
|
||||
~Geo, ~Gitaly, ~Platform, ~Prometheus, ~Release, and ~"UX".
|
||||
|
||||
The descriptions on the [labels page][labels-page] explain what falls under the
|
||||
|
@ -217,11 +217,11 @@ After adding the ~"Accepting Merge Requests" label, we try to estimate the
|
|||
[weight](#issue-weight) of the issue. We use issue weight to let contributors
|
||||
know how difficult the issue is. Additionally:
|
||||
|
||||
- We advertise [~"Accepting Merge Requests" issues with weight < 5][up-for-grabs]
|
||||
- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
|
||||
as suitable for people that have never contributed to GitLab before on the
|
||||
[Up For Grabs campaign](http://up-for-grabs.net)
|
||||
- We encourage people that have never contributed to any open source project to
|
||||
look for [~"Accepting Merge Requests" issues with a weight of 1][firt-timers]
|
||||
look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
|
||||
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
|
||||
[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
|
||||
|
|
|
@ -6,7 +6,8 @@ const Api = {
|
|||
namespacesPath: '/api/:version/namespaces.json',
|
||||
groupProjectsPath: '/api/:version/groups/:id/projects.json',
|
||||
projectsPath: '/api/:version/projects.json',
|
||||
labelsPath: '/:namespace_path/:project_path/labels',
|
||||
projectLabelsPath: '/:namespace_path/:project_path/labels',
|
||||
groupLabelsPath: '/groups/:namespace_path/labels',
|
||||
licensePath: '/api/:version/templates/licenses/:key',
|
||||
gitignorePath: '/api/:version/templates/gitignores/:key',
|
||||
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
|
||||
|
@ -74,9 +75,16 @@ const Api = {
|
|||
},
|
||||
|
||||
newLabel(namespacePath, projectPath, data, callback) {
|
||||
const url = Api.buildUrl(Api.labelsPath)
|
||||
.replace(':namespace_path', namespacePath)
|
||||
.replace(':project_path', projectPath);
|
||||
let url;
|
||||
|
||||
if (projectPath) {
|
||||
url = Api.buildUrl(Api.projectLabelsPath)
|
||||
.replace(':namespace_path', namespacePath)
|
||||
.replace(':project_path', projectPath);
|
||||
} else {
|
||||
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
|
||||
}
|
||||
|
||||
return $.ajax({
|
||||
url,
|
||||
type: 'POST',
|
||||
|
|
|
@ -53,7 +53,8 @@ $(() => {
|
|||
data: {
|
||||
state: Store.state,
|
||||
loading: true,
|
||||
endpoint: $boardApp.dataset.endpoint,
|
||||
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
|
||||
listsEndpoint: $boardApp.dataset.listsEndpoint,
|
||||
boardId: $boardApp.dataset.boardId,
|
||||
disabled: $boardApp.dataset.disabled === 'true',
|
||||
issueLinkBase: $boardApp.dataset.issueLinkBase,
|
||||
|
@ -68,7 +69,13 @@ $(() => {
|
|||
},
|
||||
},
|
||||
created () {
|
||||
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
||||
gl.boardService = new BoardService({
|
||||
boardsEndpoint: this.boardsEndpoint,
|
||||
listsEndpoint: this.listsEndpoint,
|
||||
bulkUpdatePath: this.bulkUpdatePath,
|
||||
boardId: this.boardId,
|
||||
});
|
||||
Store.rootPath = this.boardsEndpoint;
|
||||
|
||||
this.filterManager = new FilteredSearchBoards(Store.filter, true);
|
||||
this.filterManager.setup();
|
||||
|
@ -112,19 +119,21 @@ $(() => {
|
|||
gl.IssueBoardsSearch = new Vue({
|
||||
el: document.getElementById('js-add-list'),
|
||||
data: {
|
||||
filters: Store.state.filters
|
||||
filters: Store.state.filters,
|
||||
},
|
||||
mounted () {
|
||||
gl.issueBoards.newListDropdownInit();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gl.IssueBoardsModalAddBtn = new Vue({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
el: document.getElementById('js-add-issues-btn'),
|
||||
data: {
|
||||
modal: ModalStore.store,
|
||||
store: Store.state,
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
store: Store.state,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
disabled() {
|
||||
|
@ -133,6 +142,9 @@ $(() => {
|
|||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
if (!this.store) {
|
||||
return true;
|
||||
}
|
||||
return !this.store.lists.filter(list => !list.preset).length;
|
||||
},
|
||||
tooltipTitle() {
|
||||
|
@ -145,7 +157,7 @@ $(() => {
|
|||
},
|
||||
methods: {
|
||||
updateTooltip() {
|
||||
const $tooltip = $(this.$el);
|
||||
const $tooltip = $(this.$refs.addIssuesButton);
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.disabled) {
|
||||
|
@ -165,16 +177,19 @@ $(() => {
|
|||
this.updateTooltip();
|
||||
},
|
||||
template: `
|
||||
<button
|
||||
class="btn btn-create pull-right prepend-left-10"
|
||||
type="button"
|
||||
data-placement="bottom"
|
||||
:class="{ 'disabled': disabled }"
|
||||
:title="tooltipTitle"
|
||||
:aria-disabled="disabled"
|
||||
@click="openModal">
|
||||
Add issues
|
||||
</button>
|
||||
<div class="board-extra-actions">
|
||||
<button
|
||||
class="btn btn-create prepend-left-10"
|
||||
type="button"
|
||||
data-placement="bottom"
|
||||
ref="addIssuesButton"
|
||||
:class="{ 'disabled': disabled }"
|
||||
:title="tooltipTitle"
|
||||
:aria-disabled="disabled"
|
||||
@click="openModal">
|
||||
Add issues
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
|||
this.showIssueForm = !this.showIssueForm;
|
||||
},
|
||||
onScroll() {
|
||||
if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) {
|
||||
if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
||||
this.loadNextPage();
|
||||
}
|
||||
},
|
||||
|
@ -165,11 +165,9 @@ export default {
|
|||
v-if="loading">
|
||||
<loading-icon />
|
||||
</div>
|
||||
<transition name="slide-down">
|
||||
<board-new-issue
|
||||
:list="list"
|
||||
v-if="list.type !== 'closed' && showIssueForm"/>
|
||||
</transition>
|
||||
<board-new-issue
|
||||
:list="list"
|
||||
v-if="list.type !== 'closed' && showIssueForm"/>
|
||||
<ul
|
||||
class="board-list"
|
||||
v-show="!loading"
|
||||
|
|
|
@ -6,7 +6,10 @@ const Store = gl.issueBoards.BoardsStore;
|
|||
export default {
|
||||
name: 'BoardNewIssue',
|
||||
props: {
|
||||
list: Object,
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -64,10 +64,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
|||
return this.issue.assignees.length > this.numberOverLimit;
|
||||
},
|
||||
cardUrl() {
|
||||
return `${this.issueLinkBase}/${this.issue.id}`;
|
||||
return `${this.issueLinkBase}/${this.issue.iid}`;
|
||||
},
|
||||
issueId() {
|
||||
return `#${this.issue.id}`;
|
||||
if (this.issue.iid) {
|
||||
return `#${this.issue.iid}`;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
showLabelFooter() {
|
||||
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
|
||||
|
@ -143,7 +146,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
|||
:title="issue.title">{{ issue.title }}</a>
|
||||
<span
|
||||
class="card-number"
|
||||
v-if="issue.id"
|
||||
v-if="issueId"
|
||||
>
|
||||
{{ issueId }}
|
||||
</span>
|
||||
|
|
|
@ -29,7 +29,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
|
|||
const firstListIndex = 1;
|
||||
const list = this.modal.selectedList || this.state.lists[firstListIndex];
|
||||
const selectedIssues = ModalStore.getSelectedIssues();
|
||||
const issueIds = selectedIssues.map(issue => issue.globalId);
|
||||
const issueIds = selectedIssues.map(issue => issue.id);
|
||||
|
||||
// Post the data to the backend
|
||||
gl.boardService.bulkUpdate(issueIds, {
|
||||
|
|
|
@ -27,7 +27,7 @@ gl.issueBoards.newListDropdownInit = () => {
|
|||
|
||||
$this.glDropdown({
|
||||
data(term, callback) {
|
||||
$.get($this.attr('data-labels'))
|
||||
$.get($this.attr('data-list-labels-path'))
|
||||
.then((resp) => {
|
||||
callback(resp);
|
||||
});
|
||||
|
|
|
@ -18,17 +18,33 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
issueUpdate: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
updateUrl() {
|
||||
return this.issueUpdate;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeIssue() {
|
||||
const issue = this.issue;
|
||||
const lists = issue.getLists();
|
||||
const labelIds = lists.map(list => list.label.id);
|
||||
|
||||
// Post the remove data
|
||||
gl.boardService.bulkUpdate([issue.globalId], {
|
||||
remove_label_ids: labelIds,
|
||||
}).catch(() => {
|
||||
const listLabelIds = lists.map(list => list.label.id);
|
||||
let labelIds = this.issue.labels
|
||||
.map(label => label.id)
|
||||
.filter(id => !listLabelIds.includes(id));
|
||||
if (labelIds.length === 0) {
|
||||
labelIds = [''];
|
||||
}
|
||||
const data = {
|
||||
issue: {
|
||||
label_ids: labelIds,
|
||||
},
|
||||
};
|
||||
Vue.http.patch(this.updateUrl, data).catch(() => {
|
||||
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
||||
|
||||
lists.forEach((list) => {
|
||||
|
|
|
@ -7,8 +7,8 @@ import Vue from 'vue';
|
|||
|
||||
class ListIssue {
|
||||
constructor (obj, defaultAvatar) {
|
||||
this.globalId = obj.id;
|
||||
this.id = obj.iid;
|
||||
this.id = obj.id;
|
||||
this.iid = obj.iid;
|
||||
this.title = obj.title;
|
||||
this.confidential = obj.confidential;
|
||||
this.dueDate = obj.due_date;
|
||||
|
|
|
@ -4,6 +4,7 @@ class ListLabel {
|
|||
constructor (obj) {
|
||||
this.id = obj.id;
|
||||
this.title = obj.title;
|
||||
this.type = obj.type;
|
||||
this.color = obj.color;
|
||||
this.textColor = obj.text_color;
|
||||
this.description = obj.description;
|
||||
|
|
|
@ -110,11 +110,13 @@ class List {
|
|||
return gl.boardService.newIssue(this.id, issue)
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
issue.id = data.iid;
|
||||
issue.id = data.id;
|
||||
issue.iid = data.iid;
|
||||
issue.project = data.project;
|
||||
|
||||
if (this.issuesSize > 1) {
|
||||
const moveBeforeIid = this.issues[1].id;
|
||||
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
|
||||
const moveBeforeId = this.issues[1].id;
|
||||
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -126,19 +128,19 @@ class List {
|
|||
}
|
||||
|
||||
addIssue (issue, listFrom, newIndex) {
|
||||
let moveBeforeIid = null;
|
||||
let moveAfterIid = null;
|
||||
let moveBeforeId = null;
|
||||
let moveAfterId = null;
|
||||
|
||||
if (!this.findIssue(issue.id)) {
|
||||
if (newIndex !== undefined) {
|
||||
this.issues.splice(newIndex, 0, issue);
|
||||
|
||||
if (this.issues[newIndex - 1]) {
|
||||
moveBeforeIid = this.issues[newIndex - 1].id;
|
||||
moveBeforeId = this.issues[newIndex - 1].id;
|
||||
}
|
||||
|
||||
if (this.issues[newIndex + 1]) {
|
||||
moveAfterIid = this.issues[newIndex + 1].id;
|
||||
moveAfterId = this.issues[newIndex + 1].id;
|
||||
}
|
||||
} else {
|
||||
this.issues.push(issue);
|
||||
|
@ -151,30 +153,30 @@ class List {
|
|||
if (listFrom) {
|
||||
this.issuesSize += 1;
|
||||
|
||||
this.updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid);
|
||||
this.updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveIssue (issue, oldIndex, newIndex, moveBeforeIid, moveAfterIid) {
|
||||
moveIssue (issue, oldIndex, newIndex, moveBeforeId, moveAfterId) {
|
||||
this.issues.splice(oldIndex, 1);
|
||||
this.issues.splice(newIndex, 0, issue);
|
||||
|
||||
gl.boardService.moveIssue(issue.id, null, null, moveBeforeIid, moveAfterIid)
|
||||
gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId)
|
||||
.catch(() => {
|
||||
// TODO: handle request error
|
||||
});
|
||||
}
|
||||
|
||||
updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) {
|
||||
gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid)
|
||||
updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) {
|
||||
gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId)
|
||||
.catch(() => {
|
||||
// TODO: handle request error
|
||||
});
|
||||
}
|
||||
|
||||
findIssue (id) {
|
||||
return this.issues.filter(issue => issue.id === id)[0];
|
||||
return this.issues.find(issue => issue.id === id);
|
||||
}
|
||||
|
||||
removeIssue (removeIssue) {
|
||||
|
|
|
@ -3,21 +3,21 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
class BoardService {
|
||||
constructor (root, bulkUpdatePath, boardId) {
|
||||
this.boards = Vue.resource(`${root}{/id}.json`, {}, {
|
||||
constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
|
||||
this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, {
|
||||
issues: {
|
||||
method: 'GET',
|
||||
url: `${root}/${boardId}/issues.json`
|
||||
url: `${gon.relative_url_root}/boards/${boardId}/issues.json`,
|
||||
}
|
||||
});
|
||||
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
|
||||
this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, {
|
||||
generate: {
|
||||
method: 'POST',
|
||||
url: `${root}/${boardId}/lists/generate.json`
|
||||
url: `${listsEndpoint}/generate.json`
|
||||
}
|
||||
});
|
||||
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}, {
|
||||
this.issue = Vue.resource(`${gon.relative_url_root}/boards/${boardId}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, {
|
||||
bulkUpdate: {
|
||||
method: 'POST',
|
||||
url: bulkUpdatePath,
|
||||
|
@ -60,12 +60,12 @@ class BoardService {
|
|||
return this.issues.get(data);
|
||||
}
|
||||
|
||||
moveIssue (id, from_list_id = null, to_list_id = null, move_before_iid = null, move_after_iid = null) {
|
||||
moveIssue (id, from_list_id = null, to_list_id = null, move_before_id = null, move_after_id = null) {
|
||||
return this.issue.update({ id }, {
|
||||
from_list_id,
|
||||
to_list_id,
|
||||
move_before_iid,
|
||||
move_after_iid,
|
||||
move_before_id,
|
||||
move_after_id,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -21,8 +21,10 @@ let headerHeight = 50;
|
|||
|
||||
export const getHeaderHeight = () => headerHeight;
|
||||
|
||||
export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only');
|
||||
|
||||
export const canShowActiveSubItems = (el) => {
|
||||
if (el.classList.contains('active') && (sidebar && !sidebar.classList.contains('sidebar-icons-only'))) {
|
||||
if (el.classList.contains('active') && !isSidebarCollapsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -100,12 +102,13 @@ export const moveSubItemsToPosition = (el, subItems) => {
|
|||
|
||||
export const showSubLevelItems = (el) => {
|
||||
const subItems = el.querySelector('.sidebar-sub-level-items');
|
||||
const isIconOnly = subItems && subItems.classList.contains('is-fly-out-only');
|
||||
|
||||
if (!canShowSubItems() || !canShowActiveSubItems(el)) return;
|
||||
|
||||
el.classList.add(IS_OVER_CLASS);
|
||||
|
||||
if (!subItems) return;
|
||||
if (!subItems || (!isSidebarCollapsed() && isIconOnly)) return;
|
||||
|
||||
subItems.style.display = 'block';
|
||||
el.classList.add(IS_SHOWING_FLY_OUT_CLASS);
|
||||
|
|
|
@ -73,7 +73,7 @@ class Issue {
|
|||
$(document).trigger('issuable:change', isClosed);
|
||||
this.toggleCloseReopenButton(isClosed);
|
||||
|
||||
let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
|
||||
let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
|
||||
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
|
||||
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
@import "framework/flash";
|
||||
@import "framework/forms";
|
||||
@import "framework/gfm";
|
||||
@import "framework/gitlab-theme";
|
||||
@import "framework/header";
|
||||
@import "framework/highlight";
|
||||
@import "framework/issue_box";
|
||||
|
|
|
@ -412,11 +412,12 @@ table {
|
|||
|
||||
.gl-accessibility {
|
||||
&:focus {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
padding: 0 10px;
|
||||
clip: auto;
|
||||
text-decoration: none;
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
width: auto;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 200;
|
||||
z-index: 300;
|
||||
min-width: 240px;
|
||||
max-width: 500px;
|
||||
margin-top: 2px;
|
||||
|
@ -837,17 +837,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.navbar-gitlab {
|
||||
li.header-projects,
|
||||
li.header-more,
|
||||
li.header-new,
|
||||
li.header-user {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
header.navbar-gitlab .dropdown {
|
||||
.dropdown-menu,
|
||||
.dropdown-menu-nav {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
|
||||
@include new-style-dropdown('.js-namespace-select + ');
|
||||
|
||||
header.navbar-gitlab-new .header-content .dropdown-menu.projects-dropdown-menu {
|
||||
padding: 0;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
display: table;
|
||||
left: -50px;
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.projects-dropdown-container {
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
/**
|
||||
* Styles the GitLab application with a specific color theme
|
||||
*/
|
||||
|
||||
@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) {
|
||||
// Header
|
||||
|
||||
header.navbar-gitlab-new {
|
||||
background: linear-gradient(to right, $color-900, $color-800);
|
||||
|
||||
.navbar-collapse {
|
||||
color: $color-200;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
.navbar-toggle {
|
||||
border-left: 1px solid lighten($color-700, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-sub-nav,
|
||||
.navbar-nav {
|
||||
> li {
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
background-color: rgba($color-200, .2);
|
||||
}
|
||||
|
||||
&.active > a,
|
||||
&.dropdown.open > a {
|
||||
color: $color-900;
|
||||
background-color: $color-alternate;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.line-separator {
|
||||
border-left: 1px solid rgba($color-200, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-sub-nav {
|
||||
color: $color-200;
|
||||
}
|
||||
|
||||
.nav {
|
||||
> li {
|
||||
color: $color-200;
|
||||
|
||||
> a {
|
||||
svg {
|
||||
fill: $color-200;
|
||||
}
|
||||
|
||||
&.header-user-dropdown-toggle {
|
||||
.header-user-avatar {
|
||||
border-color: $color-200;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
background-color: rgba($color-200, .2);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active > a,
|
||||
&.dropdown.open > a {
|
||||
color: $color-900;
|
||||
background-color: $color-alternate;
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $color-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.impersonated-user,
|
||||
.impersonated-user:hover {
|
||||
svg {
|
||||
fill: $color-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
> a {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba($color-200, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
form {
|
||||
background-color: rgba($color-200, .2);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($color-200, .3);
|
||||
}
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
color: $color-100;
|
||||
background-color: rgba($color-200, .1);
|
||||
border-right: 1px solid $color-800;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: rgba($color-200, .8);
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.search-icon,
|
||||
.clear-icon {
|
||||
color: rgba($color-200, .8);
|
||||
}
|
||||
}
|
||||
|
||||
&.search-active {
|
||||
form {
|
||||
background-color: $white-light;
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.search-icon {
|
||||
color: rgba($color-200, .8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-sign-in {
|
||||
background-color: $color-100;
|
||||
color: $color-900;
|
||||
}
|
||||
|
||||
|
||||
// Sidebar
|
||||
.nav-sidebar li.active {
|
||||
box-shadow: inset 4px 0 0 $color-700;
|
||||
|
||||
> a {
|
||||
color: $color-900;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $color-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
&.ui_indigo {
|
||||
@include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light);
|
||||
}
|
||||
|
||||
&.ui_dark {
|
||||
@include gitlab-theme($theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, $theme-gray-800, $theme-gray-900, $white-light);
|
||||
}
|
||||
|
||||
&.ui_blue {
|
||||
@include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light);
|
||||
}
|
||||
|
||||
&.ui_green {
|
||||
@include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light);
|
||||
}
|
||||
|
||||
&.ui_light {
|
||||
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700);
|
||||
|
||||
header.navbar-gitlab-new {
|
||||
background: $theme-gray-100;
|
||||
box-shadow: 0 2px 0 0 $border-color;
|
||||
|
||||
.logo-text svg {
|
||||
fill: $theme-gray-900;
|
||||
}
|
||||
|
||||
.navbar-sub-nav,
|
||||
.navbar-nav {
|
||||
> li {
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
color: $theme-gray-900;
|
||||
}
|
||||
|
||||
&.active > a {
|
||||
color: $white-light;
|
||||
|
||||
&:hover {
|
||||
color: $white-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
.navbar-toggle,
|
||||
.navbar-toggle:hover {
|
||||
color: $theme-gray-700;
|
||||
border-left: 1px solid $theme-gray-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
form {
|
||||
background-color: $white-light;
|
||||
box-shadow: inset 0 0 0 1px $border-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $white-light;
|
||||
box-shadow: inset 0 0 0 1px $blue-100;
|
||||
|
||||
.location-badge {
|
||||
box-shadow: inset 0 0 0 1px $blue-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.search-icon {
|
||||
color: $theme-gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
color: $theme-gray-700;
|
||||
box-shadow: inset 0 0 0 1px $border-color;
|
||||
background-color: $nav-badge-bg;
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sidebar li.active {
|
||||
> a {
|
||||
color: $theme-gray-900;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $theme-gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -111,7 +111,6 @@ header {
|
|||
svg {
|
||||
height: 16px;
|
||||
width: 23px;
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,8 @@ $red-700: #a62d19;
|
|||
$red-800: #8b2615;
|
||||
$red-900: #711e11;
|
||||
|
||||
// GitLab themes
|
||||
|
||||
$indigo-50: #f7f7ff;
|
||||
$indigo-100: #ebebfa;
|
||||
$indigo-200: #d1d1f0;
|
||||
|
@ -86,6 +88,43 @@ $indigo-800: #393982;
|
|||
$indigo-900: #292961;
|
||||
$indigo-950: #1a1a40;
|
||||
|
||||
$theme-gray-50: #fafafa;
|
||||
$theme-gray-100: #f2f2f2;
|
||||
$theme-gray-200: #dfdfdf;
|
||||
$theme-gray-300: #cccccc;
|
||||
$theme-gray-400: #bababa;
|
||||
$theme-gray-500: #a7a7a7;
|
||||
$theme-gray-600: #949494;
|
||||
$theme-gray-700: #707070;
|
||||
$theme-gray-800: #4f4f4f;
|
||||
$theme-gray-900: #2e2e2e;
|
||||
$theme-gray-950: #1f1f1f;
|
||||
|
||||
$theme-blue-50: #f4f8fc;
|
||||
$theme-blue-100: #e6edf5;
|
||||
$theme-blue-200: #c8d7e6;
|
||||
$theme-blue-300: #97b3cf;
|
||||
$theme-blue-400: #648cb4;
|
||||
$theme-blue-500: #4a79a8;
|
||||
$theme-blue-600: #3e6fa0;
|
||||
$theme-blue-700: #305c88;
|
||||
$theme-blue-800: #25496e;
|
||||
$theme-blue-900: #1a3652;
|
||||
$theme-blue-950: #0f2235;
|
||||
|
||||
$theme-green-50: #f2faf6;
|
||||
$theme-green-100: #e4f3ea;
|
||||
$theme-green-200: #c0dfcd;
|
||||
$theme-green-300: #8ac2a1;
|
||||
$theme-green-400: #52a274;
|
||||
$theme-green-500: #35935c;
|
||||
$theme-green-600: #288a50;
|
||||
$theme-green-700: #1c7441;
|
||||
$theme-green-800: #145d33;
|
||||
$theme-green-900: #0d4524;
|
||||
$theme-green-950: #072d16;
|
||||
|
||||
|
||||
$black: #000;
|
||||
$black-transparent: rgba(0, 0, 0, 0.3);
|
||||
$almost-black: #242424;
|
||||
|
|
|
@ -9,10 +9,20 @@
|
|||
|
||||
header.navbar-gitlab-new {
|
||||
color: $white-light;
|
||||
background: linear-gradient(to right, $indigo-900, $indigo-800);
|
||||
border-bottom: 0;
|
||||
min-height: $new-navbar-height;
|
||||
|
||||
.logo-text {
|
||||
line-height: initial;
|
||||
|
||||
svg {
|
||||
width: 55px;
|
||||
height: 14px;
|
||||
margin: 0;
|
||||
fill: $white-light;
|
||||
}
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
@ -38,10 +48,10 @@ header.navbar-gitlab-new {
|
|||
|
||||
img {
|
||||
height: 28px;
|
||||
margin-right: 10px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
> a {
|
||||
a {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -54,22 +64,6 @@ header.navbar-gitlab-new {
|
|||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
line-height: initial;
|
||||
|
||||
svg {
|
||||
width: 55px;
|
||||
height: 14px;
|
||||
margin: 0;
|
||||
fill: $white-light;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba($indigo-200, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +100,6 @@ header.navbar-gitlab-new {
|
|||
|
||||
.navbar-collapse {
|
||||
padding-left: 0;
|
||||
color: $indigo-200;
|
||||
box-shadow: 0;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
|
@ -132,7 +125,6 @@ header.navbar-gitlab-new {
|
|||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: currentColor;
|
||||
border-left: 1px solid lighten($indigo-700, 10%);
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
|
@ -167,63 +159,49 @@ header.navbar-gitlab-new {
|
|||
will-change: color;
|
||||
margin: 4px 2px;
|
||||
padding: 6px 8px;
|
||||
color: $indigo-200;
|
||||
height: 32px;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $indigo-200;
|
||||
}
|
||||
|
||||
&.header-user-dropdown-toggle {
|
||||
margin-left: 2px;
|
||||
|
||||
.header-user-avatar {
|
||||
border-color: $indigo-200;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
opacity: 1;
|
||||
color: $white-light;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&.header-user-dropdown-toggle {
|
||||
.header-user-avatar {
|
||||
border-color: $white-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-new-dropdown-toggle {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
opacity: 1;
|
||||
color: $white-light;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
background-color: rgba($indigo-200, .2);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&.header-user-dropdown-toggle {
|
||||
.header-user-avatar {
|
||||
border-color: $white-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.impersonated-user,
|
||||
.impersonated-user:hover {
|
||||
margin-right: 1px;
|
||||
background-color: $white-light;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
svg {
|
||||
fill: $indigo-900;
|
||||
}
|
||||
}
|
||||
|
||||
.impersonation-btn,
|
||||
|
@ -241,8 +219,6 @@ header.navbar-gitlab-new {
|
|||
|
||||
&.active > a,
|
||||
&.dropdown.open > a {
|
||||
color: $indigo-900;
|
||||
background-color: $white-light;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
|
@ -256,7 +232,6 @@ header.navbar-gitlab-new {
|
|||
display: -webkit-flex;
|
||||
display: flex;
|
||||
margin: 0 0 0 6px;
|
||||
color: $indigo-200;
|
||||
|
||||
.dropdown-chevron {
|
||||
position: relative;
|
||||
|
@ -274,17 +249,6 @@ header.navbar-gitlab-new {
|
|||
text-decoration: none;
|
||||
outline: 0;
|
||||
color: $white-light;
|
||||
background-color: rgba($indigo-200, .2);
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.active > a,
|
||||
&.dropdown.open > a {
|
||||
color: $indigo-900;
|
||||
background-color: $white-light;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
|
@ -309,7 +273,6 @@ header.navbar-gitlab-new {
|
|||
}
|
||||
|
||||
&.line-separator {
|
||||
border-left: 1px solid rgba($indigo-200, .2);
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
@ -339,17 +302,14 @@ header.navbar-gitlab-new {
|
|||
height: 32px;
|
||||
border: 0;
|
||||
border-radius: $border-radius-default;
|
||||
background-color: rgba($indigo-200, .2);
|
||||
transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($indigo-200, .3);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.search-active form {
|
||||
background-color: $white-light;
|
||||
box-shadow: none;
|
||||
|
||||
.search-input {
|
||||
|
@ -377,43 +337,26 @@ header.navbar-gitlab-new {
|
|||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: rgba($indigo-200, .8);
|
||||
transition: color ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
font-size: 12px;
|
||||
color: $indigo-100;
|
||||
background-color: rgba($indigo-200, .1);
|
||||
will-change: color;
|
||||
margin: -4px 4px -4px -4px;
|
||||
line-height: 25px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 2px 0 0 2px;
|
||||
border-right: 1px solid $indigo-800;
|
||||
height: 32px;
|
||||
transition: border-color ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.search-icon,
|
||||
.clear-icon {
|
||||
color: rgba($indigo-200, .8);
|
||||
}
|
||||
}
|
||||
|
||||
&.search-active {
|
||||
.location-badge {
|
||||
color: $gl-text-color;
|
||||
background-color: $nav-badge-bg;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.search-icon {
|
||||
color: rgba($indigo-200, .8);
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
color: $white-light;
|
||||
}
|
||||
|
@ -517,8 +460,6 @@ header.navbar-gitlab-new {
|
|||
|
||||
.btn-sign-in {
|
||||
margin-top: 3px;
|
||||
background-color: $indigo-100;
|
||||
color: $indigo-900;
|
||||
font-weight: $gl-font-weight-bold;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -106,11 +106,8 @@ $new-sidebar-collapsed-width: 50px;
|
|||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.badge,
|
||||
.sidebar-context-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.badge:not(.fly-out-badge),
|
||||
.sidebar-context-title,
|
||||
.nav-item-name {
|
||||
display: none;
|
||||
}
|
||||
|
@ -118,6 +115,10 @@ $new-sidebar-collapsed-width: 50px;
|
|||
.sidebar-top-level-items > li > a {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.fly-out-top-item {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.nav-sidebar-expanded {
|
||||
|
@ -154,16 +155,9 @@ $new-sidebar-collapsed-width: 50px;
|
|||
}
|
||||
|
||||
li.active {
|
||||
box-shadow: inset 4px 0 0 $active-border;
|
||||
|
||||
> a {
|
||||
color: $active-color;
|
||||
font-weight: $gl-font-weight-bold;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $active-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
|
@ -179,6 +173,10 @@ $new-sidebar-collapsed-width: 50px;
|
|||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.fly-out-top-item {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sidebar-inner-scroll {
|
||||
|
@ -249,7 +247,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
left: $new-sidebar-width;
|
||||
min-width: 150px;
|
||||
margin-top: -1px;
|
||||
padding: 8px 1px;
|
||||
padding: 4px 1px;
|
||||
background-color: $white-light;
|
||||
box-shadow: 2px 1px 3px $dropdown-shadow-color;
|
||||
border: 1px solid $gray-darker;
|
||||
|
@ -270,6 +268,13 @@ $new-sidebar-collapsed-width: 50px;
|
|||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
margin: 4px -1px;
|
||||
padding: 0;
|
||||
background-color: $dropdown-divider-color;
|
||||
}
|
||||
|
||||
> .active {
|
||||
box-shadow: none;
|
||||
|
||||
|
@ -309,7 +314,7 @@ $new-sidebar-collapsed-width: 50px;
|
|||
font-weight: $gl-font-weight-bold;
|
||||
}
|
||||
|
||||
.sidebar-sub-level-items {
|
||||
.sidebar-sub-level-items:not(.is-fly-out-only) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -407,6 +412,19 @@ $new-sidebar-collapsed-width: 50px;
|
|||
}
|
||||
}
|
||||
|
||||
.fly-out-top-item {
|
||||
> a {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fly-out-badge {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.fly-out-top-item-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// Mobile nav
|
||||
|
||||
|
|
|
@ -117,13 +117,12 @@
|
|||
}
|
||||
|
||||
.board-title {
|
||||
position: initial;
|
||||
padding: 0;
|
||||
border-bottom: 0;
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
transform: rotate(90deg) translate(25px, 0);
|
||||
transform: rotate(90deg) translate(35px, 10px);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,11 +150,18 @@
|
|||
}
|
||||
|
||||
.board-header {
|
||||
border-top-left-radius: $border-radius-default;
|
||||
border-top-right-radius: $border-radius-default;
|
||||
position: relative;
|
||||
|
||||
&.has-border {
|
||||
&.has-border::before {
|
||||
border-top: 3px solid;
|
||||
border-color: inherit;
|
||||
border-top-left-radius: $border-radius-default;
|
||||
border-top-right-radius: $border-radius-default;
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: calc(100% + 2px);
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin-top: -1px;
|
||||
margin-right: -1px;
|
||||
margin-left: -1px;
|
||||
|
@ -176,12 +182,16 @@
|
|||
}
|
||||
|
||||
.board-title {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: $gl-padding;
|
||||
padding-bottom: ($gl-padding + 3px);
|
||||
padding: 12px $gl-padding;
|
||||
font-size: 1em;
|
||||
border-bottom: 1px solid $border-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.board-title-text {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.board-delete {
|
||||
|
@ -221,43 +231,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.slide-down-enter {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.slide-down-enter-active {
|
||||
transition: transform $fade-in-duration;
|
||||
|
||||
+ .board-list {
|
||||
transform: translateY(-136px);
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-down-enter-to {
|
||||
+ .board-list {
|
||||
transform: translateY(0);
|
||||
transition: transform $fade-in-duration ease;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-down-leave {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.slide-down-leave-active {
|
||||
transition: all $fade-in-duration;
|
||||
transform: translateY(-136px);
|
||||
|
||||
+ .board-list {
|
||||
transition: transform $fade-in-duration ease;
|
||||
transform: translateY(-136px);
|
||||
}
|
||||
}
|
||||
|
||||
.board-list-component {
|
||||
height: calc(100% - 49px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.board-list {
|
||||
|
@ -429,7 +406,7 @@
|
|||
}
|
||||
|
||||
.board-new-issue-form {
|
||||
z-index: 1;
|
||||
z-index: 4;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,67 @@
|
|||
@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) {
|
||||
.one {
|
||||
background-color: $color-1;
|
||||
border-top-left-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
.two {
|
||||
background-color: $color-2;
|
||||
border-top-right-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
.three {
|
||||
background-color: $color-3;
|
||||
border-bottom-left-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
.four {
|
||||
background-color: $color-4;
|
||||
border-bottom-right-radius: $border-radius-default;
|
||||
}
|
||||
}
|
||||
|
||||
.application-theme {
|
||||
label {
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview {
|
||||
font-size: 0;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.indigo {
|
||||
@include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
@include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600);
|
||||
}
|
||||
|
||||
&.light {
|
||||
@include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100);
|
||||
}
|
||||
|
||||
&.blue {
|
||||
@include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500);
|
||||
}
|
||||
|
||||
&.green {
|
||||
@include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-row {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.quadrant {
|
||||
display: inline-block;
|
||||
height: 50px;
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.syntax-theme {
|
||||
label {
|
||||
margin-right: 20px;
|
||||
|
|
|
@ -166,7 +166,7 @@ input[type="checkbox"]:hover {
|
|||
.dropdown-menu {
|
||||
transition-duration: 100ms, 75ms;
|
||||
transition-delay: 75ms, 100ms;
|
||||
transform: translateY(13px);
|
||||
transform: translateY(7px);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,6 +211,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
:provider,
|
||||
:remember_me,
|
||||
:skype,
|
||||
:theme_id,
|
||||
:twitter,
|
||||
:username,
|
||||
:website_url
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
module Boards
|
||||
class ApplicationController < ::ApplicationController
|
||||
respond_to :json
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
||||
|
||||
private
|
||||
|
||||
def board
|
||||
@board ||= Board.find(params[:board_id])
|
||||
end
|
||||
|
||||
def board_parent
|
||||
@board_parent ||= board.parent
|
||||
end
|
||||
|
||||
def record_not_found(exception)
|
||||
render json: { error: exception.message }, status: :not_found
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,90 @@
|
|||
module Boards
|
||||
class IssuesController < Boards::ApplicationController
|
||||
include BoardsResponses
|
||||
|
||||
before_action :authorize_read_issue, only: [:index]
|
||||
before_action :authorize_create_issue, only: [:create]
|
||||
before_action :authorize_update_issue, only: [:update]
|
||||
skip_before_action :authenticate_user!, only: [:index]
|
||||
|
||||
def index
|
||||
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
|
||||
issues = issues.page(params[:page]).per(params[:per] || 20)
|
||||
make_sure_position_is_set(issues)
|
||||
|
||||
render json: {
|
||||
issues: serialize_as_json(issues.preload(:project)),
|
||||
size: issues.total_count
|
||||
}
|
||||
end
|
||||
|
||||
def create
|
||||
service = Boards::Issues::CreateService.new(board_parent, project, current_user, issue_params)
|
||||
issue = service.execute
|
||||
|
||||
if issue.valid?
|
||||
render json: serialize_as_json(issue)
|
||||
else
|
||||
render json: issue.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = Boards::Issues::MoveService.new(board_parent, current_user, move_params)
|
||||
|
||||
if service.execute(issue)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_sure_position_is_set(issues)
|
||||
issues.each do |issue|
|
||||
issue.move_to_end && issue.save unless issue.relative_position
|
||||
end
|
||||
end
|
||||
|
||||
def issue
|
||||
@issue ||= issues_finder.execute.find(params[:id])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.merge(board_id: params[:board_id], id: params[:list_id])
|
||||
.reject { |_, value| value.nil? }
|
||||
end
|
||||
|
||||
def issues_finder
|
||||
IssuesFinder.new(current_user, project_id: board_parent.id)
|
||||
end
|
||||
|
||||
def project
|
||||
board_parent
|
||||
end
|
||||
|
||||
def move_params
|
||||
params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_id, :move_after_id)
|
||||
end
|
||||
|
||||
def issue_params
|
||||
params.require(:issue)
|
||||
.permit(:title, :milestone_id, :project_id)
|
||||
.merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
resource.as_json(
|
||||
labels: true,
|
||||
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
|
||||
include: {
|
||||
project: { only: [:id, :path] },
|
||||
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||
milestone: { only: [:id, :title] }
|
||||
},
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,75 @@
|
|||
module Boards
|
||||
class ListsController < Boards::ApplicationController
|
||||
include BoardsResponses
|
||||
|
||||
before_action :authorize_admin_list, only: [:create, :update, :destroy, :generate]
|
||||
before_action :authorize_read_list, only: [:index]
|
||||
skip_before_action :authenticate_user!, only: [:index]
|
||||
|
||||
def index
|
||||
lists = Boards::Lists::ListService.new(board.parent, current_user).execute(board)
|
||||
|
||||
render json: serialize_as_json(lists)
|
||||
end
|
||||
|
||||
def create
|
||||
list = Boards::Lists::CreateService.new(board.parent, current_user, list_params).execute(board)
|
||||
|
||||
if list.valid?
|
||||
render json: serialize_as_json(list)
|
||||
else
|
||||
render json: list.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
list = board.lists.movable.find(params[:id])
|
||||
service = Boards::Lists::MoveService.new(board_parent, current_user, move_params)
|
||||
|
||||
if service.execute(list)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
list = board.lists.destroyable.find(params[:id])
|
||||
service = Boards::Lists::DestroyService.new(board_parent, current_user)
|
||||
|
||||
if service.execute(list)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
service = Boards::Lists::GenerateService.new(board_parent, current_user)
|
||||
|
||||
if service.execute(board)
|
||||
render json: serialize_as_json(board.lists.movable)
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def list_params
|
||||
params.require(:list).permit(:label_id)
|
||||
end
|
||||
|
||||
def move_params
|
||||
params.require(:list).permit(:position)
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
resource.as_json(
|
||||
only: [:id, :list_type, :position],
|
||||
methods: [:title],
|
||||
label: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
module BoardsResponses
|
||||
def authorize_read_list
|
||||
authorize_action_for!(board.parent, :read_list)
|
||||
end
|
||||
|
||||
def authorize_read_issue
|
||||
authorize_action_for!(board.parent, :read_issue)
|
||||
end
|
||||
|
||||
def authorize_update_issue
|
||||
authorize_action_for!(issue, :admin_issue)
|
||||
end
|
||||
|
||||
def authorize_create_issue
|
||||
authorize_action_for!(project, :admin_issue)
|
||||
end
|
||||
|
||||
def authorize_admin_list
|
||||
authorize_action_for!(board.parent, :admin_list)
|
||||
end
|
||||
|
||||
def authorize_action_for!(resource, ability)
|
||||
return render_403 unless can?(current_user, ability, resource)
|
||||
end
|
||||
|
||||
def respond_with_boards
|
||||
respond_with(@boards)
|
||||
end
|
||||
|
||||
def respond_with_board
|
||||
respond_with(@board)
|
||||
end
|
||||
|
||||
def respond_with(resource)
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: serialize_as_json(resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
|
|||
:color_scheme_id,
|
||||
:layout,
|
||||
:dashboard,
|
||||
:project_view
|
||||
:project_view,
|
||||
:theme_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
module Projects
|
||||
module Boards
|
||||
class ApplicationController < Projects::ApplicationController
|
||||
respond_to :json
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
||||
|
||||
private
|
||||
|
||||
def record_not_found(exception)
|
||||
render json: { error: exception.message }, status: :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,94 +0,0 @@
|
|||
module Projects
|
||||
module Boards
|
||||
class IssuesController < Boards::ApplicationController
|
||||
before_action :authorize_read_issue!, only: [:index]
|
||||
before_action :authorize_create_issue!, only: [:create]
|
||||
before_action :authorize_update_issue!, only: [:update]
|
||||
|
||||
def index
|
||||
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
|
||||
issues = issues.page(params[:page]).per(params[:per] || 20)
|
||||
make_sure_position_is_set(issues)
|
||||
|
||||
render json: {
|
||||
issues: serialize_as_json(issues),
|
||||
size: issues.total_count
|
||||
}
|
||||
end
|
||||
|
||||
def create
|
||||
service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
|
||||
issue = service.execute
|
||||
|
||||
if issue.valid?
|
||||
render json: serialize_as_json(issue)
|
||||
else
|
||||
render json: issue.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = ::Boards::Issues::MoveService.new(project, current_user, move_params)
|
||||
|
||||
if service.execute(issue)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_sure_position_is_set(issues)
|
||||
issues.each do |issue|
|
||||
issue.move_to_end && issue.save unless issue.relative_position
|
||||
end
|
||||
end
|
||||
|
||||
def issue
|
||||
@issue ||=
|
||||
IssuesFinder.new(current_user, project_id: project.id)
|
||||
.execute
|
||||
.where(iid: params[:id])
|
||||
.first!
|
||||
end
|
||||
|
||||
def authorize_read_issue!
|
||||
return render_403 unless can?(current_user, :read_issue, project)
|
||||
end
|
||||
|
||||
def authorize_create_issue!
|
||||
return render_403 unless can?(current_user, :admin_issue, project)
|
||||
end
|
||||
|
||||
def authorize_update_issue!
|
||||
return render_403 unless can?(current_user, :update_issue, issue)
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.merge(board_id: params[:board_id], id: params[:list_id])
|
||||
.reject { |_, value| value.nil? }
|
||||
end
|
||||
|
||||
def move_params
|
||||
params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_iid, :move_after_iid)
|
||||
end
|
||||
|
||||
def issue_params
|
||||
params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
resource.as_json(
|
||||
labels: true,
|
||||
only: [:id, :iid, :title, :confidential, :due_date, :relative_position],
|
||||
include: {
|
||||
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||
milestone: { only: [:id, :title] }
|
||||
},
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,86 +0,0 @@
|
|||
module Projects
|
||||
module Boards
|
||||
class ListsController < Boards::ApplicationController
|
||||
before_action :authorize_admin_list!, only: [:create, :update, :destroy, :generate]
|
||||
before_action :authorize_read_list!, only: [:index]
|
||||
|
||||
def index
|
||||
lists = ::Boards::Lists::ListService.new(project, current_user).execute(board)
|
||||
|
||||
render json: serialize_as_json(lists)
|
||||
end
|
||||
|
||||
def create
|
||||
list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board)
|
||||
|
||||
if list.valid?
|
||||
render json: serialize_as_json(list)
|
||||
else
|
||||
render json: list.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
list = board.lists.movable.find(params[:id])
|
||||
service = ::Boards::Lists::MoveService.new(project, current_user, move_params)
|
||||
|
||||
if service.execute(list)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
list = board.lists.destroyable.find(params[:id])
|
||||
service = ::Boards::Lists::DestroyService.new(project, current_user)
|
||||
|
||||
if service.execute(list)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
service = ::Boards::Lists::GenerateService.new(project, current_user)
|
||||
|
||||
if service.execute(board)
|
||||
render json: serialize_as_json(board.lists.movable)
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_admin_list!
|
||||
return render_403 unless can?(current_user, :admin_list, project)
|
||||
end
|
||||
|
||||
def authorize_read_list!
|
||||
return render_403 unless can?(current_user, :read_list, project)
|
||||
end
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def list_params
|
||||
params.require(:list).permit(:label_id)
|
||||
end
|
||||
|
||||
def move_params
|
||||
params.require(:list).permit(:position)
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
resource.as_json(
|
||||
only: [:id, :list_type, :position],
|
||||
methods: [:title],
|
||||
label: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +1,31 @@
|
|||
class Projects::BoardsController < Projects::ApplicationController
|
||||
include BoardsResponses
|
||||
include IssuableCollections
|
||||
|
||||
before_action :authorize_read_board!, only: [:index, :show]
|
||||
before_action :assign_endpoint_vars
|
||||
|
||||
def index
|
||||
@boards = ::Boards::ListService.new(project, current_user).execute
|
||||
@boards = Boards::ListService.new(project, current_user).execute
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: serialize_as_json(@boards)
|
||||
end
|
||||
end
|
||||
respond_with_boards
|
||||
end
|
||||
|
||||
def show
|
||||
@board = project.boards.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: serialize_as_json(@board)
|
||||
end
|
||||
end
|
||||
respond_with_board
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_endpoint_vars
|
||||
@boards_endpoint = project_boards_url(project)
|
||||
@bulk_issues_path = bulk_update_project_issues_path(project)
|
||||
@namespace_path = project.namespace.full_path
|
||||
@labels_endpoint = project_labels_path(project)
|
||||
end
|
||||
|
||||
def authorize_read_board!
|
||||
return access_denied! unless can?(current_user, :read_board, project)
|
||||
end
|
||||
|
|
|
@ -1,15 +1,80 @@
|
|||
module BoardsHelper
|
||||
def board_data
|
||||
board = @board || @boards.first
|
||||
def board
|
||||
@board ||= @board || @boards.first
|
||||
end
|
||||
|
||||
def board_data
|
||||
{
|
||||
endpoint: project_boards_path(@project),
|
||||
boards_endpoint: @boards_endpoint,
|
||||
lists_endpoint: board_lists_url(board),
|
||||
board_id: board.id,
|
||||
disabled: "#{!can?(current_user, :admin_list, @project)}",
|
||||
issue_link_base: project_issues_path(@project),
|
||||
disabled: "#{!can?(current_user, :admin_list, current_board_parent)}",
|
||||
issue_link_base: build_issue_link_base,
|
||||
root_path: root_path,
|
||||
bulk_update_path: bulk_update_project_issues_path(@project),
|
||||
bulk_update_path: @bulk_issues_path,
|
||||
default_avatar: image_path(default_avatar)
|
||||
}
|
||||
end
|
||||
|
||||
def build_issue_link_base
|
||||
project_issues_path(@project)
|
||||
end
|
||||
|
||||
def current_board_json
|
||||
board = @board || @boards.first
|
||||
|
||||
board.to_json(
|
||||
only: [:id, :name, :milestone_id],
|
||||
include: {
|
||||
milestone: { only: [:title] }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def board_base_url
|
||||
project_boards_path(@project)
|
||||
end
|
||||
|
||||
def multiple_boards_available?
|
||||
current_board_parent.multiple_issue_boards_available?(current_user)
|
||||
end
|
||||
|
||||
def current_board_path(board)
|
||||
@current_board_path ||= project_board_path(current_board_parent, board)
|
||||
end
|
||||
|
||||
def current_board_parent
|
||||
@current_board_parent ||= @project
|
||||
end
|
||||
|
||||
def can_admin_issue?
|
||||
can?(current_user, :admin_issue, current_board_parent)
|
||||
end
|
||||
|
||||
def board_list_data
|
||||
{
|
||||
toggle: "dropdown",
|
||||
list_labels_path: labels_filter_path(true),
|
||||
labels: labels_filter_path(true),
|
||||
labels_endpoint: @labels_endpoint,
|
||||
namespace_path: @namespace_path,
|
||||
project_path: @project&.try(:path)
|
||||
}
|
||||
end
|
||||
|
||||
def board_sidebar_user_data
|
||||
dropdown_options = issue_assignees_dropdown_options
|
||||
|
||||
{
|
||||
toggle: 'dropdown',
|
||||
field_name: 'issue[assignee_ids][]',
|
||||
first_user: current_user&.username,
|
||||
current_user: 'true',
|
||||
project_id: @project&.try(:id),
|
||||
null_user: 'true',
|
||||
multi_select: 'true',
|
||||
'dropdown-header': dropdown_options[:data][:'dropdown-header'],
|
||||
'max-select': dropdown_options[:data][:'max-select']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -347,6 +347,14 @@ module IssuablesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def labels_path
|
||||
if @project
|
||||
project_labels_path(@project)
|
||||
elsif @group
|
||||
group_labels_path(@group)
|
||||
end
|
||||
end
|
||||
|
||||
def issuable_sidebar_options(issuable, can_edit_issuable)
|
||||
{
|
||||
endpoint: "#{issuable_json_path(issuable)}?basic=true",
|
||||
|
|
|
@ -121,13 +121,14 @@ module LabelsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def labels_filter_path
|
||||
return group_labels_path(@group, :json) if @group
|
||||
|
||||
def labels_filter_path(only_group_labels = false)
|
||||
project = @target_project || @project
|
||||
|
||||
if project
|
||||
project_labels_path(project, :json)
|
||||
elsif @group
|
||||
options = { only_group_labels: only_group_labels } if only_group_labels
|
||||
group_labels_path(@group, :json, options)
|
||||
else
|
||||
dashboard_labels_path(:json)
|
||||
end
|
||||
|
|
|
@ -40,6 +40,10 @@ module PreferencesHelper
|
|||
]
|
||||
end
|
||||
|
||||
def user_application_theme
|
||||
@user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class
|
||||
end
|
||||
|
||||
def user_color_scheme
|
||||
Gitlab::ColorSchemes.for_user(current_user).css_class
|
||||
end
|
||||
|
|
|
@ -134,19 +134,21 @@ module SearchHelper
|
|||
end
|
||||
|
||||
def search_filter_input_options(type)
|
||||
opts = {
|
||||
id: "filtered-search-#{type}",
|
||||
placeholder: 'Search or filter results...',
|
||||
data: {
|
||||
'username-params' => @users.to_json(only: [:id, :username])
|
||||
opts =
|
||||
{
|
||||
id: "filtered-search-#{type}",
|
||||
placeholder: 'Search or filter results...',
|
||||
data: {
|
||||
'username-params' => @users.to_json(only: [:id, :username])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if @project.present?
|
||||
opts[:data]['project-id'] = @project.id
|
||||
opts[:data]['base-endpoint'] = project_path(@project)
|
||||
else
|
||||
# Group context
|
||||
opts[:data]['group-id'] = @group.id
|
||||
opts[:data]['base-endpoint'] = group_canonical_path(@group)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,19 @@ class Board < ActiveRecord::Base
|
|||
|
||||
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
validates :project, presence: true
|
||||
validates :project, presence: true, if: :project_needed?
|
||||
|
||||
def project_needed?
|
||||
true
|
||||
end
|
||||
|
||||
def parent
|
||||
project
|
||||
end
|
||||
|
||||
def group_board?
|
||||
false
|
||||
end
|
||||
|
||||
def backlog_list
|
||||
lists.merge(List.backlog).take
|
||||
|
|
|
@ -10,8 +10,12 @@ module RelativePositioning
|
|||
after_save :save_positionable_neighbours
|
||||
end
|
||||
|
||||
def project_ids
|
||||
[project.id]
|
||||
end
|
||||
|
||||
def max_relative_position
|
||||
self.class.in_projects(project.id).maximum(:relative_position)
|
||||
self.class.in_projects(project_ids).maximum(:relative_position)
|
||||
end
|
||||
|
||||
def prev_relative_position
|
||||
|
@ -19,7 +23,7 @@ module RelativePositioning
|
|||
|
||||
if self.relative_position
|
||||
prev_pos = self.class
|
||||
.in_projects(project.id)
|
||||
.in_projects(project_ids)
|
||||
.where('relative_position < ?', self.relative_position)
|
||||
.maximum(:relative_position)
|
||||
end
|
||||
|
@ -32,7 +36,7 @@ module RelativePositioning
|
|||
|
||||
if self.relative_position
|
||||
next_pos = self.class
|
||||
.in_projects(project.id)
|
||||
.in_projects(project_ids)
|
||||
.where('relative_position > ?', self.relative_position)
|
||||
.minimum(:relative_position)
|
||||
end
|
||||
|
@ -59,7 +63,7 @@ module RelativePositioning
|
|||
pos_after = before.next_relative_position
|
||||
|
||||
if before.shift_after?
|
||||
issue_to_move = self.class.in_projects(project.id).find_by!(relative_position: pos_after)
|
||||
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_after)
|
||||
issue_to_move.move_after
|
||||
@positionable_neighbours = [issue_to_move]
|
||||
|
||||
|
@ -74,7 +78,7 @@ module RelativePositioning
|
|||
pos_before = after.prev_relative_position
|
||||
|
||||
if after.shift_before?
|
||||
issue_to_move = self.class.in_projects(project.id).find_by!(relative_position: pos_before)
|
||||
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_before)
|
||||
issue_to_move.move_before
|
||||
@positionable_neighbours = [issue_to_move]
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class Event < ActiveRecord::Base
|
||||
include Sortable
|
||||
include IgnorableColumn
|
||||
default_scope { reorder(nil).where.not(author_id: nil) }
|
||||
|
||||
CREATED = 1
|
||||
|
@ -50,13 +51,9 @@ class Event < ActiveRecord::Base
|
|||
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
has_one :push_event_payload, foreign_key: :event_id
|
||||
|
||||
# For Hash only
|
||||
serialize :data # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
# Callbacks
|
||||
after_create :reset_project_activity
|
||||
after_create :set_last_repository_updated_at, if: :push?
|
||||
after_create :replicate_event_for_push_events_migration
|
||||
|
||||
# Scopes
|
||||
scope :recent, -> { reorder(id: :desc) }
|
||||
|
@ -82,6 +79,10 @@ class Event < ActiveRecord::Base
|
|||
|
||||
self.inheritance_column = 'action'
|
||||
|
||||
# "data" will be removed in 10.0 but it may be possible that JOINs happen that
|
||||
# include this column, hence we're ignoring it as well.
|
||||
ignore_column :data
|
||||
|
||||
class << self
|
||||
def model_name
|
||||
ActiveModel::Name.new(self, nil, 'event')
|
||||
|
@ -159,7 +160,7 @@ class Event < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def push?
|
||||
action == PUSHED && valid_push?
|
||||
false
|
||||
end
|
||||
|
||||
def merged?
|
||||
|
@ -272,87 +273,6 @@ class Event < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def valid_push?
|
||||
data[:ref] && ref_name.present?
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
def tag?
|
||||
Gitlab::Git.tag_ref?(data[:ref])
|
||||
end
|
||||
|
||||
def branch?
|
||||
Gitlab::Git.branch_ref?(data[:ref])
|
||||
end
|
||||
|
||||
def new_ref?
|
||||
Gitlab::Git.blank_ref?(commit_from)
|
||||
end
|
||||
|
||||
def rm_ref?
|
||||
Gitlab::Git.blank_ref?(commit_to)
|
||||
end
|
||||
|
||||
def md_ref?
|
||||
!(rm_ref? || new_ref?)
|
||||
end
|
||||
|
||||
def commit_from
|
||||
data[:before]
|
||||
end
|
||||
|
||||
def commit_to
|
||||
data[:after]
|
||||
end
|
||||
|
||||
def ref_name
|
||||
if tag?
|
||||
tag_name
|
||||
else
|
||||
branch_name
|
||||
end
|
||||
end
|
||||
|
||||
def branch_name
|
||||
@branch_name ||= Gitlab::Git.ref_name(data[:ref])
|
||||
end
|
||||
|
||||
def tag_name
|
||||
@tag_name ||= Gitlab::Git.ref_name(data[:ref])
|
||||
end
|
||||
|
||||
# Max 20 commits from push DESC
|
||||
def commits
|
||||
@commits ||= (data[:commits] || []).reverse
|
||||
end
|
||||
|
||||
def commit_title
|
||||
commit = commits.last
|
||||
|
||||
commit[:message] if commit
|
||||
end
|
||||
|
||||
def commit_id
|
||||
commit_to || commit_from
|
||||
end
|
||||
|
||||
def commits_count
|
||||
data[:total_commits_count] || commits.count || 0
|
||||
end
|
||||
|
||||
def ref_type
|
||||
tag? ? "tag" : "branch"
|
||||
end
|
||||
|
||||
def push_with_commits?
|
||||
!commits.empty? && commit_from && commit_to
|
||||
end
|
||||
|
||||
def last_push_to_non_root?
|
||||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
|
||||
def target_iid
|
||||
target.respond_to?(:iid) ? target.iid : target_id
|
||||
end
|
||||
|
@ -432,16 +352,6 @@ class Event < ActiveRecord::Base
|
|||
user ? author_id == user.id : false
|
||||
end
|
||||
|
||||
# We're manually replicating data into the new table since database triggers
|
||||
# are not dumped to db/schema.rb. This could mean that a new installation
|
||||
# would not have the triggers in place, thus losing events data in GitLab
|
||||
# 10.0.
|
||||
def replicate_event_for_push_events_migration
|
||||
new_attributes = attributes.with_indifferent_access.except(:title, :data)
|
||||
|
||||
EventForMigration.create!(new_attributes)
|
||||
end
|
||||
|
||||
def to_partial_path
|
||||
# We are intentionally using `Event` rather than `self.class` so that
|
||||
# subclasses also use the `Event` implementation.
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
# This model is used to replicate events between the old "events" table and the
|
||||
# new "events_for_migration" table that will replace "events" in GitLab 10.0.
|
||||
class EventForMigration < ActiveRecord::Base
|
||||
self.table_name = 'events_for_migration'
|
||||
end
|
|
@ -34,7 +34,8 @@ class Label < ActiveRecord::Base
|
|||
|
||||
scope :templates, -> { where(template: true) }
|
||||
scope :with_title, ->(title) { where(title: title) }
|
||||
scope :on_project_boards, ->(project_id) { joins(lists: :board).merge(List.movable).where(boards: { project_id: project_id }) }
|
||||
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
|
||||
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
|
||||
|
||||
def self.prioritized(project)
|
||||
joins(:priorities)
|
||||
|
@ -172,6 +173,7 @@ class Label < ActiveRecord::Base
|
|||
|
||||
def as_json(options = {})
|
||||
super(options).tap do |json|
|
||||
json[:type] = self.try(:type)
|
||||
json[:priority] = priority(options[:project]) if options.key?(:project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1507,6 +1507,14 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def multiple_issue_boards_available?(user)
|
||||
feature_available?(:multiple_issue_boards, user)
|
||||
end
|
||||
|
||||
def issue_board_milestone_available?(user = nil)
|
||||
feature_available?(:issue_board_milestone, user)
|
||||
end
|
||||
|
||||
def full_path_was
|
||||
File.join(namespace.full_path, previous_changes['path'].first)
|
||||
end
|
||||
|
|
|
@ -15,15 +15,21 @@ class PushEvent < Event
|
|||
# should ensure the ID points to a valid project.
|
||||
validates :project_id, presence: true
|
||||
|
||||
# The "data" field must not be set for push events since it's not used and a
|
||||
# waste of space.
|
||||
validates :data, absence: true
|
||||
|
||||
# These fields are also not used for push events, thus storing them would be a
|
||||
# waste.
|
||||
validates :target_id, absence: true
|
||||
validates :target_type, absence: true
|
||||
|
||||
delegate :branch?, to: :push_event_payload
|
||||
delegate :tag?, to: :push_event_payload
|
||||
delegate :commit_from, to: :push_event_payload
|
||||
delegate :commit_to, to: :push_event_payload
|
||||
delegate :ref_type, to: :push_event_payload
|
||||
delegate :commit_title, to: :push_event_payload
|
||||
|
||||
delegate :commit_count, to: :push_event_payload
|
||||
alias_method :commits_count, :commit_count
|
||||
|
||||
def self.sti_name
|
||||
PUSHED
|
||||
end
|
||||
|
@ -36,86 +42,35 @@ class PushEvent < Event
|
|||
!!(commit_from && commit_to)
|
||||
end
|
||||
|
||||
def tag?
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.tag?
|
||||
end
|
||||
|
||||
def branch?
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.branch?
|
||||
end
|
||||
|
||||
def valid_push?
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.ref.present?
|
||||
end
|
||||
|
||||
def new_ref?
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.created?
|
||||
end
|
||||
|
||||
def rm_ref?
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.removed?
|
||||
end
|
||||
|
||||
def commit_from
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.commit_from
|
||||
end
|
||||
|
||||
def commit_to
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.commit_to
|
||||
def md_ref?
|
||||
!(rm_ref? || new_ref?)
|
||||
end
|
||||
|
||||
def ref_name
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.ref
|
||||
end
|
||||
|
||||
def ref_type
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.ref_type
|
||||
end
|
||||
|
||||
def branch_name
|
||||
return super unless push_event_payload
|
||||
|
||||
ref_name
|
||||
end
|
||||
|
||||
def tag_name
|
||||
return super unless push_event_payload
|
||||
|
||||
ref_name
|
||||
end
|
||||
|
||||
def commit_title
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.commit_title
|
||||
end
|
||||
alias_method :branch_name, :ref_name
|
||||
alias_method :tag_name, :ref_name
|
||||
|
||||
def commit_id
|
||||
commit_to || commit_from
|
||||
end
|
||||
|
||||
def commits_count
|
||||
return super unless push_event_payload
|
||||
|
||||
push_event_payload.commit_count
|
||||
def last_push_to_non_root?
|
||||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
|
||||
def validate_push_action
|
||||
|
|
|
@ -35,6 +35,7 @@ class User < ActiveRecord::Base
|
|||
default_value_for :project_view, :files
|
||||
default_value_for :notified_of_own_activity, false
|
||||
default_value_for :preferred_language, I18n.default_locale
|
||||
default_value_for :theme_id, gitlab_config.default_theme
|
||||
|
||||
attr_encrypted :otp_secret,
|
||||
key: Gitlab::Application.secrets.otp_key_base,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module Boards
|
||||
class BaseService < ::BaseService
|
||||
# Parent can either a group or a project
|
||||
attr_accessor :parent, :current_user, :params
|
||||
|
||||
def initialize(parent, user, params = {})
|
||||
@parent, @current_user, @params = parent, user, params.dup
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module Boards
|
||||
class CreateService < BaseService
|
||||
class CreateService < Boards::BaseService
|
||||
def execute
|
||||
create_board! if can_create_board?
|
||||
end
|
||||
|
@ -7,11 +7,11 @@ module Boards
|
|||
private
|
||||
|
||||
def can_create_board?
|
||||
project.boards.size == 0
|
||||
parent.boards.size == 0
|
||||
end
|
||||
|
||||
def create_board!
|
||||
board = project.boards.create(params)
|
||||
board = parent.boards.create(params)
|
||||
|
||||
if board.persisted?
|
||||
board.lists.create(list_type: :backlog)
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class CreateService < BaseService
|
||||
class CreateService < Boards::BaseService
|
||||
attr_accessor :project
|
||||
|
||||
def initialize(parent, project, user, params = {})
|
||||
@project = project
|
||||
|
||||
super(parent, user, params)
|
||||
end
|
||||
|
||||
def execute
|
||||
create_issue(params.merge(label_ids: [list.label_id]))
|
||||
end
|
||||
|
@ -8,7 +16,7 @@ module Boards
|
|||
private
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params.delete(:board_id))
|
||||
@board ||= parent.boards.find(params.delete(:board_id))
|
||||
end
|
||||
|
||||
def list
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class ListService < BaseService
|
||||
class ListService < Boards::BaseService
|
||||
def execute
|
||||
issues = IssuesFinder.new(current_user, filter_params).execute
|
||||
issues = without_board_labels(issues) unless movable_list? || closed_list?
|
||||
|
@ -11,7 +11,7 @@ module Boards
|
|||
private
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params[:board_id])
|
||||
@board ||= parent.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def list
|
||||
|
@ -33,14 +33,14 @@ module Boards
|
|||
end
|
||||
|
||||
def filter_params
|
||||
set_project
|
||||
set_parent
|
||||
set_state
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def set_project
|
||||
params[:project_id] = project.id
|
||||
def set_parent
|
||||
params[:project_id] = parent.id
|
||||
end
|
||||
|
||||
def set_state
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class MoveService < BaseService
|
||||
class MoveService < Boards::BaseService
|
||||
def execute(issue)
|
||||
return false unless can?(current_user, :update_issue, issue)
|
||||
return false if issue_params.empty?
|
||||
|
||||
update_service.execute(issue)
|
||||
update(issue)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def board
|
||||
@board ||= project.boards.find(params[:board_id])
|
||||
@board ||= parent.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def move_between_lists?
|
||||
|
@ -27,8 +27,8 @@ module Boards
|
|||
@moving_to_list ||= board.lists.find_by(id: params[:to_list_id])
|
||||
end
|
||||
|
||||
def update_service
|
||||
::Issues::UpdateService.new(project, current_user, issue_params)
|
||||
def update(issue)
|
||||
::Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
|
||||
end
|
||||
|
||||
def issue_params
|
||||
|
@ -42,7 +42,7 @@ module Boards
|
|||
)
|
||||
end
|
||||
|
||||
attrs[:move_between_iids] = move_between_iids if move_between_iids
|
||||
attrs[:move_between_ids] = move_between_ids if move_between_ids
|
||||
|
||||
attrs
|
||||
end
|
||||
|
@ -61,16 +61,16 @@ module Boards
|
|||
if moving_to_list.movable?
|
||||
moving_from_list.label_id
|
||||
else
|
||||
Label.on_project_boards(project.id).pluck(:label_id)
|
||||
Label.on_project_boards(parent.id).pluck(:label_id)
|
||||
end
|
||||
|
||||
Array(label_ids).compact
|
||||
end
|
||||
|
||||
def move_between_iids
|
||||
return unless params[:move_after_iid] || params[:move_before_iid]
|
||||
def move_between_ids
|
||||
return unless params[:move_after_id] || params[:move_before_id]
|
||||
|
||||
[params[:move_after_iid], params[:move_before_iid]]
|
||||
[params[:move_after_id], params[:move_before_id]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
module Boards
|
||||
class ListService < BaseService
|
||||
class ListService < Boards::BaseService
|
||||
def execute
|
||||
create_board! if project.boards.empty?
|
||||
project.boards
|
||||
create_board! if parent.boards.empty?
|
||||
parent.boards
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_board!
|
||||
Boards::CreateService.new(project, current_user).execute
|
||||
Boards::CreateService.new(parent, current_user).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class CreateService < BaseService
|
||||
class CreateService < Boards::BaseService
|
||||
def execute(board)
|
||||
List.transaction do
|
||||
label = available_labels.find(params[:label_id])
|
||||
label = available_labels_for(board).find(params[:label_id])
|
||||
position = next_position(board)
|
||||
|
||||
create_list(board, label, position)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_labels
|
||||
LabelsFinder.new(current_user, project_id: project.id).execute
|
||||
def available_labels_for(board)
|
||||
LabelsFinder.new(current_user, project_id: parent.id).execute
|
||||
end
|
||||
|
||||
def next_position(board)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class DestroyService < BaseService
|
||||
class DestroyService < Boards::BaseService
|
||||
def execute(list)
|
||||
return false unless list.destroyable?
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class GenerateService < BaseService
|
||||
class GenerateService < Boards::BaseService
|
||||
def execute(board)
|
||||
return false unless board.lists.movable.empty?
|
||||
|
||||
|
@ -15,11 +15,11 @@ module Boards
|
|||
|
||||
def create_list(board, params)
|
||||
label = find_or_create_label(params)
|
||||
Lists::CreateService.new(project, current_user, label_id: label.id).execute(board)
|
||||
Lists::CreateService.new(parent, current_user, label_id: label.id).execute(board)
|
||||
end
|
||||
|
||||
def find_or_create_label(params)
|
||||
::Labels::FindOrCreateService.new(current_user, project, params).execute
|
||||
::Labels::FindOrCreateService.new(current_user, parent, params).execute
|
||||
end
|
||||
|
||||
def label_params
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class ListService < BaseService
|
||||
class ListService < Boards::BaseService
|
||||
def execute(board)
|
||||
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Boards
|
||||
module Lists
|
||||
class MoveService < BaseService
|
||||
class MoveService < Boards::BaseService
|
||||
def execute(list)
|
||||
@board = list.board
|
||||
@old_position = list.position
|
||||
|
|
|
@ -3,7 +3,7 @@ module Issues
|
|||
include SpamCheckService
|
||||
|
||||
def execute(issue)
|
||||
handle_move_between_iids(issue)
|
||||
handle_move_between_ids(issue)
|
||||
filter_spam_check_params
|
||||
change_issue_duplicate(issue)
|
||||
move_issue_to_new_project(issue) || update(issue)
|
||||
|
@ -54,13 +54,13 @@ module Issues
|
|||
end
|
||||
end
|
||||
|
||||
def handle_move_between_iids(issue)
|
||||
return unless params[:move_between_iids]
|
||||
def handle_move_between_ids(issue)
|
||||
return unless params[:move_between_ids]
|
||||
|
||||
after_iid, before_iid = params.delete(:move_between_iids)
|
||||
after_id, before_id = params.delete(:move_between_ids)
|
||||
|
||||
issue_before = get_issue_if_allowed(issue.project, before_iid) if before_iid
|
||||
issue_after = get_issue_if_allowed(issue.project, after_iid) if after_iid
|
||||
issue_before = get_issue_if_allowed(issue.project, before_id) if before_id
|
||||
issue_after = get_issue_if_allowed(issue.project, after_id) if after_id
|
||||
|
||||
issue.move_between(issue_before, issue_after)
|
||||
end
|
||||
|
@ -87,8 +87,8 @@ module Issues
|
|||
|
||||
private
|
||||
|
||||
def get_issue_if_allowed(project, iid)
|
||||
issue = project.issues.find_by(iid: iid)
|
||||
def get_issue_if_allowed(project, id)
|
||||
issue = project.issues.find(id)
|
||||
issue if can?(current_user, :update_issue, issue)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
!!! 5
|
||||
%html{ lang: I18n.locale, class: page_class }
|
||||
= render "layouts/head"
|
||||
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
|
||||
%body{ class: "#{user_application_theme} #{@body_class}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
|
||||
= render "layouts/init_auto_complete" if @gfm_form
|
||||
= render 'peek/bar'
|
||||
= render "layouts/header/default"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%ul.list-unstyled.navbar-sub-nav
|
||||
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown" }) do
|
||||
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects" }) do
|
||||
%a{ href: "#", data: { toggle: "dropdown" } }
|
||||
Projects
|
||||
= custom_icon('caret_down')
|
||||
|
@ -22,7 +22,7 @@
|
|||
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
|
||||
Snippets
|
||||
|
||||
%li.dropdown.hidden-lg
|
||||
%li.header-more.dropdown.hidden-lg
|
||||
%a{ href: "#", data: { toggle: "dropdown" } }
|
||||
More
|
||||
= custom_icon('caret_down')
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
Overview
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_root_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Overview') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
|
||||
= link_to admin_root_path, title: 'Overview' do
|
||||
%span
|
||||
|
@ -55,6 +60,11 @@
|
|||
Monitoring
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles), html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_conversational_development_index_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Monitoring') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(controller: :system_info) do
|
||||
= link_to admin_system_info_path, title: 'System Info' do
|
||||
%span
|
||||
|
@ -82,6 +92,11 @@
|
|||
= custom_icon('messages')
|
||||
%span.nav-item-name
|
||||
Messages
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :broadcast_messages, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_broadcast_messages_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Messages') }
|
||||
|
||||
= nav_link(controller: [:hooks, :hook_logs]) do
|
||||
= sidebar_link admin_hooks_path, title: _('Hooks') do
|
||||
|
@ -89,6 +104,11 @@
|
|||
= custom_icon('system_hooks')
|
||||
%span.nav-item-name
|
||||
System Hooks
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: [:hooks, :hook_logs], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_hooks_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('System Hooks') }
|
||||
|
||||
= nav_link(controller: :applications) do
|
||||
= sidebar_link admin_applications_path, title: _('Applications') do
|
||||
|
@ -96,6 +116,11 @@
|
|||
= custom_icon('applications')
|
||||
%span.nav-item-name
|
||||
Applications
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :applications, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_applications_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Applications') }
|
||||
|
||||
= nav_link(controller: :abuse_reports) do
|
||||
= sidebar_link admin_abuse_reports_path, title: _("Abuse Reports") do
|
||||
|
@ -104,6 +129,12 @@
|
|||
%span.nav-item-name
|
||||
Abuse Reports
|
||||
%span.badge.count= number_with_delimiter(AbuseReport.count(:all))
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :abuse_reports, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_broadcast_messages_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Abuse Reports') }
|
||||
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all))
|
||||
|
||||
- if akismet_enabled?
|
||||
= nav_link(controller: :spam_logs) do
|
||||
|
@ -112,6 +143,11 @@
|
|||
= custom_icon('spam_logs')
|
||||
%span.nav-item-name
|
||||
Spam Logs
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :spam_logs, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_spam_logs_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Spam Logs') }
|
||||
|
||||
= nav_link(controller: :deploy_keys) do
|
||||
= sidebar_link admin_deploy_keys_path, title: _('Deploy Keys') do
|
||||
|
@ -119,6 +155,11 @@
|
|||
= custom_icon('key')
|
||||
%span.nav-item-name
|
||||
Deploy Keys
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :deploy_keys, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_deploy_keys_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Deploy Keys') }
|
||||
|
||||
= nav_link(controller: :services) do
|
||||
= sidebar_link admin_application_settings_services_path, title: _('Service Templates') do
|
||||
|
@ -126,6 +167,11 @@
|
|||
= custom_icon('service_templates')
|
||||
%span.nav-item-name
|
||||
Service Templates
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :services, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_application_settings_services_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Service Templates') }
|
||||
|
||||
= nav_link(controller: :labels) do
|
||||
= sidebar_link admin_labels_path, title: _('Labels') do
|
||||
|
@ -133,6 +179,11 @@
|
|||
= custom_icon('labels')
|
||||
%span.nav-item-name
|
||||
Labels
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :labels, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_labels_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Labels') }
|
||||
|
||||
= nav_link(controller: :appearances) do
|
||||
= sidebar_link admin_appearances_path, title: _('Appearances') do
|
||||
|
@ -140,6 +191,11 @@
|
|||
= custom_icon('appearance')
|
||||
%span.nav-item-name
|
||||
Appearance
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :appearances, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_appearances_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Appearance') }
|
||||
|
||||
= nav_link(controller: :application_settings) do
|
||||
= sidebar_link admin_application_settings_path, title: _('Settings') do
|
||||
|
@ -147,5 +203,10 @@
|
|||
= custom_icon('settings')
|
||||
%span.nav-item-name
|
||||
Settings
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to admin_application_settings_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Settings') }
|
||||
|
||||
= render 'shared/sidebar_toggle_button'
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
|
||||
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
|
||||
|
||||
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
|
||||
.nav-sidebar-inner-scroll
|
||||
.context-header
|
||||
|
@ -15,6 +18,11 @@
|
|||
Overview
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Overview') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
|
||||
= link_to group_path(@group), title: 'Group details' do
|
||||
%span
|
||||
|
@ -30,11 +38,16 @@
|
|||
.nav-icon-container
|
||||
= custom_icon('issues')
|
||||
%span.nav-item-name
|
||||
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
|
||||
Issues
|
||||
%span.badge.count= number_with_delimiter(issues.count)
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to issues_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Issues') }
|
||||
%span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues.count)
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
|
||||
= link_to issues_group_path(@group), title: 'List' do
|
||||
%span
|
||||
|
@ -55,15 +68,25 @@
|
|||
.nav-icon-container
|
||||
= custom_icon('mr_bold')
|
||||
%span.nav-item-name
|
||||
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
|
||||
Merge Requests
|
||||
%span.badge.count= number_with_delimiter(merge_requests.count)
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Merge Requests') }
|
||||
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count)
|
||||
= nav_link(path: 'group_members#index') do
|
||||
= sidebar_link group_group_members_path(@group), title: _('Members') do
|
||||
.nav-icon-container
|
||||
= custom_icon('members')
|
||||
%span.nav-item-name
|
||||
Members
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Members') }
|
||||
- if current_user && can?(current_user, :admin_group, @group)
|
||||
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
|
||||
= sidebar_link edit_group_path(@group), title: _('Settings') do
|
||||
|
@ -72,6 +95,11 @@
|
|||
%span.nav-item-name
|
||||
Settings
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(path: %w[groups#projects groups#edit ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to edit_group_path(@group) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Settings') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'groups#edit') do
|
||||
= link_to edit_group_path(@group), title: 'General' do
|
||||
%span
|
||||
|
|
|
@ -12,12 +12,22 @@
|
|||
= custom_icon('profile')
|
||||
%span.nav-item-name
|
||||
Profile
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: 'profiles#show', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Profile') }
|
||||
= nav_link(controller: [:accounts, :two_factor_auths]) do
|
||||
= sidebar_link profile_account_path, title: _('Account') do
|
||||
.nav-icon-container
|
||||
= custom_icon('account')
|
||||
%span.nav-item-name
|
||||
Account
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: [:accounts, :two_factor_auths], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_account_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Account') }
|
||||
- if current_application_settings.user_oauth_applications?
|
||||
= nav_link(controller: 'oauth/applications') do
|
||||
= sidebar_link applications_profile_path, title: _('Applications') do
|
||||
|
@ -25,24 +35,44 @@
|
|||
= custom_icon('applications')
|
||||
%span.nav-item-name
|
||||
Applications
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: 'oauth/applications', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to applications_profile_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Applications') }
|
||||
= nav_link(controller: :chat_names) do
|
||||
= sidebar_link profile_chat_names_path, title: _('Chat') do
|
||||
.nav-icon-container
|
||||
= custom_icon('chat')
|
||||
%span.nav-item-name
|
||||
Chat
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :chat_names, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_chat_names_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Chat') }
|
||||
= nav_link(controller: :personal_access_tokens) do
|
||||
= sidebar_link profile_personal_access_tokens_path, title: _('Access Tokens') do
|
||||
.nav-icon-container
|
||||
= custom_icon('access_tokens')
|
||||
%span.nav-item-name
|
||||
Access Tokens
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :personal_access_tokens, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_personal_access_tokens_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Access Tokens') }
|
||||
= nav_link(controller: :emails) do
|
||||
= sidebar_link profile_emails_path, title: _('Emails') do
|
||||
.nav-icon-container
|
||||
= custom_icon('emails')
|
||||
%span.nav-item-name
|
||||
Emails
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :emails, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_emails_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Emails') }
|
||||
- unless current_user.ldap_user?
|
||||
= nav_link(controller: :passwords) do
|
||||
= sidebar_link edit_profile_password_path, title: _('Password') do
|
||||
|
@ -50,36 +80,65 @@
|
|||
= custom_icon('lock')
|
||||
%span.nav-item-name
|
||||
Password
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :passwords, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to edit_profile_password_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Password') }
|
||||
= nav_link(controller: :notifications) do
|
||||
= sidebar_link profile_notifications_path, title: _('Notifications') do
|
||||
.nav-icon-container
|
||||
= custom_icon('notifications')
|
||||
%span.nav-item-name
|
||||
Notifications
|
||||
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :notifications, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_notifications_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Notifications') }
|
||||
= nav_link(controller: :keys) do
|
||||
= sidebar_link profile_keys_path, title: _('SSH Keys') do
|
||||
.nav-icon-container
|
||||
= custom_icon('key')
|
||||
%span.nav-item-name
|
||||
SSH Keys
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :keys, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_keys_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('SSH Keys') }
|
||||
= nav_link(controller: :gpg_keys) do
|
||||
= sidebar_link profile_gpg_keys_path, title: _('GPG Keys') do
|
||||
.nav-icon-container
|
||||
= custom_icon('key_2')
|
||||
%span.nav-item-name
|
||||
GPG Keys
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :gpg_keys, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_gpg_keys_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('GPG Keys') }
|
||||
= nav_link(controller: :preferences) do
|
||||
= sidebar_link profile_preferences_path, title: _('Preferences') do
|
||||
.nav-icon-container
|
||||
= custom_icon('preferences')
|
||||
%span.nav-item-name
|
||||
Preferences
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :preferences, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to profile_preferences_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Preferences') }
|
||||
= nav_link(path: 'profiles#audit_log') do
|
||||
= sidebar_link audit_log_profile_path, title: _('Authentication log') do
|
||||
.nav-icon-container
|
||||
= custom_icon('authentication_log')
|
||||
%span.nav-item-name
|
||||
Authentication log
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: 'profiles#audit_log', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to audit_log_profile_path do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Authentication Log') }
|
||||
|
||||
= render 'shared/sidebar_toggle_button'
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
Overview
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Overview') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: 'projects#show') do
|
||||
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
|
||||
%span= _('Details')
|
||||
|
@ -38,6 +43,11 @@
|
|||
Repository
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network), html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_tree_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Repository') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
|
||||
= link_to project_tree_path(@project) do
|
||||
#{ _('Files') }
|
||||
|
@ -90,6 +100,14 @@
|
|||
= number_with_delimiter(@project.open_issues_count)
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(controller: :issues, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_issues_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Issues') }
|
||||
- if @project.issues_enabled?
|
||||
%span.badge.count.issue_counter.fly-out-badge
|
||||
= number_with_delimiter(@project.open_issues_count)
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(controller: :issues) do
|
||||
= link_to project_issues_path(@project), title: 'Issues' do
|
||||
%span
|
||||
|
@ -133,6 +151,13 @@
|
|||
Merge Requests
|
||||
%span.badge.count.merge_counter.js-merge-counter
|
||||
= number_with_delimiter(@project.open_merge_requests_count)
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :merge_requests, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_merge_requests_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Merge Requests') }
|
||||
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge
|
||||
= number_with_delimiter(@project.open_merge_requests_count)
|
||||
|
||||
- if project_nav_tab? :pipelines
|
||||
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
|
||||
|
@ -143,6 +168,11 @@
|
|||
CI / CD
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_pipelines_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('CI / CD') }
|
||||
%li.divider.fly-out-top-item
|
||||
- if project_nav_tab? :pipelines
|
||||
= nav_link(path: ['pipelines#index', 'pipelines#show']) do
|
||||
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
|
||||
|
@ -180,6 +210,11 @@
|
|||
= custom_icon('wiki')
|
||||
%span.nav-item-name
|
||||
Wiki
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to get_project_wiki_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Wiki') }
|
||||
|
||||
- if project_nav_tab? :snippets
|
||||
= nav_link(controller: :snippets) do
|
||||
|
@ -188,6 +223,11 @@
|
|||
= custom_icon('snippets')
|
||||
%span.nav-item-name
|
||||
Snippets
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :snippets, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_snippets_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Snippets') }
|
||||
|
||||
- if project_nav_tab? :settings
|
||||
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
|
||||
|
@ -200,6 +240,11 @@
|
|||
%ul.sidebar-sub-level-items
|
||||
- can_edit = can?(current_user, :admin_project, @project)
|
||||
- if can_edit
|
||||
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to edit_project_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
#{ _('Settings') }
|
||||
%li.divider.fly-out-top-item
|
||||
= nav_link(path: %w[projects#edit]) do
|
||||
= link_to edit_project_path(@project), title: 'General' do
|
||||
%span
|
||||
|
|
|
@ -3,6 +3,26 @@
|
|||
= render 'profiles/head'
|
||||
|
||||
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
|
||||
.col-lg-4.application-theme
|
||||
%h4.prepend-top-0
|
||||
GitLab navigation theme
|
||||
%p Customize the appearance of the application header and navigation sidebar.
|
||||
.col-lg-8.application-theme
|
||||
- Gitlab::Themes.each do |theme|
|
||||
= label_tag do
|
||||
.preview{ class: theme.name.downcase }
|
||||
.preview-row
|
||||
.quadrant.one
|
||||
.quadrant.two
|
||||
.preview-row
|
||||
.quadrant.three
|
||||
.quadrant.four
|
||||
= f.radio_button :theme_id, theme.id
|
||||
= theme.name
|
||||
|
||||
.col-sm-12
|
||||
%hr
|
||||
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.prepend-top-0
|
||||
Syntax highlighting theme
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// Remove body class for any previous theme, re-add current one
|
||||
$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
|
||||
$('body').addClass('<%= user_application_theme %>')
|
||||
|
||||
// Toggle container-fluid class
|
||||
if ('<%= current_user.layout %>' === 'fluid') {
|
||||
$('.content-wrapper .container-fluid').removeClass('container-limited')
|
||||
|
|
|
@ -1 +1 @@
|
|||
= render "show"
|
||||
= render "shared/boards/show", board: @boards.first
|
||||
|
|
|
@ -1 +1 @@
|
|||
= render "show"
|
||||
= render "shared/boards/show", board: @board
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
= link_to icon('question-circle'), help_page_path("gitlab-basics/create-project"), target: '_blank', aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'}
|
||||
%div
|
||||
= render 'project_templates', f: f
|
||||
.second-column
|
||||
- if import_sources_enabled?
|
||||
- if import_sources_enabled?
|
||||
.second-column
|
||||
.project-import
|
||||
.form-group.clearfix
|
||||
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
|
||||
|
|
|
@ -27,9 +27,10 @@
|
|||
= link_to project_tags_path(@project) do
|
||||
#{n_('Tag', 'Tags', @repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
|
||||
|
||||
- if default_project_view != 'readme' && @repository.readme
|
||||
- if @repository.readme
|
||||
%li
|
||||
= link_to _('Readme'), readme_path(@project)
|
||||
= link_to _('Readme'),
|
||||
default_project_view != 'readme' ? readme_path(@project) : '#readme'
|
||||
|
||||
- if @repository.changelog
|
||||
%li
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- if readme.rich_viewer
|
||||
%article.file-holder.readme-holder{ class: ("limited-width-container" unless fluid_layout) }
|
||||
%article.file-holder.readme-holder{ id: 'readme', class: ("limited-width-container" unless fluid_layout) }
|
||||
.js-file-title.file-title
|
||||
= blob_icon readme.mode, readme.name
|
||||
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
= webpack_bundle_tag 'filtered_search'
|
||||
= webpack_bundle_tag 'boards'
|
||||
|
||||
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
|
||||
%script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
|
||||
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
|
||||
|
||||
= render "projects/issues/head"
|
||||
|
@ -30,7 +30,7 @@
|
|||
":root-path" => "rootPath",
|
||||
":board-id" => "boardId",
|
||||
":key" => "_uid" }
|
||||
= render "projects/boards/components/sidebar"
|
||||
= render "shared/boards/components/sidebar"
|
||||
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
|
||||
"new-issue-path" => new_project_issue_path(@project),
|
||||
"milestone-path" => milestones_filter_dropdown_path,
|
|
@ -7,20 +7,26 @@
|
|||
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }",
|
||||
"aria-hidden": "true" }
|
||||
|
||||
%span.has-tooltip{ "v-if": "list.type !== \"label\"",
|
||||
%span.board-title-text.has-tooltip{ "v-if": "list.type !== \"label\"",
|
||||
":title" => '(list.label ? list.label.description : "")' }
|
||||
{{ list.title }}
|
||||
|
||||
%span.has-tooltip{ "v-if": "list.type === \"label\"",
|
||||
":title" => '(list.label ? list.label.description : "")',
|
||||
data: { container: "body", placement: "bottom" },
|
||||
class: "label color-label title",
|
||||
class: "label color-label title board-title-text",
|
||||
":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color : \"#2e2e2e\") }" }
|
||||
{{ list.title }}
|
||||
.issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
|
||||
- if can?(current_user, :admin_list, current_board_parent)
|
||||
%board-delete{ "inline-template" => true,
|
||||
":list" => "list",
|
||||
"v-if" => "!list.preset && list.id" }
|
||||
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
|
||||
= icon("trash")
|
||||
.issue-count-badge.clearfix{ "v-if" => 'list.type !== "blank"' }
|
||||
%span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
|
||||
{{ list.issuesSize }}
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can?(current_user, :admin_list, current_board_parent)
|
||||
%button.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button",
|
||||
"@click" => "showNewIssueForm",
|
||||
"v-if" => 'list.type !== "closed"',
|
||||
|
@ -28,12 +34,7 @@
|
|||
"title" => "New issue",
|
||||
data: { placement: "top", container: "body" } }
|
||||
= icon("plus", class: "js-no-trigger-collapse")
|
||||
- if can?(current_user, :admin_list, @project)
|
||||
%board-delete{ "inline-template" => true,
|
||||
":list" => "list",
|
||||
"v-if" => "!list.preset && list.id" }
|
||||
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
|
||||
= icon("trash")
|
||||
|
||||
%board-list{ "v-if" => 'list.type !== "blank"',
|
||||
":list" => "list",
|
||||
":issues" => "list.issues",
|
||||
|
@ -42,5 +43,5 @@
|
|||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath",
|
||||
"ref" => "board-list" }
|
||||
- if can?(current_user, :admin_list, @project)
|
||||
- if can?(current_user, :admin_list, current_board_parent)
|
||||
%board-blank-state{ "v-if" => 'list.id == "blank"' }
|
|
@ -10,18 +10,19 @@
|
|||
%br/
|
||||
%span
|
||||
= precede "#" do
|
||||
{{ issue.id }}
|
||||
{{ issue.iid }}
|
||||
%a.gutter-toggle.pull-right{ role: "button",
|
||||
href: "#",
|
||||
"@click.prevent" => "closeSidebar",
|
||||
"aria-label" => "Toggle sidebar" }
|
||||
= custom_icon("icon_close", size: 15)
|
||||
.js-issuable-update
|
||||
= render "projects/boards/components/sidebar/assignee"
|
||||
= render "projects/boards/components/sidebar/milestone"
|
||||
= render "projects/boards/components/sidebar/due_date"
|
||||
= render "projects/boards/components/sidebar/labels"
|
||||
= render "projects/boards/components/sidebar/notifications"
|
||||
= render "shared/boards/components/sidebar/assignee"
|
||||
= render "shared/boards/components/sidebar/milestone"
|
||||
= render "shared/boards/components/sidebar/due_date"
|
||||
= render "shared/boards/components/sidebar/labels"
|
||||
= render "shared/boards/components/sidebar/notifications"
|
||||
%remove-btn{ ":issue" => "issue",
|
||||
":issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'",
|
||||
":list" => "list",
|
||||
"v-if" => "canRemove" }
|
|
@ -2,13 +2,13 @@
|
|||
%template{ "v-if" => "issue.assignees" }
|
||||
%assignee-title{ ":number-of-assignees" => "issue.assignees.length",
|
||||
":loading" => "loadingAssignees",
|
||||
":editable" => can?(current_user, :admin_issue, @project) }
|
||||
":editable" => can_admin_issue? }
|
||||
%assignees.value{ "root-path" => "#{root_url}",
|
||||
":users" => "issue.assignees",
|
||||
":editable" => can?(current_user, :admin_issue, @project),
|
||||
":editable" => can_admin_issue?,
|
||||
"@assign-self" => "assignSelf" }
|
||||
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
.selectbox.hide-collapsed
|
||||
%input.js-vue{ type: "hidden",
|
||||
name: "issue[assignee_ids][]",
|
||||
|
@ -20,9 +20,9 @@
|
|||
":data-username" => "assignee.username" }
|
||||
.dropdown
|
||||
- dropdown_options = issue_assignees_dropdown_options
|
||||
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
|
||||
":data-issuable-id" => "issue.id",
|
||||
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
|
||||
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: board_sidebar_user_data,
|
||||
":data-issuable-id" => "issue.iid",
|
||||
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
|
||||
= dropdown_options[:title]
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
|
|
@ -1,7 +1,7 @@
|
|||
.block.due_date
|
||||
.title
|
||||
Due date
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
= icon("spinner spin", class: "block-loading")
|
||||
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
|
||||
.value
|
||||
|
@ -10,12 +10,12 @@
|
|||
No due date
|
||||
%span.bold{ "v-if" => "issue.dueDate" }
|
||||
{{ issue.dueDate | due-date }}
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
%span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" }
|
||||
\-
|
||||
%a.js-remove-due-date{ href: "#", role: "button" }
|
||||
remove due date
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
.selectbox
|
||||
%input{ type: "hidden",
|
||||
name: "issue[due_date]",
|
||||
|
@ -23,7 +23,7 @@
|
|||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
|
||||
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
|
||||
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
|
||||
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
|
||||
%span.dropdown-toggle-text Due date
|
||||
= icon('chevron-down')
|
||||
.dropdown-menu.dropdown-menu-due-date
|
|
@ -1,7 +1,7 @@
|
|||
.block.labels
|
||||
.title
|
||||
Labels
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
= icon("spinner spin", class: "block-loading")
|
||||
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
|
||||
.value.issuable-show-labels
|
||||
|
@ -11,7 +11,7 @@
|
|||
"v-for" => "label in issue.labels" }
|
||||
%span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
|
||||
{{ label.title }}
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
.selectbox
|
||||
%input{ type: "hidden",
|
||||
name: "issue[label_names][]",
|
||||
|
@ -19,12 +19,19 @@
|
|||
":value" => "label.id" }
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
|
||||
data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: project_labels_path(@project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
|
||||
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
|
||||
data: { toggle: "dropdown",
|
||||
field_name: "issue[label_names][]",
|
||||
show_no: "true",
|
||||
show_any: "true",
|
||||
project_id: @project&.try(:id),
|
||||
labels: labels_filter_path(false),
|
||||
namespace_path: @project.try(:namespace).try(:full_path),
|
||||
project_path: @project.try(:path) },
|
||||
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
|
||||
%span.dropdown-toggle-text
|
||||
Label
|
||||
= icon('chevron-down')
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
|
||||
= render partial: "shared/issuable/label_page_default"
|
||||
- if can? current_user, :admin_label, @project and @project
|
||||
- if can?(current_user, :admin_label, current_board_parent)
|
||||
= render partial: "shared/issuable/label_page_create"
|
|
@ -1,7 +1,7 @@
|
|||
.block.milestone
|
||||
.title
|
||||
Milestone
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
= icon("spinner spin", class: "block-loading")
|
||||
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
|
||||
.value
|
||||
|
@ -9,17 +9,17 @@
|
|||
None
|
||||
%span.bold.has-tooltip{ "v-if" => "issue.milestone" }
|
||||
{{ issue.milestone.title }}
|
||||
- if can?(current_user, :admin_issue, @project)
|
||||
- if can_admin_issue?
|
||||
.selectbox
|
||||
%input{ type: "hidden",
|
||||
":value" => "issue.milestone.id",
|
||||
name: "issue[milestone_id]",
|
||||
"v-if" => "issue.milestone" }
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
|
||||
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
|
||||
":data-selected" => "milestoneTitle",
|
||||
":data-issuable-id" => "issue.id",
|
||||
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
|
||||
":data-issuable-id" => "issue.iid",
|
||||
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
|
||||
Milestone
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-selectable
|
|
@ -1,5 +1,5 @@
|
|||
- if current_user
|
||||
.block.light.subscription{ ":data-url" => "'#{project_issues_path(@project)}/' + issue.id + '/toggle_subscription'" }
|
||||
.block.light.subscription{ ":data-url" => "'#{build_issue_link_base}/' + issue.iid + '/toggle_subscription'" }
|
||||
%span.issuable-header-text.hide-collapsed.pull-left
|
||||
Notifications
|
||||
%button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
|
|
@ -0,0 +1 @@
|
|||
= render "show"
|
|
@ -0,0 +1 @@
|
|||
= render "show"
|
|
@ -8,20 +8,19 @@
|
|||
- if show_boards_content
|
||||
.issue-board-dropdown-content
|
||||
%p
|
||||
Create lists from the labels you use in your project. Issues with that
|
||||
label will automatically be added to the list.
|
||||
Create lists from labels. Issues with that label appear in that list.
|
||||
= dropdown_filter(filter_placeholder)
|
||||
= dropdown_content
|
||||
- if @project && show_footer
|
||||
- if current_board_parent && show_footer
|
||||
= dropdown_footer do
|
||||
%ul.dropdown-footer-list
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
- if can?(current_user, :admin_label, current_board_parent)
|
||||
%li
|
||||
%a.dropdown-toggle-page{ href: "#" }
|
||||
Create new label
|
||||
%li
|
||||
= link_to project_labels_path(@project), :"data-is-link" => true do
|
||||
- if show_create && @project && can?(current_user, :admin_label, @project)
|
||||
= link_to labels_path, :"data-is-link" => true do
|
||||
- if show_create && can?(current_user, :admin_label, current_board_parent)
|
||||
Manage labels
|
||||
- else
|
||||
View labels
|
||||
|
|
|
@ -104,13 +104,13 @@
|
|||
= icon('times')
|
||||
.filter-dropdown-container
|
||||
- if type == :boards
|
||||
- if can?(current_user, :admin_list, @project)
|
||||
- if can?(current_user, :admin_list, board.parent)
|
||||
.dropdown.prepend-left-10#js-add-list
|
||||
%button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) } }
|
||||
%button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
|
||||
Add list
|
||||
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
|
||||
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
- if can?(current_user, :admin_label, board.parent)
|
||||
= render partial: "shared/issuable/label_page_create"
|
||||
= dropdown_loading
|
||||
#js-add-issues-btn.prepend-left-10
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add div id to the readme in the project overview
|
||||
merge_request: 13735
|
||||
author: Riccardo Padovani @rpadovani
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add option in preferences to change navigation theme color
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Finish migration to the new events setup
|
||||
merge_request:
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add documentation for PlantUML in reStructuredText
|
||||
merge_request: 13900
|
||||
author: Markus Koller
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix stray OR in New Project page
|
||||
merge_request: 14096
|
||||
author: Robin Bobbitt
|
||||
type: fixed
|
|
@ -76,6 +76,13 @@ production: &base
|
|||
|
||||
# default_can_create_group: false # default: true
|
||||
# username_changing_enabled: false # default: true - User can change her username/namespace
|
||||
## Default theme ID
|
||||
## 1 - Indigo
|
||||
## 2 - Dark
|
||||
## 3 - Light
|
||||
## 4 - Blue
|
||||
## 5 - Green
|
||||
# default_theme: 1 # default: 1
|
||||
|
||||
## Automatic issue closing
|
||||
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
|
||||
|
|
|
@ -232,6 +232,7 @@ Settings['gitlab'] ||= Settingslogic.new({})
|
|||
Settings.gitlab['default_projects_limit'] ||= 100000
|
||||
Settings.gitlab['default_branch_protection'] ||= 2
|
||||
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
|
||||
Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
|
||||
Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
|
||||
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
|
||||
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
|
||||
|
|
|
@ -74,6 +74,19 @@ Rails.application.routes.draw do
|
|||
# Notification settings
|
||||
resources :notification_settings, only: [:create, :update]
|
||||
|
||||
# Boards resources shared between group and projects
|
||||
resources :boards do
|
||||
resources :lists, module: :boards, only: [:index, :create, :update, :destroy] do
|
||||
collection do
|
||||
post :generate
|
||||
end
|
||||
|
||||
resources :issues, only: [:index, :create, :update]
|
||||
end
|
||||
|
||||
resources :issues, module: :boards, only: [:index, :update]
|
||||
end
|
||||
|
||||
draw :import
|
||||
draw :uploads
|
||||
draw :explore
|
||||
|
|
|
@ -343,19 +343,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
|
||||
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
|
||||
|
||||
resources :boards, only: [:index, :show] do
|
||||
scope module: :boards do
|
||||
resources :issues, only: [:index, :update]
|
||||
|
||||
resources :lists, only: [:index, :create, :update, :destroy] do
|
||||
collection do
|
||||
post :generate
|
||||
end
|
||||
|
||||
resources :issues, only: [:index, :create]
|
||||
end
|
||||
end
|
||||
end
|
||||
resources :boards, only: [:index, :show, :create, :update, :destroy]
|
||||
|
||||
resources :todos, only: [:create]
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class AddThemeIdToUsers < ActiveRecord::Migration
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :users, :theme_id, :integer, limit: 2
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class StealRemainingEventMigrationJobs < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# Set this constant to true if this migration requires downtime.
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
Gitlab::BackgroundMigration.steal('MigrateEventsToPushEventPayloads')
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class SwapEventMigrationTables < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# Set this constant to true if this migration requires downtime.
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
rename_tables
|
||||
end
|
||||
|
||||
def down
|
||||
rename_tables
|
||||
end
|
||||
|
||||
def rename_tables
|
||||
rename_table :events, :events_old
|
||||
rename_table :events_for_migration, :events
|
||||
rename_table :events_old, :events_for_migration
|
||||
end
|
||||
end
|
|
@ -7,6 +7,5 @@ class LimitsToMysql < ActiveRecord::Migration
|
|||
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
|
||||
change_column :snippets, :content, :text, limit: 2147483647
|
||||
change_column :notes, :st_diff, :text, limit: 2147483647
|
||||
change_column :events, :data, :text, limit: 2147483647
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue