From 344bbc6325fdd949cd27ec4d1f127adea537f223 Mon Sep 17 00:00:00 2001 From: juanfran Date: Thu, 20 Oct 2016 12:29:35 +0200 Subject: [PATCH] epics pagination --- .../epics-table/epics-table.controller.coffee | 5 ++ .../epics-table.controller.spec.coffee | 8 ++ .../dashboard/epics-table/epics-table.jade | 10 ++- .../dashboard/epics-table/epics-table.scss | 9 +++ app/modules/epics/epics.service.coffee | 49 ++++++++---- app/modules/epics/epics.service.spec.coffee | 74 ++++++++++++++++--- .../resources/epics-resource.service.coffee | 11 ++- 7 files changed, 138 insertions(+), 28 deletions(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 4934b4d9..57967021 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -41,6 +41,8 @@ class EpicsTableController } taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics + taiga.defineImmutableProperty @, 'disabledEpicsPagination', () => return @epicsService._disablePagination + taiga.defineImmutableProperty @, 'loadingEpics', () => return @epicsService._loadingEpics toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions @@ -50,6 +52,9 @@ class EpicsTableController .then null, () => # on error @confirm.notify("error") + nextPage: () -> + @epicsService.nextPage() + hoverEpicTableOption: () -> if @.timer @timeout.cancel(@.timer) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index cdd83c6c..395e6476 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -32,6 +32,7 @@ describe "EpicTable", -> _mockTgEpicsService = () -> mocks.tgEpicsService = { createEpic: sinon.stub() + nextPage: sinon.stub() } provide.value "tgEpicsService", mocks.tgEpicsService @@ -55,3 +56,10 @@ describe "EpicTable", -> epicTableCtrl.displayOptions = true epicTableCtrl.toggleEpicTableOptions() expect(epicTableCtrl.displayOptions).to.be.false + + it "next page", () -> + epicTableCtrl = controller "EpicsTableCtrl" + + epicTableCtrl.nextPage() + + expect(mocks.tgEpicsService.nextPage).to.have.been.calledOnce diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index fe99fcbd..d9c0dfd4 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -86,7 +86,13 @@ mixin epicSwitch(name, model) for="switch-progress" ) +epicSwitch('switch-progress', 'vm.column.progress') - .epics-table-body(tg-epics-sortable="vm.reorderEpic(epic, newIndex)") + + .epics-table-body( + tg-epics-sortable="vm.reorderEpic(epic, newIndex)" + infinite-scroll="vm.nextPage()" + infinite-scroll-disabled="vm.disabledEpicsPagination" + infinite-scroll-immediate-check="false" + ) .epics-table-body-row( tg-repeat="epic in vm.epics track by epic.get('id')" tg-bind-scope @@ -95,3 +101,5 @@ mixin epicSwitch(name, model) epic="epic" column="vm.column" ) + + div(tg-loading="vm.loadingEpics") diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 8814fbc0..d31fe4a8 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -2,6 +2,15 @@ .epics-table { margin-top: 2rem; + .loading { + margin: 2% auto; + width: 3rem; + img { + @include loading-spinner; + max-height: 3rem; + max-width: 3rem; + } + } } .epics-table-header { diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 4095bbf1..cb611601 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -28,19 +28,39 @@ class EpicsService ] constructor: (@projectService, @attachmentsService, @resources, @xhrError) -> - @._epics = Immutable.List() + @.clear() + taiga.defineImmutableProperty @, 'epics', () => return @._epics clear: () -> + @._loadingEpics = false + @._disablePagination = false + @._page = 1 @._epics = Immutable.List() - fetchEpics: () -> - return @resources.epics.list(@projectService.project.get('id')) - .then (epics) => - @._epics = epics + fetchEpics: (reset = false) -> + @._loadingEpics = true + @._disablePagination = true + + return @resources.epics.list(@projectService.project.get('id'), @._page) + .then (result) => + if reset + @.clear() + @._epics = result.list + else + @._epics = @._epics.concat(result.list) + + @._loadingEpics = false + + @._disablePagination = !result.headers('x-pagination-next') .catch (xhr) => @xhrError.response(xhr) + nextPage: () -> + @._page++ + + @.fetchEpics() + listRelatedUserStories: (epic) -> return @resources.userstories.listInEpic(epic.get('id')) @@ -52,8 +72,7 @@ class EpicsService promises = _.map attachments.toJS(), (attachment) => @attachmentsService.upload(attachment.file, epic.get('id'), epic.get('project'), 'epic') - Promise.all(promises).then () => - @.fetchEpics() + Promise.all(promises).then(@.fetchEpics.bind(this, true)) reorderEpic: (epic, newIndex) -> withoutMoved = @.epics.filter (it) => it.get('id') != epic.get('id') @@ -72,9 +91,8 @@ class EpicsService epics_order: newOrder, version: epic.get('version') } + return @resources.epics.reorder(epic.get('id'), data, setOrders) - .then () => - @.fetchEpics() reorderRelatedUserstory: (epic, epicUserstories, userstory, newIndex) -> withoutMoved = epicUserstories.filter (it) => it.get('id') != userstory.get('id') @@ -98,6 +116,13 @@ class EpicsService .then () => return @.listRelatedUserStories(epic) + replaceEpic: (epic) -> + @._epics = @._epics.map (it) -> + if it.get('id') == epic.get('id') + return epic + + return it + updateEpicStatus: (epic, statusId) -> data = { status: statusId, @@ -105,8 +130,7 @@ class EpicsService } return @resources.epics.patch(epic.get('id'), data) - .then () => - @.fetchEpics() + .then(@.replaceEpic.bind(this)) updateEpicAssignedTo: (epic, userId) -> data = { @@ -115,7 +139,6 @@ class EpicsService } return @resources.epics.patch(epic.get('id'), data) - .then () => - @.fetchEpics() + .then(@.replaceEpic.bind(this)) angular.module('taigaEpics').service('tgEpicsService', EpicsService) diff --git a/app/modules/epics/epics.service.spec.coffee b/app/modules/epics/epics.service.spec.coffee index 58efa075..a173ca10 100644 --- a/app/modules/epics/epics.service.spec.coffee +++ b/app/modules/epics/epics.service.spec.coffee @@ -91,13 +91,50 @@ describe "tgEpicsService", -> expect(epicsService._epics.size).to.be.equal(0) it "fetch epics success", () -> - epics = Immutable.fromJS([ + result = {} + result.list = Immutable.fromJS([ { id: 111 } { id: 112 } ]) - promise = mocks.tgResources.epics.list.withArgs(1).promise().resolve(epics) - epicsService.fetchEpics().then () -> - expect(epicsService.epics).to.be.equal(epics) + + result.headers = () -> true + + promise = mocks.tgResources.epics.list.withArgs(1).promise() + + fetchPromise = epicsService.fetchEpics() + + expect(epicsService._loadingEpics).to.be.true + expect(epicsService._disablePagination).to.be.true + + promise.resolve(result) + + fetchPromise.then () -> + expect(epicsService.epics).to.be.equal(result.list) + expect(epicsService._loadingEpics).to.be.false + expect(epicsService._disablePagination).to.be.false + + it "fetch epics success, last page", () -> + result = {} + result.list = Immutable.fromJS([ + { id: 111 } + { id: 112 } + ]) + + result.headers = () -> false + + promise = mocks.tgResources.epics.list.withArgs(1).promise() + + fetchPromise = epicsService.fetchEpics() + + expect(epicsService._loadingEpics).to.be.true + expect(epicsService._disablePagination).to.be.true + + promise.resolve(result) + + fetchPromise.then () -> + expect(epicsService.epics).to.be.equal(result.list) + expect(epicsService._loadingEpics).to.be.false + expect(epicsService._disablePagination).to.be.true it "fetch epics error", () -> epics = Immutable.fromJS([ @@ -108,6 +145,23 @@ describe "tgEpicsService", -> epicsService.fetchEpics().then () -> expect(mocks.tgXhrErrorService.response.withArgs(new Error("error"))).have.been.calledOnce + it "replace epic", () -> + epics = Immutable.fromJS([ + { id: 111 } + { id: 112 } + ]) + + epicsService._epics = epics + + epic = Immutable.Map({ + id: 112, + title: "title1" + }) + + epicsService.replaceEpic(epic) + + expect(epicsService._epics.get(1)).to.be.equal(epic) + it "list related userstories", () -> epic = Immutable.fromJS({ id: 1 @@ -155,9 +209,9 @@ describe "tgEpicsService", -> .promise() .resolve() - epicsService.fetchEpics = sinon.stub() + epicsService.replaceEpic = sinon.stub() epicsService.updateEpicStatus(epic, 33).then () -> - expect(epicsService.fetchEpics).have.been.calledOnce + expect(epicsService.replaceEpic).have.been.calledOnce it "Update epic assigned to", () -> epic = Immutable.fromJS({ @@ -171,9 +225,9 @@ describe "tgEpicsService", -> .promise() .resolve() - epicsService.fetchEpics = sinon.stub() + epicsService.replaceEpic = sinon.stub() epicsService.updateEpicAssignedTo(epic, 33).then () -> - expect(epicsService.fetchEpics).have.been.calledOnce + expect(epicsService.replaceEpic).have.been.calledOnce it "reorder epic", () -> epicsService._epics = Immutable.fromJS([ @@ -199,9 +253,7 @@ describe "tgEpicsService", -> .promise() .resolve() - epicsService.fetchEpics = sinon.stub() - epicsService.reorderEpic(epicsService._epics.get(2), 1).then () -> - expect(epicsService.fetchEpics).have.been.calledOnce + epicsService.reorderEpic(epicsService._epics.get(2), 1) it "reorder related userstory in epic", () -> epic = Immutable.fromJS({ diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 293830b9..30d82791 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -33,13 +33,17 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) - service.list = (projectId) -> + service.list = (projectId, page=0) -> url = urlsService.resolve("epics") - params = {project: projectId} + params = {project: projectId, page: page} return http.get(url, params) - .then (result) -> Immutable.fromJS(result.data) + .then (result) -> + return { + list: Immutable.fromJS(result.data) + headers: result.headers + } service.patch = (id, patch) -> url = urlsService.resolve("epics") + "/#{id}" @@ -59,6 +63,7 @@ Resource = (urlsService, http) -> options = {"headers": {"set-orders": JSON.stringify(setOrders)}} return http.patch(url, data, null, options) + .then (result) -> Immutable.fromJS(result.data) service.addRelatedUserstory = (epicId, userstoryId) -> url = urlsService.resolve("epic-related-userstories", epicId)