From a8188b643f6aa72558e56823b4012ff0b78b75ee Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 28 Nov 2016 09:28:19 +0000 Subject: Real time issue boards Lists are added automatically when another user adds them. List data is automatically updated, include title, description tooltip & color Closes #24478 --- app/assets/javascripts/boards/boards_bundle.js.es6 | 74 +++++++++++++++++----- .../javascripts/boards/components/board.js.es6 | 3 + app/assets/javascripts/boards/models/list.js.es6 | 43 +++++++++++-- app/views/projects/boards/_show.html.haml | 2 +- changelogs/unreleased/issue-boards-realtime.yml | 4 ++ 5 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/issue-boards-realtime.yml diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 7ba918a05f8..cb41d5f5864 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -48,25 +48,69 @@ $(() => { gl.boardService = new BoardService(this.endpoint, this.boardId); }, mounted () { + const interval = new gl.SmartInterval({ + callback: () => { + this.fetchAll(); + }, + startingInterval: 5 * 1000, // 5 seconds + maxInterval: 10 * 1000, // 10 seconds + incrementByFactorOf: 10, + lazyStart: true, + }); + Store.disabled = this.disabled; - gl.boardService.all() - .then((resp) => { - resp.json().forEach((board) => { - const list = Store.addList(board); - - if (list.type === 'done') { - list.position = Infinity; - } else if (list.type === 'backlog') { - list.position = -1; + + this.fetchAll().then(() => { + this.loading = false; + interval.start(); + }); + }, + methods: { + fetchAll() { + return gl.boardService.all() + .then((resp) => { + const data = resp.json(); + + // Remove any old lists + if (this.state.lists.length) { + const dataListIds = data.map( list => list.id ); + + this.state.lists.forEach((list) => { + if (dataListIds.indexOf(list.id) === -1) { + list.destroy(false); + } + }); } - }); - this.state.lists = _.sortBy(this.state.lists, 'position'); + // Create/Update lists + data.forEach((board) => { + const list = Store.findList('id', board.id, false); - Store.addBlankState(); - this.loading = false; - }); - } + if (list) { + // If list already exists, update the data + list.title = board.title; + + if (board.position !== null) { + list.position = board.position; + } + + if (list.label) { + list.label.description = board.label.description; + list.label.color = board.label.color; + list.label.textColor = board.label.text_color; + } + } else { + // If list doesn't exist, create a new list + Store.addList(board); + } + }); + + this.state.lists = _.sortBy(this.state.lists, 'position'); + + Store.addBlankState(); + }); + }, + }, }); gl.IssueBoardsSearch = new Vue({ diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index 31de3b25284..f4271002743 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -83,5 +83,8 @@ this.sortable = Sortable.create(this.$el.parentNode, options); }, + updated() { + $('.has-tooltip', $(this.$el)).tooltip('fixTitle'); + }, }); })(); diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 429bd27c3fb..aff8dc1931d 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -3,7 +3,6 @@ class List { constructor (obj) { this.id = obj.id; this._uid = this.guid(); - this.position = obj.position; this.title = obj.title; this.type = obj.list_type; this.preset = ['backlog', 'done', 'blank'].indexOf(this.type) > -1; @@ -13,13 +12,33 @@ class List { this.loadingMore = false; this.issues = []; this.issuesSize = 0; + this._interval = new gl.SmartInterval({ + callback: () => { + this.getIssues(false); + }, + startingInterval: 6 * 1000, // 6 seconds + maxInterval: 11 * 1000, // 11 seconds + incrementByFactorOf: 10, + lazyStart: true, + }); + + if (this.type === 'done') { + this.position = Infinity; + } else if (this.type === 'backlog') { + this.position = -1; + } else { + this.position = obj.position; + } if (obj.label) { this.label = new ListLabel(obj.label); } if (this.type !== 'blank' && this.id) { - this.getIssues(); + this.getIssues() + .then(() => { + this._interval.start(); + }); } } @@ -41,12 +60,16 @@ class List { }); } - destroy () { + destroy (persist = true) { const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this); gl.issueBoards.BoardsStore.state.lists.splice(index, 1); gl.issueBoards.BoardsStore.updateNewListDropdown(this.id); - gl.boardService.destroyList(this.id); + this._interval.destroy(); + + if (persist) { + gl.boardService.destroyList(this.id); + } } update () { @@ -102,7 +125,17 @@ class List { createIssues (data) { data.forEach((issueObj) => { - this.addIssue(new ListIssue(issueObj)); + const issue = this.findIssue(issueObj.iid); + + if (issue) { + if (issueObj.assignee) { + issue.assignee = new ListUser(issueObj.assignee); + } else { + issue.assignee = undefined; + } + } else { + this.addIssue(new ListIssue(issueObj)); + } }); } diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index 356bd50f7f3..68a38f646cb 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -24,5 +24,5 @@ ":list" => "list", ":disabled" => "disabled", ":issue-link-base" => "issueLinkBase", - ":key" => "_uid" } + ":key" => "list._uid" } = render "projects/boards/components/sidebar" diff --git a/changelogs/unreleased/issue-boards-realtime.yml b/changelogs/unreleased/issue-boards-realtime.yml new file mode 100644 index 00000000000..596848b6b7b --- /dev/null +++ b/changelogs/unreleased/issue-boards-realtime.yml @@ -0,0 +1,4 @@ +--- +title: Realtime issue boards +merge_request: +author: -- cgit v1.2.1