diff options
author | Felipe Artur <felipefac@gmail.com> | 2018-02-19 16:06:16 -0300 |
---|---|---|
committer | Felipe Artur <felipefac@gmail.com> | 2018-03-03 12:56:17 -0300 |
commit | dd071c4b6e9754a0abeec45ab2040d9e2d5a62b8 (patch) | |
tree | 9dda99bb987378cb6cbc95d236757d11f21176a6 /app/assets | |
parent | 98fecb5f8e64c4c64c96d065bc342d986140367e (diff) | |
download | gitlab-ce-dd071c4b6e9754a0abeec45ab2040d9e2d5a62b8.tar.gz |
Bring one group board to CE
Diffstat (limited to 'app/assets')
13 files changed, 268 insertions, 54 deletions
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 23fec503586..84885ca9306 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable vue/require-default-prop */ import './issue_card_inner'; import eventHub from '../eventhub'; @@ -34,6 +35,9 @@ export default { type: String, default: '', }, + groupId: { + type: Number, + }, }, data() { return { @@ -88,6 +92,7 @@ export default { :list="list" :issue="issue" :issue-link-base="issueLinkBase" + :group-id="groupId" :root-path="rootPath" :update-filters="true" /> diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 6637904d87d..0d03c1c419c 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -15,6 +15,11 @@ export default { loadingIcon, }, props: { + groupId: { + type: Number, + required: false, + default: 0, + }, disabled: { type: Boolean, required: true, @@ -170,6 +175,7 @@ export default { <loading-icon /> </div> <board-new-issue + :group-id="groupId" :list="list" v-if="list.type !== 'closed' && showIssueForm"/> <ul @@ -185,6 +191,7 @@ export default { :list="list" :issue="issue" :issue-link-base="issueLinkBase" + :group-id="groupId" :root-path="rootPath" :disabled="disabled" :key="issue.id" /> diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index efface7143d..870d242e774 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -1,12 +1,21 @@ <script> import eventHub from '../eventhub'; +import ProjectSelect from './project_select.vue'; import ListIssue from '../models/issue'; const Store = gl.issueBoards.BoardsStore; export default { name: 'BoardNewIssue', + components: { + ProjectSelect, + }, props: { + groupId: { + type: Number, + required: false, + default: 0, + }, list: { type: Object, required: true, @@ -16,10 +25,20 @@ export default { return { title: '', error: false, + selectedProject: {}, }; }, + computed: { + disabled() { + if (this.groupId) { + return this.title === '' || !this.selectedProject.name; + } + return this.title === ''; + }, + }, mounted() { this.$refs.input.focus(); + eventHub.$on('setSelectedProject', this.setSelectedProject); }, methods: { submit(e) { @@ -34,6 +53,7 @@ export default { labels, subscribed: true, assignees: [], + project_id: this.selectedProject.id, }); eventHub.$emit(`scroll-board-list-${this.list.id}`); @@ -62,52 +82,62 @@ export default { this.title = ''; eventHub.$emit(`hide-issue-form-${this.list.id}`); }, + setSelectedProject(selectedProject) { + this.selectedProject = selectedProject; + }, }, }; </script> <template> - <div class="card board-new-issue-form"> - <form @submit="submit($event)"> - <div - class="flash-container" - v-if="error" - > - <div class="flash-alert"> - An error occurred. Please try again. - </div> - </div> - <label - class="label-light" - :for="list.id + '-title'" - > - Title - </label> - <input - class="form-control" - type="text" - v-model="title" - ref="input" - autocomplete="off" - :id="list.id + '-title'" - /> - <div class="clearfix prepend-top-10"> - <button - class="btn btn-success pull-left" - type="submit" - :disabled="title === ''" - ref="submit-button" + <div class="board-new-issue-form"> + <div class="card"> + <form @submit="submit($event)"> + <div + class="flash-container" + v-if="error" > - Submit issue - </button> - <button - class="btn btn-default pull-right" - type="button" - @click="cancel" + <div class="flash-alert"> + An error occurred. Please try again. + </div> + </div> + <label + class="label-light" + :for="list.id + '-title'" > - Cancel - </button> - </div> - </form> + Title + </label> + <input + class="form-control" + type="text" + v-model="title" + ref="input" + autocomplete="off" + :id="list.id + '-title'" + /> + <project-select + v-if="groupId" + :group-id="groupId" + /> + <div class="clearfix prepend-top-10"> + <button + class="btn btn-success pull-left" + type="submit" + :disabled="disabled" + ref="submit-button" + > + Submit issue + </button> + <button + class="btn btn-default pull-right" + type="button" + @click="cancel" + > + Cancel + </button> + </div> + </form> + </div> </div> </template> + diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index bf474879024..fc2bad2415f 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -31,6 +31,10 @@ gl.issueBoards.IssueCardInner = Vue.extend({ required: false, default: false, }, + groupId: { + type: Number, + required: false, + }, }, data() { return { @@ -64,7 +68,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({ return this.issue.assignees.length > this.numberOverLimit; }, cardUrl() { - return `${this.issueLinkBase}/${this.issue.iid}`; + let baseUrl = this.issueLinkBase; + + if (this.groupId && this.issue.project) { + baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path); + } + + return `${baseUrl}/${this.issue.iid}`; }, issueId() { if (this.issue.iid) { @@ -148,7 +158,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({ class="card-number" v-if="issueId" > - {{ issueId }} + <template v-if="groupId && issue.project">{{issue.project.path}}</template>{{ issueId }} </span> </h4> <div class="card-assignee"> diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue new file mode 100644 index 00000000000..d99b222c305 --- /dev/null +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -0,0 +1,127 @@ +<script> + /* global ListIssue */ + import _ from 'underscore'; + import eventHub from '../eventhub'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import Api from '../../api'; + + export default { + name: 'BoardProjectSelect', + components: { + loadingIcon, + }, + props: { + groupId: { + type: Number, + required: true, + default: 0, + }, + }, + data() { + return { + loading: true, + selectedProject: {}, + }; + }, + computed: { + selectedProjectName() { + return this.selectedProject.name || 'Select a project'; + }, + }, + mounted() { + $(this.$refs.projectsDropdown).glDropdown({ + filterable: true, + filterRemote: true, + search: { + fields: ['name_with_namespace'], + }, + clicked: ({ $el, e }) => { + e.preventDefault(); + this.selectedProject = { + id: $el.data('project-id'), + name: $el.data('project-name'), + }; + eventHub.$emit('setSelectedProject', this.selectedProject); + }, + selectable: true, + data: (term, callback) => { + this.loading = true; + return Api.groupProjects(this.groupId, term, (projects) => { + this.loading = false; + callback(projects); + }); + }, + renderRow(project) { + return ` + <li> + <a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}"> + ${_.escape(project.name)} + </a> + </li> + `; + }, + text: project => project.name, + }); + }, + }; +</script> + +<template> + <div> + <label class="label-light prepend-top-10"> + Project + </label> + <div + ref="projectsDropdown" + class="dropdown" + > + <button + class="dropdown-menu-toggle wide" + type="button" + data-toggle="dropdown" + aria-expanded="false" + > + {{ selectedProjectName }} + <i + class="fa fa-chevron-down" + aria-hidden="true" + > + </i> + </button> + <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width"> + <div class="dropdown-title"> + <span>Projects</span> + <button + aria-label="Close" + type="button" + class="dropdown-title-button dropdown-menu-close" + > + <i + aria-hidden="true" + data-hidden="true" + class="fa fa-times dropdown-menu-close-icon" + > + </i> + </button> + </div> + <div class="dropdown-input"> + <input + class="dropdown-input-field" + type="search" + placeholder="Search projects" + /> + <i + aria-hidden="true" + data-hidden="true" + class="fa fa-search dropdown-input-search" + > + </i> + </div> + <div class="dropdown-content"></div> + <div class="dropdown-loading"> + <loading-icon /> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 0ae32bb4d0a..09c683ff621 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -24,7 +24,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ }, computed: { updateUrl() { - return this.issueUpdate; + return this.issueUpdate.replace(':project_path', this.issue.project.path); }, }, methods: { @@ -32,17 +32,21 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ const issue = this.issue; const lists = issue.getLists(); const listLabelIds = lists.map(list => list.label.id); - let labelIds = this.issue.labels + + let labelIds = issue.labels .map(label => label.id) .filter(id => !listLabelIds.includes(id)); if (labelIds.length === 0) { labelIds = ['']; } + const data = { issue: { label_ids: labelIds, }, }; + + // Post the remove data Vue.http.patch(this.updateUrl, data).catch(() => { Flash(__('Failed to remove issue from board, please try again.')); diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index 57a7cc4ca30..fb40b9f5565 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -6,6 +6,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager { constructor(store, updateUrl = false, cantEdit = []) { super({ page: 'boards', + stateFiltersSelector: '.issues-state-filters', }); this.store = store; diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 8e31f1865f0..b8749a13f68 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -13,6 +13,7 @@ import './models/issue'; import './models/label'; import './models/list'; import './models/milestone'; +import './models/project'; import './models/assignee'; import './stores/boards_store'; import './stores/modal_store'; @@ -89,7 +90,7 @@ export default () => { sidebarEventHub.$off('toggleSubscription', this.toggleSubscription); }, mounted () { - this.filterManager = new FilteredSearchBoards(Store.filter, true); + this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit); this.filterManager.setup(); Store.disabled = this.disabled; @@ -179,6 +180,7 @@ export default () => { return { modal: ModalStore.store, store: Store.state, + canAdminList: this.$options.el.hasAttribute('data-can-admin-list'), }; }, computed: { @@ -232,6 +234,7 @@ export default () => { :class="{ 'disabled': disabled }" :title="tooltipTitle" :aria-disabled="disabled" + v-if="canAdminList" @click="openModal"> Add issues </button> diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js index 38a0eb12f92..5e31c6314b2 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js @@ -1,6 +1,8 @@ /* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */ /* global DocumentTouch */ +import sortableConfig from '../../sortable/sortable_config'; + window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; @@ -18,19 +20,14 @@ gl.issueBoards.onEnd = () => { gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch; gl.issueBoards.getBoardSortableDefaultOptions = (obj) => { - const defaultSortOptions = { - animation: 200, - forceFallback: true, - fallbackClass: 'is-dragging', - fallbackOnBody: true, - ghostClass: 'is-ghost', + const defaultSortOptions = Object.assign({}, sortableConfig, { filter: '.board-delete, .btn', delay: gl.issueBoards.touchEnabled ? 100 : 0, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, onStart: gl.issueBoards.onStart, - onEnd: gl.issueBoards.onEnd - }; + onEnd: gl.issueBoards.onEnd, + }); Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); return defaultSortOptions; diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 3bfb6d39ad5..4c5079efc8b 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -4,6 +4,7 @@ /* global ListAssignee */ import Vue from 'vue'; +import IssueProject from './project'; class ListIssue { constructor (obj, defaultAvatar) { @@ -23,6 +24,12 @@ class ListIssue { this.isLoading = {}; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; + this.milestone_id = obj.milestone_id; + this.project_id = obj.project_id; + + if (obj.project) { + this.project = new IssueProject(obj.project); + } if (obj.milestone) { this.milestone = new ListMilestone(obj.milestone); @@ -105,7 +112,8 @@ class ListIssue { data.issue.label_ids = ['']; } - return Vue.http.patch(url, data); + const projectPath = this.project ? this.project.path : ''; + return Vue.http.patch(url.replace(':project_path', projectPath), data); } } diff --git a/app/assets/javascripts/boards/models/project.js b/app/assets/javascripts/boards/models/project.js new file mode 100644 index 00000000000..a3d5c7af7ac --- /dev/null +++ b/app/assets/javascripts/boards/models/project.js @@ -0,0 +1,6 @@ +export default class IssueProject { + constructor(obj) { + this.id = obj.id; + this.path = obj.path; + } +} diff --git a/app/assets/javascripts/pages/groups/boards/index.js b/app/assets/javascripts/pages/groups/boards/index.js new file mode 100644 index 00000000000..5cfe8723204 --- /dev/null +++ b/app/assets/javascripts/pages/groups/boards/index.js @@ -0,0 +1,9 @@ +import UsersSelect from '~/users_select'; +import ShortcutsNavigation from '~/shortcuts_navigation'; +import initBoards from '~/boards'; + +document.addEventListener('DOMContentLoaded', () => { + new UsersSelect(); // eslint-disable-line no-new + new ShortcutsNavigation(); // eslint-disable-line no-new + initBoards(); +}); diff --git a/app/assets/javascripts/sortable/sortable_config.js b/app/assets/javascripts/sortable/sortable_config.js new file mode 100644 index 00000000000..43ef5d66422 --- /dev/null +++ b/app/assets/javascripts/sortable/sortable_config.js @@ -0,0 +1,7 @@ +export default { + animation: 200, + forceFallback: true, + fallbackClass: 'is-dragging', + fallbackOnBody: true, + ghostClass: 'is-ghost', +}; |