summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2017-09-07 18:47:08 +0200
committerKamil Trzcinski <ayufan@ayufan.eu>2017-09-07 18:47:08 +0200
commit129b4eb1de48965d4d029e9b21bd5a0f8f9d37fa (patch)
treef7d97bda52d0a1021886f0952fe8f33dc0049c10 /app
parent2c4d57ffdf8e111bbbb5817d9fd1882fab770f21 (diff)
parent12ddc28f844fe63446a16ac908b09ed7b13c71ef (diff)
downloadgitlab-ce-129b4eb1de48965d4d029e9b21bd5a0f8f9d37fa.tar.gz
Merge branch 'zj/gitlab-ce-zj-auto-devops-table' into 37158-autodevops-banner
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js16
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js51
-rw-r--r--app/assets/javascripts/boards/components/board_list.js10
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.js5
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js9
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js2
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js28
-rw-r--r--app/assets/javascripts/boards/models/issue.js4
-rw-r--r--app/assets/javascripts/boards/models/label.js1
-rw-r--r--app/assets/javascripts/boards/models/list.js28
-rw-r--r--app/assets/javascripts/boards/services/board_service.js20
-rw-r--r--app/assets/javascripts/fly_out_nav.js7
-rw-r--r--app/assets/javascripts/issue.js2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss3
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss27
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss265
-rw-r--r--app/assets/stylesheets/framework/header.scss1
-rw-r--r--app/assets/stylesheets/framework/variables.scss39
-rw-r--r--app/assets/stylesheets/new_nav.scss119
-rw-r--r--app/assets/stylesheets/new_sidebar.scss46
-rw-r--r--app/assets/stylesheets/pages/boards.scss63
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss64
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/controllers/admin/users_controller.rb1
-rw-r--r--app/controllers/boards/application_controller.rb21
-rw-r--r--app/controllers/boards/issues_controller.rb90
-rw-r--r--app/controllers/boards/lists_controller.rb75
-rw-r--r--app/controllers/concerns/boards_responses.rb42
-rw-r--r--app/controllers/profiles/preferences_controller.rb3
-rw-r--r--app/controllers/projects/boards/application_controller.rb15
-rw-r--r--app/controllers/projects/boards/issues_controller.rb94
-rw-r--r--app/controllers/projects/boards/lists_controller.rb86
-rw-r--r--app/controllers/projects/boards_controller.rb27
-rw-r--r--app/helpers/boards_helper.rb77
-rw-r--r--app/helpers/issuables_helper.rb8
-rw-r--r--app/helpers/labels_helper.rb7
-rw-r--r--app/helpers/preferences_helper.rb4
-rw-r--r--app/helpers/search_helper.rb14
-rw-r--r--app/models/board.rb14
-rw-r--r--app/models/concerns/relative_positioning.rb14
-rw-r--r--app/models/event.rb102
-rw-r--r--app/models/event_for_migration.rb5
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/push_event.rb77
-rw-r--r--app/models/user.rb1
-rw-r--r--app/services/boards/base_service.rb10
-rw-r--r--app/services/boards/create_service.rb6
-rw-r--r--app/services/boards/issues/create_service.rb12
-rw-r--r--app/services/boards/issues/list_service.rb10
-rw-r--r--app/services/boards/issues/move_service.rb20
-rw-r--r--app/services/boards/list_service.rb8
-rw-r--r--app/services/boards/lists/create_service.rb9
-rw-r--r--app/services/boards/lists/destroy_service.rb2
-rw-r--r--app/services/boards/lists/generate_service.rb6
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/boards/lists/move_service.rb2
-rw-r--r--app/services/issues/update_service.rb16
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml61
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml32
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml61
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml45
-rw-r--r--app/views/profiles/preferences/show.html.haml20
-rw-r--r--app/views/profiles/preferences/update.js.erb4
-rw-r--r--app/views/projects/boards/index.html.haml2
-rw-r--r--app/views/projects/boards/show.html.haml2
-rw-r--r--app/views/projects/new.html.haml4
-rw-r--r--app/views/projects/show.html.haml5
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/shared/boards/_show.html.haml (renamed from app/views/projects/boards/_show.html.haml)4
-rw-r--r--app/views/shared/boards/components/_board.html.haml (renamed from app/views/projects/boards/components/_board.html.haml)23
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml (renamed from app/views/projects/boards/components/_sidebar.html.haml)13
-rw-r--r--app/views/shared/boards/components/sidebar/_assignee.html.haml (renamed from app/views/projects/boards/components/sidebar/_assignee.html.haml)12
-rw-r--r--app/views/shared/boards/components/sidebar/_due_date.html.haml (renamed from app/views/projects/boards/components/sidebar/_due_date.html.haml)8
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml (renamed from app/views/projects/boards/components/sidebar/_labels.html.haml)17
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml (renamed from app/views/projects/boards/components/sidebar/_milestone.html.haml)10
-rw-r--r--app/views/shared/boards/components/sidebar/_notifications.html.haml (renamed from app/views/projects/boards/components/sidebar/_notifications.html.haml)2
-rw-r--r--app/views/shared/boards/index.html.haml1
-rw-r--r--app/views/shared/boards/show.html.haml1
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml11
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml6
85 files changed, 1336 insertions, 723 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 8acddd6194c..38d1effc77c 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -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',
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 89c14180149..ea00efe4b46 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -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>
`,
});
});
diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js
index bebca17fb1e..6159680f1e6 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.js
@@ -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"
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js
index 4af8b0c7713..541b8049855 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.js
+++ b/app/assets/javascripts/boards/components/board_new_issue.js
@@ -6,7 +6,10 @@ const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardNewIssue',
props: {
- list: Object,
+ list: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index 9a5d87ede7e..bf474879024 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -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>
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 478a1335b2b..a656f0546c0 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -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, {
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 72bb9e10fbc..d7f203b3f96 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -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);
});
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 6a900d4abd0..1e623cf58b7 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -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) => {
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 6c2d8a3781b..407db176446 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -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;
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/boards/models/label.js
index 9af88d167d6..98c1ec014c4 100644
--- a/app/assets/javascripts/boards/models/label.js
+++ b/app/assets/javascripts/boards/models/label.js
@@ -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;
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 08f7c5ddcd2..df2809e1805 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -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) {
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index 3742507b236..38eea38f949 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -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,
});
}
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 063155a167a..4b19f7b4188 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -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);
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 7c4f4da6127..c0bd64814ca 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -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));
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c0524bf6aa3..35e7a10379f 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -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";
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index a85051642dd..706a9cffe87 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -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;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4a1bb8402c..cb501030356 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -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 {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
new file mode 100644
index 00000000000..71f764923ff
--- /dev/null
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index b00a2d053e2..ab3c34df1fb 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -111,7 +111,6 @@ header {
svg {
height: 16px;
width: 23px;
- fill: currentColor;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 88b08998dfd..becdd7ff35b 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -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;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 2b6c0fc015c..8e095cbdd7e 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -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;
}
}
- }
-
- .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);
- }
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ outline: 0;
+ opacity: 1;
+ color: $white-light;
- svg {
- fill: currentColor;
- }
+ svg {
+ fill: currentColor;
+ }
- &.header-user-dropdown-toggle {
- .header-user-avatar {
- border-color: $white-light;
+ &.header-user-dropdown-toggle {
+ .header-user-avatar {
+ border-color: $white-light;
+ }
}
}
}
+ .header-new-dropdown-toggle {
+ margin-right: 0;
+ }
+
.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 {
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index fd5e344d8c9..4bbd30056a9 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -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
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 314dd2d1a21..700be173039 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -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;
}
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index 305feaacaa1..c197494b152 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -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;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 615020ca856..13dd7b5a780 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -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;
}
}
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a99563b7100..635298bc24a 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -211,6 +211,7 @@ class Admin::UsersController < Admin::ApplicationController
:provider,
:remember_me,
:skype,
+ :theme_id,
:twitter,
:username,
:website_url
diff --git a/app/controllers/boards/application_controller.rb b/app/controllers/boards/application_controller.rb
new file mode 100644
index 00000000000..b2675025fc0
--- /dev/null
+++ b/app/controllers/boards/application_controller.rb
@@ -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
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
new file mode 100644
index 00000000000..8d4ec2d6d9d
--- /dev/null
+++ b/app/controllers/boards/issues_controller.rb
@@ -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
diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb
new file mode 100644
index 00000000000..381fd4d7508
--- /dev/null
+++ b/app/controllers/boards/lists_controller.rb
@@ -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
diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb
new file mode 100644
index 00000000000..2c9c095a5d7
--- /dev/null
+++ b/app/controllers/concerns/boards_responses.rb
@@ -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
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 1e557c47638..cce2a847b53 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:color_scheme_id,
:layout,
:dashboard,
- :project_view
+ :project_view,
+ :theme_id
)
end
end
diff --git a/app/controllers/projects/boards/application_controller.rb b/app/controllers/projects/boards/application_controller.rb
deleted file mode 100644
index dad38fff6b9..00000000000
--- a/app/controllers/projects/boards/application_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
deleted file mode 100644
index 653e7bc7e40..00000000000
--- a/app/controllers/projects/boards/issues_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb
deleted file mode 100644
index ad53bb749a0..00000000000
--- a/app/controllers/projects/boards/lists_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 808affa4f98..d1b99ecce4a 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -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
-
- respond_to do |format|
- format.html
- format.json do
- render json: serialize_as_json(@boards)
- end
- end
+ @boards = Boards::ListService.new(project, current_user).execute
+
+ 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
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 8b33c362a9c..4bd61aa8f86 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -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
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index ce2999e6696..66e1e607e01 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -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",
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e60513b35c7..e1ba7898ee6 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -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
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index d36bb4ab074..0d7347ed30d 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -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
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 98e824a8c65..af6683a548b 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -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
diff --git a/app/models/board.rb b/app/models/board.rb
index 97d0f550925..5bb7d3d3722 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -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
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 7cb9a28a284..e961c97e337 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -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]
diff --git a/app/models/event.rb b/app/models/event.rb
index 996768a267b..c313bbb66f8 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -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.
diff --git a/app/models/event_for_migration.rb b/app/models/event_for_migration.rb
deleted file mode 100644
index a1672da5eec..00000000000
--- a/app/models/event_for_migration.rb
+++ /dev/null
@@ -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
diff --git a/app/models/label.rb b/app/models/label.rb
index 674bb3f2720..958141a7358 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index ca64fb173fb..ff5638dd155 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1511,6 +1511,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
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
index 3f1ff979de6..23ffb0d4ea8 100644
--- a/app/models/push_event.rb
+++ b/app/models/push_event.rb
@@ -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
diff --git a/app/models/user.rb b/app/models/user.rb
index 105eb62f1fa..f75cc21c65c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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,
diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb
new file mode 100644
index 00000000000..72822ffffa1
--- /dev/null
+++ b/app/services/boards/base_service.rb
@@ -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
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 9eedb9e65a2..bd0bb387662 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -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)
diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb
index c0d7ff5b585..7c4a79f555e 100644
--- a/app/services/boards/issues/create_service.rb
+++ b/app/services/boards/issues/create_service.rb
@@ -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
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index eb345fead2d..d85d93e251b 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -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
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index ecabb2a48e4..797d6df7c1a 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -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
diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb
index 84f1fc3a4e2..6d0dd0a9f99 100644
--- a/app/services/boards/list_service.rb
+++ b/app/services/boards/list_service.rb
@@ -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
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index fe0d762ccd2..183556a1d6b 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -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)
diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb
index f986e05944c..d75c5fd3dc6 100644
--- a/app/services/boards/lists/destroy_service.rb
+++ b/app/services/boards/lists/destroy_service.rb
@@ -1,6 +1,6 @@
module Boards
module Lists
- class DestroyService < BaseService
+ class DestroyService < Boards::BaseService
def execute(list)
return false unless list.destroyable?
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
index 939f9bfd068..05d4ab5dbcc 100644
--- a/app/services/boards/lists/generate_service.rb
+++ b/app/services/boards/lists/generate_service.rb
@@ -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
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index df2a01a69e5..e57c95294af 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -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?
diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb
index f2a68865f7b..7d0730e8332 100644
--- a/app/services/boards/lists/move_service.rb
+++ b/app/services/boards/lists/move_service.rb
@@ -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
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index deb4990eb4f..b4ca3966505 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -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
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 65ac8aaa59b..0ca34b276a7 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -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"
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 8a39c4d775f..c254ee02dd8 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -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')
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 3b53117deb6..8ab2b686f86 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -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'
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 5a1511b262f..e01dfa7c854 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -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
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index ccb6d1492f1..4c26d107ea7 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -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'
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 760c4c97c33..27fadc1d952 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -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
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 9e7fe556d88..2b72eeab8d6 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -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
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
index 431ab9d052b..8966dd3fd86 100644
--- a/app/views/profiles/preferences/update.js.erb
+++ b/app/views/profiles/preferences/update.js.erb
@@ -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')
diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml
index 2a5b8b1441e..bb56769bd3f 100644
--- a/app/views/projects/boards/index.html.haml
+++ b/app/views/projects/boards/index.html.haml
@@ -1 +1 @@
-= render "show"
+= render "shared/boards/show", board: @boards.first
diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml
index 2a5b8b1441e..e5b5f6404bb 100644
--- a/app/views/projects/boards/show.html.haml
+++ b/app/views/projects/boards/show.html.haml
@@ -1 +1 @@
-= render "show"
+= render "shared/boards/show", board: @board
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index adffd67029a..819392b8f0c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -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
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 952e9b33bf0..d8f5114f4b5 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -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
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 4579a912f39..4daacbe157c 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -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
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 303e20e8780..1a50b7d4b69 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -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,
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index 64f5f6d7ba0..ce0aa72ab00 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -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"' }
diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 09d70f658a3..b3f73e96b81 100644
--- a/app/views/projects/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -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" }
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml
index 8d957613be1..3d2e8471a60 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml
@@ -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
diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index e8394eab213..db794d6f855 100644
--- a/app/views/projects/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -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
diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 6b389736e8b..1f540bdaf93 100644
--- a/app/views/projects/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -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"
diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index a1ddb261ea3..d09c7c218e0 100644
--- a/app/views/projects/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -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
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/shared/boards/components/sidebar/_notifications.html.haml
index aaddd7e249f..9b989c23cab 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/shared/boards/components/sidebar/_notifications.html.haml
@@ -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" }
diff --git a/app/views/shared/boards/index.html.haml b/app/views/shared/boards/index.html.haml
new file mode 100644
index 00000000000..2a5b8b1441e
--- /dev/null
+++ b/app/views/shared/boards/index.html.haml
@@ -0,0 +1 @@
+= render "show"
diff --git a/app/views/shared/boards/show.html.haml b/app/views/shared/boards/show.html.haml
new file mode 100644
index 00000000000..2a5b8b1441e
--- /dev/null
+++ b/app/views/shared/boards/show.html.haml
@@ -0,0 +1 @@
+= render "show"
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index e8feff32d26..ad031e6af80 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -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
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index e81789ea7a2..161b1c9fd72 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -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