From 86c7186bbfd34fa3932f8e51533e8501761d7a93 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 20 Jul 2016 13:45:21 +0200 Subject: [PATCH 01/64] Add action support to PodApi angular service --- karma/test-services.coffee | 17 +++++++++++++++++ static/coffee/services.coffee | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index e1ea660ec..74ede6806 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -634,5 +634,22 @@ describe('services:', () -> $httpBackend.flush() ) ) + + describe('trigger', () -> + it('triggers an action', () -> + $httpBackend.expectPUT('/pods/action/21/', { + data: { + type: 'foo' + payload: {bar: 23} + } + }) + .respond({foo: 'bar'}) + + PodApi.trigger(21, 'foo', {bar: 23}) + .then((res) -> expect(res).toEqual({foo: 'bar'})) + + $httpBackend.flush() + ) + ) ) ) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index a9d892626..6c778bb6b 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -549,4 +549,13 @@ services.factory('PodApi', ['$window', '$http', ($window, $http) -> get: (podId, caseId) -> $http.get("/pods/read/#{podId}/", {params: {case_id: caseId}}) .then((d) -> d.data) + + trigger: (podId, type, payload = {}) -> + $http.put("/pods/action/#{podId}/", { + data: { + type + payload + } + }) + .then((d) -> d.data) ]) From 40dbd1380ff866e368d80373030a07681656d2d3 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 20 Jul 2016 15:15:45 +0200 Subject: [PATCH 02/64] Add commas for separating object properties in PodApi service and its tests --- karma/test-services.coffee | 2 +- static/coffee/services.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index 74ede6806..c6840f4f3 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -639,7 +639,7 @@ describe('services:', () -> it('triggers an action', () -> $httpBackend.expectPUT('/pods/action/21/', { data: { - type: 'foo' + type: 'foo', payload: {bar: 23} } }) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index 6c778bb6b..770cc1caa 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -553,7 +553,7 @@ services.factory('PodApi', ['$window', '$http', ($window, $http) -> trigger: (podId, type, payload = {}) -> $http.put("/pods/action/#{podId}/", { data: { - type + type, payload } }) From b901c3f84441c78903bdf506cb1f31a35de00f31 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 20 Jul 2016 17:59:05 +0200 Subject: [PATCH 03/64] Change base pod directive to draw actions --- karma/test-directives.coffee | 62 +++++++++++++++++++++++++++++++++++- static/templates/pod.html | 27 +++++++++++++--- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 8216d02e2..9c69f17ad 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -69,7 +69,12 @@ describe('directives:', () -> $compile = _$compile_ $rootScope.podConfig = {title: 'Foo'} - $rootScope.podData = {items: []} + $rootScope.podData = { + items: [], + actions: [] + } + + $rootScope.trigger = -> )) it('should draw the pod items', () -> @@ -110,5 +115,60 @@ describe('directives:', () -> expect(item2.querySelector('.pod-item-value').textContent) .toContain('Corge') ) + + it('should draw the pod actions', -> + $rootScope.podData.actions = [{ + case_id: 21, + type: 'foo', + name: 'Foo', + payload: {bar: 'baz'} + }, { + case_id: 21, + type: 'quux', + name: 'Quux', + payload: {corge: 'grault'} + }] + + el = $compile('
')($rootScope)[0] + $rootScope.$digest() + + action1 = el.querySelectorAll('.pod-action')[0] + action2 = el.querySelectorAll('.pod-action')[1] + + expect(action1.textContent).toContain('Foo') + expect(action2.textContent).toContain('Quux') + ) + + it('should call trigger() when an action button is clicked', -> + $rootScope.podData.actions = [{ + type: 'foo', + name: 'Foo', + case_id: 21, + payload: {a: 'b'} + }, { + type: 'bar', + name: 'Bar', + case_id: 21, + payload: {c: 'd'} + }] + + $rootScope.trigger = jasmine.createSpy('trigger') + + el = $compile('
')($rootScope)[0] + $rootScope.$digest() + + action1 = el.querySelectorAll('.pod-action')[0] + action2 = el.querySelectorAll('.pod-action')[1] + + expect($rootScope.trigger).not.toHaveBeenCalledWith(21, 'foo', {a: 'b'}) + + angular.element(action1).triggerHandler('click') + + expect($rootScope.trigger).toHaveBeenCalledWith(21, 'foo', {a: 'b'}) + expect($rootScope.trigger).not.toHaveBeenCalledWith(21, 'bar', {c: 'd'}) + + angular.element(action2).triggerHandler('click') + expect($rootScope.trigger).toHaveBeenCalledWith(21, 'bar', {c: 'd'}) + ) ) ) diff --git a/static/templates/pod.html b/static/templates/pod.html index db887b193..debc34d33 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -1,12 +1,31 @@
{{ podConfig.title }}
-
+
-
-
{{ item.name }}
-
{{ item.value }}
+
+
+
+
+
{{ item.name }}
+
+ +
+
{{ item.value }}
+
+
+
+ +
From 526afad652eedcc5f088c2625b4560d13b8cb9c2 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 20 Jul 2016 17:59:30 +0200 Subject: [PATCH 04/64] Fix padding for items in pod body --- static/less/pods.less | 5 +++++ templates/cases/case_read.haml | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 static/less/pods.less diff --git a/static/less/pods.less b/static/less/pods.less new file mode 100644 index 000000000..748965f1b --- /dev/null +++ b/static/less/pods.less @@ -0,0 +1,5 @@ +.panel-body-pod { + // pod body already has padding defined by the grid it uses for its items + padding-left: 0; + padding-right: 0; +} diff --git a/templates/cases/case_read.haml b/templates/cases/case_read.haml index a87f4dd25..685ea5be2 100644 --- a/templates/cases/case_read.haml +++ b/templates/cases/case_read.haml @@ -168,6 +168,12 @@ {{ block.super }} - compress css + %link{ + type:"text/less", + rel:"stylesheet", + href:"{{ STATIC_URL }}less/pods.less" + } + - for pod_type in pod_types - for style in pod_type.styles %link{ From f0a00d49f879998a164894c3efed3dd692e5710d Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 20 Jul 2016 18:00:33 +0200 Subject: [PATCH 05/64] Fix multi-line attribute spacing for pod template --- static/templates/pod.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/templates/pod.html b/static/templates/pod.html index debc34d33..35845bb1e 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -5,7 +5,8 @@
-
{{ item.name }}
@@ -21,7 +22,8 @@
- From 054db41dcd76dc6ec32f724959af34087f41400d Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 10:50:36 +0200 Subject: [PATCH 06/64] Don't call action trigger function with case ids in pod directive --- karma/test-directives.coffee | 12 ++++-------- static/templates/pod.html | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 9c69f17ad..1e3f6a846 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -118,12 +118,10 @@ describe('directives:', () -> it('should draw the pod actions', -> $rootScope.podData.actions = [{ - case_id: 21, type: 'foo', name: 'Foo', payload: {bar: 'baz'} }, { - case_id: 21, type: 'quux', name: 'Quux', payload: {corge: 'grault'} @@ -143,12 +141,10 @@ describe('directives:', () -> $rootScope.podData.actions = [{ type: 'foo', name: 'Foo', - case_id: 21, payload: {a: 'b'} }, { type: 'bar', name: 'Bar', - case_id: 21, payload: {c: 'd'} }] @@ -160,15 +156,15 @@ describe('directives:', () -> action1 = el.querySelectorAll('.pod-action')[0] action2 = el.querySelectorAll('.pod-action')[1] - expect($rootScope.trigger).not.toHaveBeenCalledWith(21, 'foo', {a: 'b'}) + expect($rootScope.trigger).not.toHaveBeenCalledWith('foo', {a: 'b'}) angular.element(action1).triggerHandler('click') - expect($rootScope.trigger).toHaveBeenCalledWith(21, 'foo', {a: 'b'}) - expect($rootScope.trigger).not.toHaveBeenCalledWith(21, 'bar', {c: 'd'}) + expect($rootScope.trigger).toHaveBeenCalledWith('foo', {a: 'b'}) + expect($rootScope.trigger).not.toHaveBeenCalledWith('bar', {c: 'd'}) angular.element(action2).triggerHandler('click') - expect($rootScope.trigger).toHaveBeenCalledWith(21, 'bar', {c: 'd'}) + expect($rootScope.trigger).toHaveBeenCalledWith('bar', {c: 'd'}) ) ) ) diff --git a/static/templates/pod.html b/static/templates/pod.html index 35845bb1e..6358b43ff 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -26,7 +26,7 @@ href="#" class="pod-action list-group-item" ng-repeat="action in podData.actions" - ng-click="trigger(action.case_id, action.type, action.payload)"> + ng-click="trigger(action.type, action.payload)"> {{ action.name }}
From a5e7be4fae586423f6a62c1ec22afe4a468b1186 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 11:44:56 +0200 Subject: [PATCH 07/64] Update PodApi service sending of actions request to match api change --- karma/test-services.coffee | 15 +++++++++------ static/coffee/services.coffee | 11 +++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index c6840f4f3..4ffbe1139 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -637,16 +637,19 @@ describe('services:', () -> describe('trigger', () -> it('triggers an action', () -> - $httpBackend.expectPUT('/pods/action/21/', { + $httpBackend.expectPOST('/pods/action/21/', { data: { - type: 'foo', - payload: {bar: 23} + case_id: 23, + action: { + type: 'foo', + payload: {bar: 'baz'} + } } }) - .respond({foo: 'bar'}) + .respond({quux: 'corge'}) - PodApi.trigger(21, 'foo', {bar: 23}) - .then((res) -> expect(res).toEqual({foo: 'bar'})) + PodApi.trigger(21, 23, 'foo', {bar: 'baz'}) + .then((res) -> expect(res).toEqual({quux: 'corge'})) $httpBackend.flush() ) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index 770cc1caa..613a1cf60 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -550,11 +550,14 @@ services.factory('PodApi', ['$window', '$http', ($window, $http) -> $http.get("/pods/read/#{podId}/", {params: {case_id: caseId}}) .then((d) -> d.data) - trigger: (podId, type, payload = {}) -> - $http.put("/pods/action/#{podId}/", { + trigger: (podId, caseId, type, payload = {}) -> + $http.post("/pods/action/#{podId}/", { data: { - type, - payload + case_id: caseId + action: { + type, + payload + } } }) .then((d) -> d.data) From c71557084024294526bdf46a840ed0c7129ed433 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 15:52:59 +0200 Subject: [PATCH 08/64] Refactor base pod controller's updating of pod data into its own method --- static/coffee/controllers.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index bc8c163c3..079163122 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -813,7 +813,9 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.podId = podId $scope.caseId = caseId $scope.podConfig = podConfig + $scope.update() - return PodApi.get(podId, caseId) + $scope.update = -> + PodApi.get($scope.podId, $scope.caseId) .then((d) -> $scope.podData = d) ]) From ddc121008fe2acaa1db500d5879ccff3ad5ab429 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 15:55:43 +0200 Subject: [PATCH 09/64] Update base pod directive's action tests to instantiate directive as a void element --- karma/test-directives.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 1e3f6a846..4d19ee154 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -127,7 +127,7 @@ describe('directives:', () -> payload: {corge: 'grault'} }] - el = $compile('
')($rootScope)[0] + el = $compile('')($rootScope)[0] $rootScope.$digest() action1 = el.querySelectorAll('.pod-action')[0] @@ -150,7 +150,7 @@ describe('directives:', () -> $rootScope.trigger = jasmine.createSpy('trigger') - el = $compile('
')($rootScope)[0] + el = $compile('')($rootScope)[0] $rootScope.$digest() action1 = el.querySelectorAll('.pod-action')[0] From aafcd492e157f021056ab926314f2be77e53cf81 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 17:15:29 +0200 Subject: [PATCH 10/64] Update base pod controller to trigger actions --- karma/test-controllers.coffee | 55 +++++++++++++++++++++++++++++--- static/coffee/controllers.coffee | 15 +++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 044b0e327..97be0198c 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -674,16 +674,18 @@ describe('controllers:', () -> describe('PodController', () -> $scope = null + PodApi = null beforeEach(() -> $scope = $rootScope.$new() + + PodApi = new class PodApi + get: -> $q.resolve({foo: 'bar'}) + trigger: -> $q.resolve({success: true}) ) describe('init', () -> - it('should attach pod data to the scope', () -> - PodApi = new class PodApi - get: -> - + it('should fetch and attach pod data to the scope', () -> spyOn(PodApi, 'get').and.returnValue($q.resolve({foo: 'bar'})) $controller('PodController', { @@ -698,7 +700,50 @@ describe('controllers:', () -> expect($scope.caseId).toEqual(23) expect($scope.podConfig).toEqual({title: 'Baz'}) expect(PodApi.get).toHaveBeenCalledWith(21, 23) - expect($scope.podData).toEqual({foo: 'bar'})) + expect($scope.podData).toEqual({foo: 'bar'}) + ) + ) + + describe('trigger', () -> + it('should trigger the given action', () -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + $scope.podData = {bar: 'baz'} + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) + + $scope.trigger('grault', {garply: 'waldo'}) + $scope.$apply() + + expect(PodApi.trigger) + .toHaveBeenCalledWith(21, 23, 'grault', {garply: 'waldo'}) + ) + + it('should fetch and attach data to the scope if successful', () -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + $scope.podData = {bar: 'baz'} + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'get').and.returnValue($q.resolve({quux: 'corge'})) + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) + + $scope.trigger('grault', {garply: 'waldo'}) + $scope.$apply() + + expect($scope.podData).toEqual({quux: 'corge'}) + ) ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 079163122..fb134d295 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -818,4 +818,19 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.update = -> PodApi.get($scope.podId, $scope.caseId) .then((d) -> $scope.podData = d) + + $scope.trigger = (type, payload) -> + PodApi.trigger($scope.podId, $scope.caseId, type, payload) + .then(({success, payload}) -> + if success + $scope.onTriggerSuccess() + else + $scope.onTriggerFailure(payload)) + + $scope.onTriggerFailure = (payload) -> + # TODO show failure message + + $scope.onTriggerSuccess = () -> + # TODO update notes + $scope.update() ]) From 713ec63b658918fdf48a589553ff38bd2b7ac9ad Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 18:05:23 +0200 Subject: [PATCH 11/64] Fix action request body in PodApi service --- karma/test-services.coffee | 10 ++++------ static/coffee/services.coffee | 16 +++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index 4ffbe1139..a0f39145a 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -638,12 +638,10 @@ describe('services:', () -> describe('trigger', () -> it('triggers an action', () -> $httpBackend.expectPOST('/pods/action/21/', { - data: { - case_id: 23, - action: { - type: 'foo', - payload: {bar: 'baz'} - } + case_id: 23, + action: { + type: 'foo', + payload: {bar: 'baz'} } }) .respond({quux: 'corge'}) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index 613a1cf60..650b10074 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -552,13 +552,11 @@ services.factory('PodApi', ['$window', '$http', ($window, $http) -> trigger: (podId, caseId, type, payload = {}) -> $http.post("/pods/action/#{podId}/", { - data: { - case_id: caseId - action: { - type, - payload - } - } - }) - .then((d) -> d.data) + case_id: caseId + action: { + type, + payload + } + }) + .then((d) -> d.data) ]) From a4fabaca2ec98603b5e312f9864e396d2d2dd7ae Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 18:26:17 +0200 Subject: [PATCH 12/64] Fix pod action anchor tags to not make the page jump --- static/templates/pod.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/templates/pod.html b/static/templates/pod.html index 6358b43ff..59c315a28 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -23,7 +23,7 @@
From 459cac89c43058dfa6944c1c8412c77d5a2fcde3 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 21 Jul 2016 18:33:10 +0200 Subject: [PATCH 13/64] Update case timeline after pod action trigger --- karma/test-controllers.coffee | 24 ++++++++++++++++++++++++ static/coffee/controllers.coffee | 4 +++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 97be0198c..01d68aceb 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -84,6 +84,11 @@ describe('controllers:', () -> expect($scope.contact).toEqual(test.ann) ) + it('should should broadcast timelineChanged on podActionSuccess', (done) -> + $scope.$on('timelineChanged', -> done()) + $scope.$emit('podActionSuccess') + ) + it('addNote', () -> noteModal = spyOnPromise($q, $scope, UtilsService, 'noteModal') addNote = spyOnPromise($q, $scope, CaseService, 'addNote') @@ -725,6 +730,25 @@ describe('controllers:', () -> .toHaveBeenCalledWith(21, 23, 'grault', {garply: 'waldo'}) ) + it('should emit a podActionSuccess event if successful', (done) -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + $scope.podData = {bar: 'baz'} + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) + $scope.trigger('grault', {garply: 'waldo'}) + + $scope.$on('podActionSuccess', -> done()) + + $scope.$apply() + ) + it('should fetch and attach data to the scope if successful', () -> $scope.podId = 21 $scope.caseId = 23 diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index fb134d295..055f2e1d9 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -502,6 +502,8 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.refresh() + $scope.$on('podActionSuccess', -> $scope.$broadcast('timelineChanged')) + $scope.refresh = () -> CaseService.fetchSingle($scope.caseId).then((caseObj) -> $scope.caseObj = caseObj @@ -831,6 +833,6 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> # TODO show failure message $scope.onTriggerSuccess = () -> - # TODO update notes + $scope.$emit('podActionSuccess') $scope.update() ]) From 38ceb6d04f7b09b3ab29468c2fa9ccc94e1b5dc5 Mon Sep 17 00:00:00 2001 From: Rudi Giesler Date: Fri, 22 Jul 2016 09:59:27 +0200 Subject: [PATCH 14/64] Change case field to case_id in error message --- casepro/pods/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/casepro/pods/views.py b/casepro/pods/views.py index aa16dab86..a5daafd52 100644 --- a/casepro/pods/views.py +++ b/casepro/pods/views.py @@ -3,6 +3,7 @@ import json from django.http import JsonResponse +from casepro.cases.models import Case, CaseAction from casepro.pods import registry @@ -34,4 +35,15 @@ def perform_pod_action(request, index): except ValueError as e: return JsonResponse({'reason': 'JSON decode error', 'details': e.message}, status=400) + case_id = data.get('case_id') + if case_id is None: + return JsonResponse( + {'reason': 'Request object needs to have a "case_id" field'}, status=400) + + action_data = data.get('action', {}) + success, payload = pod.perform_action(action_data.get('type'), action_data.get('payload', {})) + if success is True: + case = Case.objects.get(id=case_id) + CaseAction.create(case, request.user, CaseAction.ADD_NOTE, note=payload.get('message')) + return JsonResponse(pod.perform_action(data)) From fd88660c912d23d0b40dc6e9bace8341659ac37e Mon Sep 17 00:00:00 2001 From: Rudi Giesler Date: Thu, 21 Jul 2016 13:22:15 +0200 Subject: [PATCH 15/64] Add docstring for pod read_data format --- casepro/pods/base.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/casepro/pods/base.py b/casepro/pods/base.py index 548259ed0..5e0e94245 100644 --- a/casepro/pods/base.py +++ b/casepro/pods/base.py @@ -32,7 +32,35 @@ def config_json(self): return json.dumps(self.config._config_data) def read_data(self, params): - """Should return the data that should be used to create the display for the pod.""" + """ + Should return the data that should be used to create the display for the pod. + + For the base implementation, the data should be an object with 'items' and 'actions' keys. + + The items key should be a list of objects, that have 'name' and 'value' keys, with the value of the keys being + what will be displayed. + + The 'actions' key should be a list of objects, that have 'type', 'name' and 'payload' keys, where type and + payload is what is sent to the 'perform_action' function to determine which button has been pressed, and 'name' + is the text that is displayed on the button. + + Example: + { + 'items': [ + { + 'name': 'EDD', + 'value': '2015-07-18', + }, + ], + 'actions': [ + { + 'type': 'remove_edd', + 'name': 'Remove EDD', + 'payload': {}, + }, + ], + } + """ return {} def perform_action(self, params): From eda494aad7d1fd0adf429cc0ac71be4d5d1510f2 Mon Sep 17 00:00:00 2001 From: Rudi Giesler Date: Thu, 21 Jul 2016 13:23:17 +0200 Subject: [PATCH 16/64] Add docstring to pod perform_action to describe format --- casepro/pods/base.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/casepro/pods/base.py b/casepro/pods/base.py index 5e0e94245..aa14bab59 100644 --- a/casepro/pods/base.py +++ b/casepro/pods/base.py @@ -63,9 +63,17 @@ def read_data(self, params): """ return {} - def perform_action(self, params): - """Should perform the action specified by params.""" - return {} + def perform_action(self, type_, params): + """ + Should perform the action specified by the type and params (which are specified in the read function). + + Returns a tuple (success, payload), where 'success' is a boolean value indicating whether the action was + successful or not. If true, a case action note will be created. + + For the base implementation, payload is an object with a 'message' key, which is the error message if success + is false, or the message to place in the case action note if success is true. + """ + return (False, {'message': ''}) class PodPlugin(AppConfig): From 47d7a4aa5d752892b0773345a8892d41868d5fe2 Mon Sep 17 00:00:00 2001 From: Rudi Giesler Date: Thu, 21 Jul 2016 13:23:55 +0200 Subject: [PATCH 17/64] Create case action note if action was successful --- casepro/pods/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/casepro/pods/views.py b/casepro/pods/views.py index a5daafd52..445da709a 100644 --- a/casepro/pods/views.py +++ b/casepro/pods/views.py @@ -21,7 +21,10 @@ def read_pod_data(request, index): def perform_pod_action(request, index): - """Deletegates to the `perform_action` function of the correct pod.""" + """ + Delegates to the `perform_action` function of the correct pod. If the action completes successfully, a new case + action note is created with the success message. + """ if request.method != 'POST': return JsonResponse({'reason': 'Method not allowed'}, status=405) @@ -46,4 +49,4 @@ def perform_pod_action(request, index): case = Case.objects.get(id=case_id) CaseAction.create(case, request.user, CaseAction.ADD_NOTE, note=payload.get('message')) - return JsonResponse(pod.perform_action(data)) + return JsonResponse({'success': success, 'payload': payload}) From 21903d1fe6977a32a6aa7d0bf63854f9053d4bad Mon Sep 17 00:00:00 2001 From: Rudi Giesler Date: Thu, 21 Jul 2016 13:24:27 +0200 Subject: [PATCH 18/64] Update tests for pod action changes --- casepro/pods/tests/test_views.py | 68 +++++++++++++++++++++++++++++++- casepro/pods/tests/utils.py | 13 ++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/casepro/pods/tests/test_views.py b/casepro/pods/tests/test_views.py index 51827926c..6dd1bf504 100644 --- a/casepro/pods/tests/test_views.py +++ b/casepro/pods/tests/test_views.py @@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse from django.test import modify_settings +from casepro.cases.models import CaseAction from casepro.test import BaseCasesTest @@ -100,6 +101,21 @@ def test_invalid_json(self): 'details': 'No JSON object could be decoded' }) + def test_case_id_required(self): + ''' + If the case id is not present in the request, an error response should be returned. + ''' + with self.settings(PODS=[{'label': 'base_pod'}]): + from casepro.pods import registry + reload(registry) + + response = self.url_post_json( + 'unicef', reverse('perform_pod_action', args=('0',)), {}) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json, { + 'reason': 'Request object needs to have a "case" field' + }) + def test_pod_valid_request(self): """ If it is a valid post request, the action should be performed. @@ -107,7 +123,55 @@ def test_pod_valid_request(self): with self.settings(PODS=[{'label': 'base_pod'}]): from casepro.pods import registry reload(registry) + + contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name') + msg = self.create_message(self.unicef, 0, contact, 'Test message') + case = self.create_case(self.unicef, contact, self.moh, msg) + response = self.url_post_json( - 'unicef', reverse('perform_pod_action', args=('0',)), {}) + 'unicef', reverse('perform_pod_action', args=('0',)), {'case_id': case.id}) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json, {}) + self.assertEqual(response.json, { + 'success': False, + 'payload': { + 'message': '', + } + }) + + @modify_settings(INSTALLED_APPS={ + 'append': 'casepro.pods.tests.utils.SuccessActionPlugin', + }) + def test_case_action_note_created_on_successful_action(self): + ''' + If the action is successful, a case action note should be created. + ''' + CaseAction.objects.all().delete() + self.login(self.admin) + + with self.settings(PODS=[{'label': 'success_pod'}]): + from casepro.pods import registry + reload(registry) + + contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name') + msg = self.create_message(self.unicef, 0, contact, 'Test message') + case = self.create_case(self.unicef, contact, self.moh, msg) + + response = self.url_post_json( + 'unicef', reverse('perform_pod_action', args=('0',)), { + 'case_id': case.id, + 'action': { + 'type': 'foo', + 'payload': {'foo': 'bar'}, + }, + }) + + self.assertEqual(response.status_code, 200) + message = "Type foo Params {u'foo': u'bar'}" + self.assertEqual(response.json, { + 'success': True, + 'payload': { + 'message': message + } + }) + [caseaction] = CaseAction.objects.all() + self.assertEqual(caseaction.note, message) diff --git a/casepro/pods/tests/utils.py b/casepro/pods/tests/utils.py index a89db7933..482066eb2 100644 --- a/casepro/pods/tests/utils.py +++ b/casepro/pods/tests/utils.py @@ -14,3 +14,16 @@ class DummyPodPlugin(PodPlugin): directive = 'dummy-pod' scripts = ('dummy-script.coffee',) styles = ('dummy-style.less',) + + +class SuccessActionPod(Pod): + def perform_action(self, type_, params): + ''' + Returns a successful action result with a message containing the type and params. + ''' + return (True, {'message': 'Type %s Params %r' % (type_, params)}) + + +class SuccessActionPlugin(DummyPodPlugin): + pod_class = SuccessActionPod + label = 'success_pod' From 477bcbcdcdff32936a375714cbbbc1404d4f07e1 Mon Sep 17 00:00:00 2001 From: Rudi Giesler Date: Fri, 22 Jul 2016 10:21:12 +0200 Subject: [PATCH 19/64] Fix expected error message in test --- casepro/pods/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casepro/pods/tests/test_views.py b/casepro/pods/tests/test_views.py index 6dd1bf504..1b03db300 100644 --- a/casepro/pods/tests/test_views.py +++ b/casepro/pods/tests/test_views.py @@ -113,7 +113,7 @@ def test_case_id_required(self): 'unicef', reverse('perform_pod_action', args=('0',)), {}) self.assertEqual(response.status_code, 400) self.assertEqual(response.json, { - 'reason': 'Request object needs to have a "case" field' + 'reason': 'Request object needs to have a "case_id" field' }) def test_pod_valid_request(self): From e73cba4456fae8696e462376ace15686c557294f Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 22 Jul 2016 10:46:47 +0200 Subject: [PATCH 20/64] Use [[s for angular templates --- static/templates/pod.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/templates/pod.html b/static/templates/pod.html index 59c315a28..585738eb8 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -1,5 +1,5 @@
From 34519690b02607fd7358c12fd71f7c3b2a398bd6 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 22 Jul 2016 17:30:19 +0200 Subject: [PATCH 21/64] Add notifications directive --- karma/test-directives.coffee | 62 +++++++++++++++++++++++++++++ static/coffee/directives.coffee | 6 +++ static/templates/notifications.html | 14 +++++++ templates/cases/case_read.haml | 2 + 4 files changed, 84 insertions(+) create mode 100644 static/templates/notifications.html diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 2ced93977..13784d226 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -111,4 +111,66 @@ describe('directives:', () -> .toContain('Corge') ) ) + + #======================================================================= + # Tests for pod + #======================================================================= + describe('cpNotifications', () -> + $rootScope = null + $compile = null + + beforeEach(inject((_$rootScope_, _$compile_) -> + $rootScope = _$rootScope_ + $compile = _$compile_ + + $rootScope.notifications = [] + $rootScope.removeNotification = -> + )) + + it('should draw the notifications', () -> + $rootScope.notifications = [{ + type: 'danger', + message: 'Foo' + }, { + type: 'success', + message: 'Bar' + }] + + el = $compile('')($rootScope)[0] + $rootScope.$digest() + + notification1 = el.querySelector('.alert:nth-child(1)') + notification2 = el.querySelector('.alert:nth-child(2)') + + expect(notification1.classList.contains('alert-danger')).toBe(true) + expect(notification1.textContent).toMatch('Foo') + + expect(notification2.classList.contains('alert-success')).toBe(true) + expect(notification2.textContent).toMatch('Bar') + ) + + it('should call removeNotification() when a notification is closed', -> + $rootScope.notifications = [{ + type: 'danger', + message: 'Foo' + }, { + type: 'success', + message: 'Bar' + }] + + $rootScope.removeNotification = jasmine.createSpy('removeNotification') + + el = $compile('')($rootScope)[0] + $rootScope.$digest() + + angular.element(el.querySelector('.alert:nth-child(2) .close')) + .triggerHandler('click') + + expect($rootScope.removeNotification.calls.mostRecent().args[0]) + .toEqual(jasmine.objectContaining({ + type: 'success', + message: 'Bar' + })) + ) + ) ) diff --git a/static/coffee/directives.coffee b/static/coffee/directives.coffee index 84a189d41..fc9690f2a 100644 --- a/static/coffee/directives.coffee +++ b/static/coffee/directives.coffee @@ -31,6 +31,12 @@ directives.directive('cpContact', () -> } ) + +directives.directive('cpNotifications', -> { + templateUrl: -> '/sitestatic/templates/notifications.html' +}) + + #===================================================================== # Pod directive #===================================================================== diff --git a/static/templates/notifications.html b/static/templates/notifications.html new file mode 100644 index 000000000..85c632984 --- /dev/null +++ b/static/templates/notifications.html @@ -0,0 +1,14 @@ +
+ + + + [[ notification.message ]] +
diff --git a/templates/cases/case_read.haml b/templates/cases/case_read.haml index 7764948dc..2abf0b76d 100644 --- a/templates/cases/case_read.haml +++ b/templates/cases/case_read.haml @@ -66,6 +66,8 @@ .row .col-md-8 + + %form.sendform{ ng-if:"!caseObj.is_closed" } // TODO maxlength not working %textarea.form-control{ type:"text", autocomplete:"off", placeholder:"Enter message", ng-maxlength:"{{ maxMsgChars }}", ng-model:"$parent.newMessage", ng-disabled:"sending", ng-change:"onNewMessageChanged()", ng-trim:"false" } From 779c6a7469ea61b476905b98dd58c43f5676697d Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 22 Jul 2016 17:41:21 +0200 Subject: [PATCH 22/64] Change pod controller to emit event on pod action failure --- karma/test-controllers.coffee | 18 ++++++++++++++++++ static/coffee/controllers.coffee | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index a7ed909bf..12cfe085b 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -729,6 +729,24 @@ describe('controllers:', () -> .toHaveBeenCalledWith(21, 23, 'grault', {garply: 'waldo'}) ) + it('should emit a podActionFailure event if successful', (done) -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + $scope.podData = {bar: 'baz'} + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: false})) + $scope.trigger('grault', {garply: 'waldo'}) + + $scope.$on('podActionFailure', -> done()) + $scope.$apply() + ) + it('should fetch and attach data to the scope if successful', () -> $scope.podId = 21 $scope.caseId = 23 diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index e82dd84e1..74eff57d9 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -840,7 +840,7 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.onTriggerFailure(payload)) $scope.onTriggerFailure = (payload) -> - # TODO show failure message + $scope.$emit('podActionFailure', payload) $scope.onTriggerSuccess = () -> # TODO update notes From 542b5a73f527d772217e3b6e2cc2ffe9c217dd0a Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 22 Jul 2016 17:43:11 +0200 Subject: [PATCH 23/64] Change CaseController to add a notification on pod action failure --- karma/test-controllers.coffee | 11 +++++++++++ static/coffee/controllers.coffee | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 12cfe085b..b5519913b 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -84,6 +84,17 @@ describe('controllers:', () -> expect($scope.contact).toEqual(test.ann) ) + it('should should add a notification on podActionFailure', () -> + $scope.notifications = [] + + $scope.$emit('podActionFailure', {message: 'o_O'}) + + expect($scope.notifications).toEqual([{ + type: 'danger', + message: 'o_O' + }]) + ) + it('addNote', () -> noteModal = spyOnPromise($q, $scope, UtilsService, 'noteModal') addNote = spyOnPromise($q, $scope, CaseService, 'addNote') diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 74eff57d9..f0bbdc444 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -510,6 +510,7 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.contact = null $scope.newMessage = '' $scope.sending = false + $scope.notifications = [] $scope.init = (caseId, maxMsgChars) -> $scope.caseId = caseId @@ -517,6 +518,19 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.refresh() + $scope.$on('podActionFailure', (e, {message}) -> + $scope.addNotification({ + type: 'danger', + message + })) + + $scope.addNotification = (notification) -> + $scope.notifications.push(notification) + + $scope.removeNotification = (notification) -> + $scope.notifications = $scope.notifications + .filter((d) -> d != notification) + $scope.refresh = () -> CaseService.fetchSingle($scope.caseId).then((caseObj) -> $scope.caseObj = caseObj From 085b46926e8eef86007368f64a41e28e5f6fb0d0 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 22 Jul 2016 18:16:07 +0200 Subject: [PATCH 24/64] Parse fetched pod data in pod controller to add busy flags to action data --- karma/test-controllers.coffee | 58 ++++++++++++++++++++++++++++---- static/coffee/controllers.coffee | 15 +++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index a7ed909bf..151e84f5e 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -684,13 +684,26 @@ describe('controllers:', () -> $scope = $rootScope.$new() PodApi = new class PodApi - get: -> $q.resolve({foo: 'bar'}) + get: -> $q.resolve({ + items: [], + actions: [] + }) trigger: -> $q.resolve({success: true}) ) describe('init', () -> it('should fetch and attach pod data to the scope', () -> - spyOn(PodApi, 'get').and.returnValue($q.resolve({foo: 'bar'})) + spyOn(PodApi, 'get').and.returnValue($q.resolve({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + payload: {} + }] + })) $controller('PodController', { $scope @@ -704,7 +717,18 @@ describe('controllers:', () -> expect($scope.caseId).toEqual(23) expect($scope.podConfig).toEqual({title: 'Baz'}) expect(PodApi.get).toHaveBeenCalledWith(21, 23) - expect($scope.podData).toEqual({foo: 'bar'}) + expect($scope.podData).toEqual({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + isBusy: false, + payload: {} + }] + }) ) ) @@ -713,7 +737,6 @@ describe('controllers:', () -> $scope.podId = 21 $scope.caseId = 23 $scope.podConfig = {title: 'Foo'} - $scope.podData = {bar: 'baz'} $controller('PodController', { $scope @@ -733,20 +756,41 @@ describe('controllers:', () -> $scope.podId = 21 $scope.caseId = 23 $scope.podConfig = {title: 'Foo'} - $scope.podData = {bar: 'baz'} $controller('PodController', { $scope PodApi }) - spyOn(PodApi, 'get').and.returnValue($q.resolve({quux: 'corge'})) + spyOn(PodApi, 'get').and.returnValue($q.resolve({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + payload: {} + }] + })) + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) $scope.trigger('grault', {garply: 'waldo'}) $scope.$apply() - expect($scope.podData).toEqual({quux: 'corge'}) + expect($scope.podData).toEqual({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + isBusy: false, + payload: {} + }] + }) ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index e82dd84e1..b7d32bd24 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -829,6 +829,7 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.update = -> PodApi.get($scope.podId, $scope.caseId) + .then(parsePodData) .then((d) -> $scope.podData = d) $scope.trigger = (type, payload) -> @@ -845,4 +846,18 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.onTriggerSuccess = () -> # TODO update notes $scope.update() + + parsePodData = (d) -> + d = angular.extend({ + items: [], + actions: [] + }, d) + + d.actions = d.actions + .map(parsePodAction) + + d + + parsePodAction = (d) -> + angular.extend({isBusy: false}, d) ]) From d2a7816827503cab42e4984331815dd1c606e293 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 22 Jul 2016 18:44:48 +0200 Subject: [PATCH 25/64] Change pod controller to set actions to busy when triggered --- karma/test-controllers.coffee | 41 ++++++++++++++++++++++++++++++++ static/coffee/controllers.coffee | 20 +++++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 151e84f5e..c58350102 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -738,6 +738,11 @@ describe('controllers:', () -> $scope.caseId = 23 $scope.podConfig = {title: 'Foo'} + $scope.podData = { + items: [], + actions: [] + } + $controller('PodController', { $scope PodApi @@ -752,11 +757,47 @@ describe('controllers:', () -> .toHaveBeenCalledWith(21, 23, 'grault', {garply: 'waldo'}) ) + it('should mark the action as busy', () -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + + $scope.podData = { + items: [], + actions: [{ + type: 'grault' + isBusy: false, + payload: {} + }, { + type: 'fred', + isBusy: false, + payload: {} + }] + } + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) + + $scope.trigger('grault', {garply: 'waldo'}) + expect($scope.podData.actions[0].isBusy).toBe(true) + + $scope.$apply() + ) + it('should fetch and attach data to the scope if successful', () -> $scope.podId = 21 $scope.caseId = 23 $scope.podConfig = {title: 'Foo'} + $scope.podData = { + items: [], + actions: [] + } + $controller('PodController', { $scope PodApi diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index b7d32bd24..a8d22251e 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -833,12 +833,10 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> .then((d) -> $scope.podData = d) $scope.trigger = (type, payload) -> + $scope.podData.actions = updateAction(type, {isBusy: true}) + PodApi.trigger($scope.podId, $scope.caseId, type, payload) - .then(({success, payload}) -> - if success - $scope.onTriggerSuccess() - else - $scope.onTriggerFailure(payload)) + .then((res) -> triggerDone(type, res)) $scope.onTriggerFailure = (payload) -> # TODO show failure message @@ -847,6 +845,16 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> # TODO update notes $scope.update() + triggerDone = (type, {success, payload}) -> + if success + $scope.onTriggerSuccess() + else + $scope.onTriggerFailure(payload) + + updateAction = (type, props) -> + $scope.podData.actions + .map((d) -> if d.type == type then angular.extend({}, d, props) else d) + parsePodData = (d) -> d = angular.extend({ items: [], @@ -859,5 +867,5 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> d parsePodAction = (d) -> - angular.extend({isBusy: false}, d) + angular.extend({}, d, {isBusy: false}) ]) From 8a8d797e69c2a2222bdf38c5b93fcacb90a01ac4 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 13:16:45 +0200 Subject: [PATCH 26/64] Change pod controller to set actions to not be busy when triggers complete --- karma/test-controllers.coffee | 4 ++++ static/coffee/controllers.coffee | 2 ++ 2 files changed, 6 insertions(+) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index c58350102..9ffc349f8 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -780,6 +780,10 @@ describe('controllers:', () -> PodApi }) + # defer getting new data indefinitely to prevent isBusy being set to + # false when we retrieve new data + spyOn(PodApi, 'get').and.returnValue($q.defer().promise) + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) $scope.trigger('grault', {garply: 'waldo'}) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index a8d22251e..30c894639 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -851,6 +851,8 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> else $scope.onTriggerFailure(payload) + $scope.podData.actions = updateAction(type, {isBusy: false}) + updateAction = (type, props) -> $scope.podData.actions .map((d) -> if d.type == type then angular.extend({}, d, props) else d) From d38f15de38a5fcba74824b83d041b9d1ceceb2c0 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 15:07:07 +0200 Subject: [PATCH 27/64] Change pod controller to support using custom busy text for actions --- karma/test-controllers.coffee | 97 ++++++++++++++++++++++++++++++++ static/coffee/controllers.coffee | 10 +++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 9ffc349f8..6ddc54c95 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -725,6 +725,102 @@ describe('controllers:', () -> actions: [{ type: 'baz', name: 'Baz', + busyText: 'Baz', + isBusy: false, + payload: {} + }] + }) + ) + ) + + describe('update', () -> + it('should fetch and update pod data', () -> + $scope.podId = 21 + $scope.caseId = 23 + + spyOn(PodApi, 'get').and.returnValue($q.resolve({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + payload: {} + }] + })) + + $controller('PodController', { + $scope + PodApi + }) + + $scope.update() + $scope.$apply() + + expect(PodApi.get).toHaveBeenCalledWith(21, 23) + + expect($scope.podData).toEqual({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + busyText: 'Baz', + isBusy: false, + payload: {} + }] + }) + ) + + it("should default an action's busy text to the action's name", () -> + $scope.podId = 21 + $scope.caseId = 23 + + spyOn(PodApi, 'get').and.returnValue($q.resolve({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + busy_text: 'Bazzing', + payload: {} + }, { + type: 'quux', + name: 'Quux', + payload: {} + }] + })) + + $controller('PodController', { + $scope + PodApi + }) + + $scope.update() + $scope.$apply() + + expect(PodApi.get).toHaveBeenCalledWith(21, 23) + + expect($scope.podData).toEqual({ + items: [{ + name: 'Foo', + value: 'Bar' + }] + actions: [{ + type: 'baz', + name: 'Baz', + busyText: 'Bazzing', + isBusy: false, + payload: {} + }, { + type: 'quux', + name: 'Quux', + busyText: 'Quux', isBusy: false, payload: {} }] @@ -832,6 +928,7 @@ describe('controllers:', () -> actions: [{ type: 'baz', name: 'Baz', + busyText: 'Baz', isBusy: false, payload: {} }] diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 30c894639..b4f902922 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -868,6 +868,12 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> d - parsePodAction = (d) -> - angular.extend({}, d, {isBusy: false}) + parsePodAction = ({type, name, busy_text, payload}) -> + { + type, + name, + payload, + busyText: busy_text ? name, + isBusy: false + } ]) From bb06575870fe665235d5b2f6b536a9969bf760a9 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 15:08:14 +0200 Subject: [PATCH 28/64] Minor cleanup for pod controller --- static/coffee/controllers.coffee | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index b4f902922..4507904f9 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -838,21 +838,22 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> PodApi.trigger($scope.podId, $scope.caseId, type, payload) .then((res) -> triggerDone(type, res)) - $scope.onTriggerFailure = (payload) -> - # TODO show failure message - - $scope.onTriggerSuccess = () -> - # TODO update notes - $scope.update() - triggerDone = (type, {success, payload}) -> if success - $scope.onTriggerSuccess() + onTriggerSuccess() else - $scope.onTriggerFailure(payload) + onTriggerFailure(payload) $scope.podData.actions = updateAction(type, {isBusy: false}) + onTriggerFailure = (payload) -> + $scope.$emit('podActionFailure', payload) + # TODO show failure message + + onTriggerSuccess = () -> + $scope.$emit('podActionSuccess') + $scope.update() + updateAction = (type, props) -> $scope.podData.actions .map((d) -> if d.type == type then angular.extend({}, d, props) else d) @@ -868,12 +869,11 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> d - parsePodAction = ({type, name, busy_text, payload}) -> - { - type, - name, - payload, - busyText: busy_text ? name, - isBusy: false - } + parsePodAction = ({type, name, busy_text, payload}) -> { + type, + name, + payload, + busyText: busy_text ? name, + isBusy: false + } ]) From e28738e913d92536c60032acac1cf4b6c06ea87c Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 15:20:54 +0200 Subject: [PATCH 29/64] Change pod directive to draw busy actions differently --- karma/test-directives.coffee | 25 +++++++++++++++++++++++++ static/templates/pod.html | 6 ++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 088fe1186..042e76705 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -120,10 +120,14 @@ describe('directives:', () -> $rootScope.podData.actions = [{ type: 'foo', name: 'Foo', + busyText: 'Foo', + isBusy: false, payload: {bar: 'baz'} }, { type: 'quux', name: 'Quux', + busyText: 'Quux', + isBusy: false, payload: {corge: 'grault'} }] @@ -137,14 +141,35 @@ describe('directives:', () -> expect(action2.textContent).toContain('Quux') ) + it('should draw busy pod actions', -> + $rootScope.podData.actions = [{ + type: 'baz', + name: 'Baz', + isBusy: true, + busyText: 'Bazzing', + payload: {} + }] + + el = $compile('')($rootScope)[0] + $rootScope.$digest() + + action1 = el.querySelectorAll('.pod-action')[0] + expect(action1.textContent).toContain('Bazzing') + expect(action1.classList.contains('disabled')).toBe(true) + ) + it('should call trigger() when an action button is clicked', -> $rootScope.podData.actions = [{ type: 'foo', name: 'Foo', + busyText: 'Foo', + isBusy: false, payload: {a: 'b'} }, { type: 'bar', name: 'Bar', + busyText: 'Bar', + isBusy: false, payload: {c: 'd'} }] diff --git a/static/templates/pod.html b/static/templates/pod.html index 585738eb8..fda37bf98 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -25,9 +25,11 @@ - [[ action.name ]] + [[ action.name ]] + [[ action.busyText ]]
From 9fad2735cb46c696dd3b98beb06d4ff463921018 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 15:22:24 +0200 Subject: [PATCH 30/64] More consistent callback names in pod controller --- static/coffee/controllers.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 4507904f9..b21345779 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -836,9 +836,9 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.podData.actions = updateAction(type, {isBusy: true}) PodApi.trigger($scope.podId, $scope.caseId, type, payload) - .then((res) -> triggerDone(type, res)) + .then((res) -> onTriggerDone(type, res)) - triggerDone = (type, {success, payload}) -> + onTriggerDone = (type, {success, payload}) -> if success onTriggerSuccess() else From 3990069fa13c95c0e7c58219c9feb38ffc787184 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 18:08:36 +0200 Subject: [PATCH 31/64] Refactor PodApi to avoid needing to repeat logic for handling responses --- karma/test-services.coffee | 40 +++++++++++++++++++++++------------ static/coffee/services.coffee | 28 ++++++++++++++++++------ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index a0f39145a..e7d5e7941 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -623,33 +623,47 @@ describe('services:', () -> PodApi = _PodApi_ )) - describe('get', () -> - it('gets a pod', () -> - $httpBackend.expectGET('/pods/read/21/?case_id=23') + describe('method', () -> + it('constructs a pod api method', () -> + $httpBackend.expectGET('/pods/foo/21/?bar=23') .respond({foo: 'bar'}) - PodApi.get(21, 23) + method = PodApi.method((id, bar) -> { + method: 'GET', + url: "/pods/foo/#{id}/", + params: {bar} + }) + + method(21, 23) .then((res) -> expect(res).toEqual({foo: 'bar'})) $httpBackend.flush() ) ) + describe('get', () -> + it('gets a pod', () -> + expect(PodApi.get.fn(21, 23)).toEqual({ + method: 'GET', + url: "/pods/read/21/", + params: {case_id: 23} + }) + ) + ) + describe('trigger', () -> it('triggers an action', () -> - $httpBackend.expectPOST('/pods/action/21/', { - case_id: 23, + expect(PodApi.trigger.fn(21, 23, 'foo', {bar: 'baz'})).toEqual({ + method: 'POST', + url: "/pods/action/21/", + data: { + case_id: 23 action: { type: 'foo', payload: {bar: 'baz'} } - }) - .respond({quux: 'corge'}) - - PodApi.trigger(21, 23, 'foo', {bar: 'baz'}) - .then((res) -> expect(res).toEqual({quux: 'corge'})) - - $httpBackend.flush() + } + }) ) ) ) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index 585dfe774..bb8e40064 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -551,18 +551,32 @@ services.factory('UtilsService', ['$window', '$uibModal', ($window, $uibModal) - # Pod API service #===================================================================== services.factory('PodApi', ['$window', '$http', ($window, $http) -> - new class PodApi - get: (podId, caseId) -> - $http.get("/pods/read/#{podId}/", {params: {case_id: caseId}}) + method = (fn) -> + res = (args...) -> + $http(fn(args...)) .then((d) -> d.data) - trigger: (podId, caseId, type, payload = {}) -> - $http.post("/pods/action/#{podId}/", { + res.fn = fn + res + + new class PodApi + method: method, + + get: method((podId, caseId) -> { + method: 'GET', + url: "/pods/read/#{podId}/", + params: {case_id: caseId} + }) + + trigger: method((podId, caseId, type, payload = {}) -> { + method: 'POST', + url: "/pods/action/#{podId}/", + data: { case_id: caseId action: { type, payload } - }) - .then((d) -> d.data) + } + }) ]) From 7af3be6e0a47b8c6600c83257e56041229c0f05f Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 18:19:55 +0200 Subject: [PATCH 32/64] Reject PodApi errors as PodApiErrors --- karma/test-services.coffee | 16 ++++++++++++++++ static/coffee/services.coffee | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index e7d5e7941..e1ecc1844 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -639,6 +639,22 @@ describe('services:', () -> $httpBackend.flush() ) + + it('rejects response errors as PodApiErrors', () -> + $httpBackend.expectGET('/pods/foo/') + .respond(500) + + method = PodApi.method(-> { + method: 'GET', + url: "/pods/foo/" + }) + + method() + .catch((e) -> e) + .then((e) -> expect(e instanceof PodApi.PodApiError).toBe(true)) + + $httpBackend.flush() + ) ) describe('get', () -> diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index bb8e40064..fe94d1a17 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -550,16 +550,23 @@ services.factory('UtilsService', ['$window', '$uibModal', ($window, $uibModal) - #===================================================================== # Pod API service #===================================================================== -services.factory('PodApi', ['$window', '$http', ($window, $http) -> +services.factory('PodApi', ['$q', '$window', '$http', ($q, $window, $http) -> + class PodApiError extends Error + constructor: (error) -> + this.error = error + method = (fn) -> res = (args...) -> $http(fn(args...)) + .catch((e) -> $q.reject(new PodApiError(e))) .then((d) -> d.data) res.fn = fn res new class PodApi + PodApiError: PodApiError, + method: method, get: method((podId, caseId) -> { From 61cfa1539f26414310aa029dd32f759505094bb5 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 18:26:35 +0200 Subject: [PATCH 33/64] Add trap utility --- karma/test-utils.coffee | 15 +++++++++++++++ static/coffee/utils.coffee | 8 +++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/karma/test-utils.coffee b/karma/test-utils.coffee index 26c6eae58..245623644 100644 --- a/karma/test-utils.coffee +++ b/karma/test-utils.coffee @@ -46,4 +46,19 @@ describe('utils:', () -> expect(utils.find(items, 'bar', "Z")).toEqual({foo: 5, bar: "Z"}) ) ) + + describe('trap', () -> + it('should call the accept function for values of the given type', () -> + class Foo + foo = new Foo() + expect(utils.trap(Foo, ((v) -> v), (-> null))(foo)).toEqual(foo) + ) + + it('should call the reject function for values not of the given type', () -> + class Foo + class Bar + bar = new Bar() + expect(utils.trap(Foo, ((v) -> v), (-> null))(bar)).toEqual(null) + ) + ) ) diff --git a/static/coffee/utils.coffee b/static/coffee/utils.coffee index 113988e69..5c063a1d8 100644 --- a/static/coffee/utils.coffee +++ b/static/coffee/utils.coffee @@ -44,4 +44,10 @@ namespace('utils', (exports) -> if angular.equals(item[prop], value) return item return null -) \ No newline at end of file + + exports.trap = (type, acceptFn, rejectFn) -> (v) -> + if v instanceof type + acceptFn(v) + else + rejectFn(v) +) From 1b82c418c162a9897b5b592c9afb85c49c925257 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 25 Jul 2016 18:35:17 +0200 Subject: [PATCH 34/64] Emit an event if pod api calls fail in pod controller --- karma/test-controllers.coffee | 43 ++++++++++++++++++++++++++++++++ static/coffee/controllers.coffee | 7 +++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 6ddc54c95..00c072352 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -679,11 +679,14 @@ describe('controllers:', () -> describe('PodController', () -> $scope = null PodApi = null + class PodApiError beforeEach(() -> $scope = $rootScope.$new() PodApi = new class PodApi + PodApiError: PodApiError, + get: -> $q.resolve({ items: [], actions: [] @@ -826,6 +829,23 @@ describe('controllers:', () -> }] }) ) + + it('should emit a podApiError if the api call fails', (done) -> + $scope.podId = 21 + $scope.caseId = 23 + + spyOn(PodApi, 'get') + .and.returnValue($q.reject(new PodApiError(null))) + + $controller('PodController', { + $scope + PodApi + }) + + $scope.$on('podApiError', -> done()) + $scope.update() + $scope.$apply() + ) ) describe('trigger', () -> @@ -934,6 +954,29 @@ describe('controllers:', () -> }] }) ) + + it('should emit a podApiError if the api call fails', (done) -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + + $scope.podData = { + items: [], + actions: [] + } + + spyOn(PodApi, 'trigger') + .and.returnValue($q.reject(new PodApiError(null))) + + $controller('PodController', { + $scope + PodApi + }) + + $scope.$on('podApiError', -> done()) + $scope.trigger('grault', {garply: 'waldo'}) + $scope.$apply() + ) ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index b21345779..6fe401289 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -820,7 +820,7 @@ controllers.controller('DateRangeController', ['$scope', ($scope) -> #============================================================================ # Pod controller #============================================================================ -controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> +controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, PodApi) -> $scope.init = (podId, caseId, podConfig) -> $scope.podId = podId $scope.caseId = caseId @@ -831,12 +831,14 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> PodApi.get($scope.podId, $scope.caseId) .then(parsePodData) .then((d) -> $scope.podData = d) + .catch(utils.trap(PodApi.PodApiError, onApiError, $q.reject)) $scope.trigger = (type, payload) -> $scope.podData.actions = updateAction(type, {isBusy: true}) PodApi.trigger($scope.podId, $scope.caseId, type, payload) .then((res) -> onTriggerDone(type, res)) + .catch(utils.trap(PodApi.PodApiError, onApiError, $q.reject)) onTriggerDone = (type, {success, payload}) -> if success @@ -846,6 +848,9 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> $scope.podData.actions = updateAction(type, {isBusy: false}) + onApiError = -> + $scope.$emit('podApiError') + onTriggerFailure = (payload) -> $scope.$emit('podActionFailure', payload) # TODO show failure message From 787fa0fe396d6fbecffd99d359200f59099daa32 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Tue, 26 Jul 2016 13:59:28 +0200 Subject: [PATCH 35/64] Emit timelineChanged events from pod controller on pod action success rather than rely on CaseController for this (and instead have CaseController act as a proxy) --- karma/test-controllers.coffee | 9 +++++---- static/coffee/controllers.coffee | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index ca3ed0944..69007b2b4 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -84,9 +84,10 @@ describe('controllers:', () -> expect($scope.contact).toEqual(test.ann) ) - it('should should broadcast timelineChanged on podActionSuccess', (done) -> + it('should should proxy timelineChanged events from child scopes', (done) -> $scope.$on('timelineChanged', -> done()) - $scope.$emit('podActionSuccess') + child = $scope.$new(false) + child.$emit('timelineChanged') ) it('addNote', () -> @@ -734,7 +735,7 @@ describe('controllers:', () -> .toHaveBeenCalledWith(21, 23, 'grault', {garply: 'waldo'}) ) - it('should emit a podActionSuccess event if successful', (done) -> + it('should emit a timelineChanged event if successful', (done) -> $scope.podId = 21 $scope.caseId = 23 $scope.podConfig = {title: 'Foo'} @@ -748,7 +749,7 @@ describe('controllers:', () -> spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) $scope.trigger('grault', {garply: 'waldo'}) - $scope.$on('podActionSuccess', -> done()) + $scope.$on('timelineChanged', -> done()) $scope.$apply() ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index ca4451884..20e9cb2a9 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -517,7 +517,8 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.refresh() - $scope.$on('podActionSuccess', -> $scope.$broadcast('timelineChanged')) + $scope.$on('timelineChange', (e) -> + $scope.$broadcast('timelineChanged') if e.targetScope != $scope) $scope.refresh = () -> CaseService.fetchSingle($scope.caseId).then((caseObj) -> @@ -845,6 +846,6 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> # TODO show failure message $scope.onTriggerSuccess = () -> - $scope.$emit('podActionSuccess') + $scope.$emit('timelineChanged') $scope.update() ]) From 098e1d148c65ee8e6419d3bc522f0301c0ea989e Mon Sep 17 00:00:00 2001 From: justinvdm Date: Tue, 26 Jul 2016 18:59:22 +0200 Subject: [PATCH 36/64] Change case notifications approach to keep notification content inside template --- karma/test-controllers.coffee | 38 ++++++++----- karma/test-directives.coffee | 72 ++++++++++-------------- static/coffee/controllers.coffee | 24 ++++---- static/coffee/directives.coffee | 12 +++- static/templates/alert.html | 6 ++ static/templates/case-notifications.html | 7 +++ static/templates/notifications.html | 14 ----- templates/cases/case_read.haml | 2 +- 8 files changed, 88 insertions(+), 87 deletions(-) create mode 100644 static/templates/alert.html create mode 100644 static/templates/case-notifications.html delete mode 100644 static/templates/notifications.html diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 8020fb0a0..decd41a77 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -84,17 +84,6 @@ describe('controllers:', () -> expect($scope.contact).toEqual(test.ann) ) - it('should should add a notification on podActionFailure', () -> - $scope.notifications = [] - - $scope.$emit('podActionFailure', {message: 'o_O'}) - - expect($scope.notifications).toEqual([{ - type: 'danger', - message: 'o_O' - }]) - ) - it('addNote', () -> noteModal = spyOnPromise($q, $scope, UtilsService, 'noteModal') addNote = spyOnPromise($q, $scope, CaseService, 'addNote') @@ -136,6 +125,20 @@ describe('controllers:', () -> expect(UtilsService.confirmModal).toHaveBeenCalled() expect(CaseService.unwatch).toHaveBeenCalledWith(test.case1) ) + + it('should should add a notification on notification events', () -> + $scope.notifications = [] + $scope.$emit('notification', {type: 'foo'}) + expect($scope.notifications).toEqual([{type: 'foo'}]) + ) + + describe('addNotification', () -> + it('should add the given notification', () -> + $scope.notifications = [] + $scope.addNotification({type: 'foo'}) + expect($scope.notifications).toEqual([{type: 'foo'}]) + ) + ) ) #======================================================================= @@ -745,7 +748,7 @@ describe('controllers:', () -> .toHaveBeenCalledWith(21, 23, 'grault', {garply: 'waldo'}) ) - it('should emit a podActionFailure event if successful', (done) -> + it('should emit a notification event if unsuccessful', (done) -> $scope.podId = 21 $scope.caseId = 23 $scope.podConfig = {title: 'Foo'} @@ -756,10 +759,17 @@ describe('controllers:', () -> PodApi }) - spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: false})) + spyOn(PodApi, 'trigger').and.returnValue($q.resolve({ + success: false, + payload: {fred: 'xxyyxx'} + })) + $scope.trigger('grault', {garply: 'waldo'}) - $scope.$on('podActionFailure', -> done()) + $scope.$on('notification', (e, {type, payload}) -> + expect(type).toEqual('pod_action_failure') + expect(payload).toEqual({fred: 'xxyyxx'}) + done()) $scope.$apply() ) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 6c65ea461..fb9c31227 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -158,64 +158,52 @@ describe('directives:', () -> ) #======================================================================= - # Tests for pod + # Tests for cpAlert #======================================================================= - describe('cpNotifications', () -> - $rootScope = null - $compile = null + describe('cpAlert', () -> + beforeEach(() -> + $rootScope.notifications = [] + ) - beforeEach(inject((_$rootScope_, _$compile_) -> - $rootScope = _$rootScope_ - $compile = _$compile_ + it('should draw the alert', () -> + template = $compile('Foo') + el = template($rootScope)[0] + $rootScope.$digest() + alert = el.querySelector('.alert') + expect(alert.classList.contains('alert-danger')).toBe(true) + expect(alert.textContent).toMatch('Foo') + ) + ) + + #======================================================================= + # Tests for cpCaseNotifications + #======================================================================= + describe('cpCaseNotifications', () -> + beforeEach(() -> $rootScope.notifications = [] - $rootScope.removeNotification = -> - )) + ) - it('should draw the notifications', () -> + it('should draw pod_action_failure notifications', () -> $rootScope.notifications = [{ - type: 'danger', - message: 'Foo' + type: 'pod_action_failure', + payload: {message: 'Foo'} }, { - type: 'success', - message: 'Bar' + type: 'pod_action_failure', + payload: {message: 'Bar'} }] - el = $compile('')($rootScope)[0] + template = $compile('') + el = template($rootScope)[0] $rootScope.$digest() - notification1 = el.querySelector('.alert:nth-child(1)') - notification2 = el.querySelector('.alert:nth-child(2)') + [notification1, notification2] = el.querySelectorAll('.alert') expect(notification1.classList.contains('alert-danger')).toBe(true) expect(notification1.textContent).toMatch('Foo') - expect(notification2.classList.contains('alert-success')).toBe(true) + expect(notification2.classList.contains('alert-danger')).toBe(true) expect(notification2.textContent).toMatch('Bar') ) - - it('should call removeNotification() when a notification is closed', -> - $rootScope.notifications = [{ - type: 'danger', - message: 'Foo' - }, { - type: 'success', - message: 'Bar' - }] - - $rootScope.removeNotification = jasmine.createSpy('removeNotification') - - el = $compile('')($rootScope)[0] - $rootScope.$digest() - - angular.element(el.querySelector('.alert:nth-child(2) .close')) - .triggerHandler('click') - - expect($rootScope.removeNotification.calls.mostRecent().args[0]) - .toEqual(jasmine.objectContaining({ - type: 'success', - message: 'Bar' - })) - ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 428ea37be..f797e482a 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -524,19 +524,12 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.refresh() - $scope.$on('podActionFailure', (e, {message}) -> - $scope.addNotification({ - type: 'danger', - message - })) + $scope.$on('notification', (e, notification) -> + $scope.addNotification(notification)) $scope.addNotification = (notification) -> $scope.notifications.push(notification) - $scope.removeNotification = (notification) -> - $scope.notifications = $scope.notifications - .filter((d) -> d != notification) - $scope.refresh = () -> CaseService.fetchSingle($scope.caseId).then((caseObj) -> $scope.caseObj = caseObj @@ -856,14 +849,17 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> PodApi.trigger($scope.podId, $scope.caseId, type, payload) .then(({success, payload}) -> if success - $scope.onTriggerSuccess() + onTriggerSuccess() else - $scope.onTriggerFailure(payload)) + onTriggerFailure(payload)) - $scope.onTriggerFailure = (payload) -> - $scope.$emit('podActionFailure', payload) + onTriggerFailure = (payload) -> + $scope.$emit('notification', { + type: 'pod_action_failure', + payload + }) - $scope.onTriggerSuccess = () -> + onTriggerSuccess = () -> # TODO update notes $scope.update() ]) diff --git a/static/coffee/directives.coffee b/static/coffee/directives.coffee index d4e33c3d6..298dace2b 100644 --- a/static/coffee/directives.coffee +++ b/static/coffee/directives.coffee @@ -53,8 +53,16 @@ directives.directive('cpFieldvalue', () -> } ) -directives.directive('cpNotifications', -> { - templateUrl: -> '/sitestatic/templates/notifications.html' +directives.directive('cpAlert', -> { + restrict: 'E', + transclude: true, + scope: {type: '@'}, + templateUrl: '/sitestatic/templates/alert.html' +}) + + +directives.directive('cpCaseNotifications', -> { + templateUrl: '/sitestatic/templates/case-notifications.html' }) #===================================================================== diff --git a/static/templates/alert.html b/static/templates/alert.html new file mode 100644 index 000000000..9e3634e91 --- /dev/null +++ b/static/templates/alert.html @@ -0,0 +1,6 @@ +
+ + +
diff --git a/static/templates/case-notifications.html b/static/templates/case-notifications.html new file mode 100644 index 000000000..4a3e888ef --- /dev/null +++ b/static/templates/case-notifications.html @@ -0,0 +1,7 @@ +
+
+ + [[ notification.payload.message ]] + +
+
diff --git a/static/templates/notifications.html b/static/templates/notifications.html deleted file mode 100644 index 85c632984..000000000 --- a/static/templates/notifications.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - [[ notification.message ]] -
diff --git a/templates/cases/case_read.haml b/templates/cases/case_read.haml index c16cea1a6..f8b7521d0 100644 --- a/templates/cases/case_read.haml +++ b/templates/cases/case_read.haml @@ -66,7 +66,7 @@ .row .col-md-8 - + %form.sendform{ ng-if:"!caseObj.is_closed" } // TODO maxlength not working From 94f891e053287fd015353af7d4265ea439fa769b Mon Sep 17 00:00:00 2001 From: justinvdm Date: Tue, 26 Jul 2016 19:12:26 +0200 Subject: [PATCH 37/64] Remove accidental change to pod action trigger success callback in pod controller --- static/coffee/controllers.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 9b2e77301..a69d1d280 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -869,7 +869,6 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, $scope.$emit('podActionFailure', payload) onTriggerSuccess = () -> - $scope.$emit('podActionSuccess') $scope.update() updateAction = (type, props) -> From 26369ba0dbb376f93e7a83d4042e6eaa6e80ec65 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Tue, 26 Jul 2016 19:27:39 +0200 Subject: [PATCH 38/64] Emit notification for trigger api failures in pod controller --- karma/test-controllers.coffee | 94 ++++++++++++++++++-------------- static/coffee/controllers.coffee | 18 +++--- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 52975fef0..c43354125 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -848,23 +848,6 @@ describe('controllers:', () -> }] }) ) - - it('should emit a podApiError if the api call fails', (done) -> - $scope.podId = 21 - $scope.caseId = 23 - - spyOn(PodApi, 'get') - .and.returnValue($q.reject(new PodApiError(null))) - - $controller('PodController', { - $scope - PodApi - }) - - $scope.$on('podApiError', -> done()) - $scope.update() - $scope.$apply() - ) ) describe('trigger', () -> @@ -956,6 +939,60 @@ describe('controllers:', () -> $scope.$apply() ) + it('should emit a notification if trigger api method fails', (done) -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + + $scope.podData = { + items: [], + actions: [] + } + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'trigger') + .and.returnValue($q.reject(new PodApiError(null))) + + $scope.trigger('grault', {garply: 'waldo'}) + + $scope.$on('notification', (e, {type, payload}) -> + expect(type).toEqual('pod_action_api_failure') + done()) + + $scope.$apply() + ) + + it('should emit a notification if get api method fails', (done) -> + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + + $scope.podData = { + items: [], + actions: [] + } + + $controller('PodController', { + $scope + PodApi + }) + + spyOn(PodApi, 'get') + .and.returnValue($q.reject(new PodApiError(null))) + + $scope.trigger('grault', {garply: 'waldo'}) + + $scope.$on('notification', (e, {type, payload}) -> + expect(type).toEqual('pod_action_api_failure') + done()) + + $scope.$apply() + ) + it('should fetch and attach data to the scope if successful', () -> $scope.podId = 21 $scope.caseId = 23 @@ -1002,29 +1039,6 @@ describe('controllers:', () -> }] }) ) - - it('should emit a podApiError if the api call fails', (done) -> - $scope.podId = 21 - $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [] - } - - spyOn(PodApi, 'trigger') - .and.returnValue($q.reject(new PodApiError(null))) - - $controller('PodController', { - $scope - PodApi - }) - - $scope.$on('podApiError', -> done()) - $scope.trigger('grault', {garply: 'waldo'}) - $scope.$apply() - ) ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 83139dbd5..30875f6a0 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -839,31 +839,35 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, $scope.podId = podId $scope.caseId = caseId $scope.podConfig = podConfig + + # TODO handle api failures $scope.update() $scope.update = -> PodApi.get($scope.podId, $scope.caseId) .then(parsePodData) .then((d) -> $scope.podData = d) - .catch(utils.trap(PodApi.PodApiError, onApiError, $q.reject)) $scope.trigger = (type, payload) -> $scope.podData.actions = updateAction(type, {isBusy: true}) PodApi.trigger($scope.podId, $scope.caseId, type, payload) .then((res) -> onTriggerDone(type, res)) - .catch(utils.trap(PodApi.PodApiError, onApiError, $q.reject)) + .catch(utils.trap(PodApi.PodApiError, onTriggerApiError, $q.reject)) onTriggerDone = (type, {success, payload}) -> if success - onTriggerSuccess() + p = onTriggerSuccess() else - onTriggerFailure(payload) + p = onTriggerFailure(payload) - $scope.podData.actions = updateAction(type, {isBusy: false}) + p.then(-> + $scope.podData.actions = updateAction(type, {isBusy: false})) - onApiError = -> - $scope.$emit('podApiError') + onTriggerApiError = -> + $scope.$emit('notification', { + type: 'pod_action_api_failure' + }) onTriggerFailure = (payload) -> $scope.$emit('notification', { From 7955092d462ec047150338f4f5f104c5ac145e4a Mon Sep 17 00:00:00 2001 From: justinvdm Date: Tue, 26 Jul 2016 19:48:08 +0200 Subject: [PATCH 39/64] Add notification for pod action api failures --- karma/test-directives.coffee | 17 +++++++++++++++++ static/templates/case-notifications.html | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index e41723a2c..e5188af59 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -286,5 +286,22 @@ describe('directives:', () -> expect(notification2.classList.contains('alert-danger')).toBe(true) expect(notification2.textContent).toMatch('Bar') ) + + it('should draw pod_action_api_failure notifications', () -> + $rootScope.notifications = [{ + type: 'pod_action_api_failure' + }, { + type: 'pod_action_api_failure' + }] + + template = $compile('') + el = template($rootScope)[0] + $rootScope.$digest() + + [notification1, notification2] = el.querySelectorAll('.alert') + + expect(notification1.classList.contains('alert-danger')).toBe(true) + expect(notification2.classList.contains('alert-danger')).toBe(true) + ) ) ) diff --git a/static/templates/case-notifications.html b/static/templates/case-notifications.html index 4a3e888ef..c8070c3bc 100644 --- a/static/templates/case-notifications.html +++ b/static/templates/case-notifications.html @@ -4,4 +4,10 @@ [[ notification.payload.message ]]
+ +
+ + Sorry, we encountered a problem on our side while completing your action. + +
From 804ef03194bf876e7426a1cf27b21d77f9aeefed Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 27 Jul 2016 14:59:37 +0200 Subject: [PATCH 40/64] Change pod controller and directive to show when pod is loading --- karma/test-controllers.coffee | 23 +++++++++++++++++++++++ karma/test-directives.coffee | 11 +++++++++++ static/coffee/controllers.coffee | 5 +++-- static/templates/pod.html | 8 ++++++-- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index c379dfdd4..34f7038cb 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -756,6 +756,7 @@ describe('controllers:', () -> items: [], actions: [] }) + trigger: -> $q.resolve({success: true}) ) @@ -799,6 +800,28 @@ describe('controllers:', () -> }] }) ) + + it("should set the pod status to loading while it is loading", () -> + d = $q.defer() + + spyOn(PodApi, 'get').and.returnValue(d.promise.then(-> $q.resolve({ + items: [] + actions: [] + }))) + + $controller('PodController', { + $scope + PodApi + }) + + $scope.init(21, 23, {title: 'Baz'}) + expect($scope.status).toEqual('loading') + + d.resolve() + $scope.$apply() + + expect($scope.status).toEqual('idle') + ) ) describe('update', () -> diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index e5188af59..730ac13e4 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -114,11 +114,13 @@ describe('directives:', () -> $compile = _$compile_ $rootScope.podConfig = {title: 'Foo'} + $rootScope.podData = { items: [], actions: [] } + $rootScope.status = 'idle' $rootScope.trigger = -> )) @@ -236,6 +238,15 @@ describe('directives:', () -> angular.element(action2).triggerHandler('click') expect($rootScope.trigger).toHaveBeenCalledWith('bar', {c: 'd'}) ) + + it('should draw when it is loading', () -> + $rootScope.status = 'loading' + + el = $compile('')($rootScope)[0] + $rootScope.$digest() + + expect(el.textContent).toMatch('Loading') + ) ) #======================================================================= diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 2c2bff2d5..45a308d9f 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -857,9 +857,11 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, $scope.podId = podId $scope.caseId = caseId $scope.podConfig = podConfig + $scope.status = 'loading' # TODO handle api failures $scope.update() + .then(-> $scope.status = 'idle') $scope.update = -> PodApi.get($scope.podId, $scope.caseId) @@ -879,8 +881,7 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, else p = onTriggerFailure(payload) - p.then(-> - $scope.podData.actions = updateAction(type, {isBusy: false})) + p.then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) onTriggerApiError = -> $scope.$emit('notification', { diff --git a/static/templates/pod.html b/static/templates/pod.html index fda37bf98..b1556f6f8 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -1,7 +1,11 @@
[[ podConfig.title ]]
-
+
+ Loading... +
+ +
@@ -21,7 +25,7 @@
-
+
Date: Wed, 27 Jul 2016 16:28:49 +0200 Subject: [PATCH 41/64] Change pod controller and directive to show when loading pod fails --- karma/test-controllers.coffee | 13 +++++++++++++ karma/test-directives.coffee | 11 ++++++++++- static/coffee/controllers.coffee | 9 ++++++--- static/templates/pod.html | 6 +++++- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 34f7038cb..62a3c4f78 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -822,6 +822,19 @@ describe('controllers:', () -> expect($scope.status).toEqual('idle') ) + + it("should set the pod status to loading-failed if loading fails", () -> + spyOn(PodApi, 'get').and.returnValue($q.reject(new PodApiError(null))) + + $controller('PodController', { + $scope + PodApi + }) + + $scope.init(21, 23, {title: 'Baz'}) + $scope.$apply() + expect($scope.status).toEqual('loading-failed') + ) ) describe('update', () -> diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 730ac13e4..a37a10850 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -239,7 +239,7 @@ describe('directives:', () -> expect($rootScope.trigger).toHaveBeenCalledWith('bar', {c: 'd'}) ) - it('should draw when it is loading', () -> + it('should draw whether it is loading', () -> $rootScope.status = 'loading' el = $compile('')($rootScope)[0] @@ -247,6 +247,15 @@ describe('directives:', () -> expect(el.textContent).toMatch('Loading') ) + + it('should draw whether loading has failed', () -> + $rootScope.status = 'loading-failed' + + el = $compile('')($rootScope)[0] + $rootScope.$digest() + + expect(el.textContent).toMatch('Could not load') + ) ) #======================================================================= diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 45a308d9f..6c51a1a6c 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -859,9 +859,9 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, $scope.podConfig = podConfig $scope.status = 'loading' - # TODO handle api failures $scope.update() .then(-> $scope.status = 'idle') + .catch(utils.trap(PodApi.PodApiError, onLoadApiFailure, $q.reject)) $scope.update = -> PodApi.get($scope.podId, $scope.caseId) @@ -873,7 +873,7 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, PodApi.trigger($scope.podId, $scope.caseId, type, payload) .then((res) -> onTriggerDone(type, res)) - .catch(utils.trap(PodApi.PodApiError, onTriggerApiError, $q.reject)) + .catch(utils.trap(PodApi.PodApiError, onTriggerApiFailure, $q.reject)) onTriggerDone = (type, {success, payload}) -> if success @@ -883,7 +883,10 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, p.then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) - onTriggerApiError = -> + onLoadApiFailure = -> + $scope.status = 'loading-failed' + + onTriggerApiFailure = -> $scope.$emit('notification', { type: 'pod_action_api_failure' }) diff --git a/static/templates/pod.html b/static/templates/pod.html index b1556f6f8..4443392a9 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -2,7 +2,11 @@
[[ podConfig.title ]]
- Loading... + Loading... +
+ +
+ Could not load
From 2fc686256baf4230555717a6be27920e68180c9a Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 27 Jul 2016 16:40:40 +0200 Subject: [PATCH 42/64] Display a notification if loading pods fails --- karma/test-controllers.coffee | 11 +++++++++++ static/coffee/controllers.coffee | 14 ++++++++++++-- static/templates/case-notifications.html | 7 +++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 62a3c4f78..efd173ff5 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -141,6 +141,17 @@ describe('controllers:', () -> expect($scope.notifications).toEqual([{type: 'foo'}]) ) + it('should should ignore duplicate pod_load_api_failure notifications', () -> + $scope.notifications = [] + + $scope.$emit('notification', {type: 'pod_load_api_failure'}) + expect($scope.notifications).toEqual([{type: 'pod_load_api_failure'}]) + + $scope.$emit('notification', {type: 'pod_load_api_failure'}) + $scope.$emit('notification', {type: 'pod_load_api_failure'}) + expect($scope.notifications).toEqual([{type: 'pod_load_api_failure'}]) + ) + describe('addNotification', () -> it('should add the given notification', () -> $scope.notifications = [] diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 6c51a1a6c..0be9c2a34 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -16,6 +16,7 @@ CASE_SUMMARY_MAX_LEN = 255 CASE_NOTE_MAX_LEN = 1024 OUTGOING_TEXT_MAX_LEN = 480 +SINGLETON_NOTIFICATIONS = ['pod_load_api_failure'] #============================================================================ # Inbox controller (DOM parent of messages and cases) @@ -508,7 +509,6 @@ controllers.controller('HomeController', ['$scope', '$controller', 'LabelService # Case view controller #============================================================================ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'CaseService', 'ContactService', 'MessageService', 'PartnerService', 'UtilsService', ($scope, $window, $timeout, CaseService, ContactService, MessageService, PartnerService, UtilsService) -> - $scope.allLabels = $window.contextData.all_labels $scope.fields = $window.contextData.fields @@ -516,6 +516,7 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.contact = null $scope.newMessage = '' $scope.sending = false + $scope.notifications = [] $scope.init = (caseId, maxMsgChars) -> @@ -528,7 +529,12 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.addNotification(notification)) $scope.addNotification = (notification) -> - $scope.notifications.push(notification) + if (not shouldIgnoreNotification(notification)) + $scope.notifications.push(notification) + + shouldIgnoreNotification = ({type}) -> + type in SINGLETON_NOTIFICATIONS and + $scope.notifications.some((d) -> type == d.type) $scope.refresh = () -> CaseService.fetchSingle($scope.caseId).then((caseObj) -> @@ -886,6 +892,10 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, onLoadApiFailure = -> $scope.status = 'loading-failed' + $scope.$emit('notification', { + type: 'pod_load_api_failure' + }) + onTriggerApiFailure = -> $scope.$emit('notification', { type: 'pod_action_api_failure' diff --git a/static/templates/case-notifications.html b/static/templates/case-notifications.html index c8070c3bc..037716ac7 100644 --- a/static/templates/case-notifications.html +++ b/static/templates/case-notifications.html @@ -10,4 +10,11 @@ Sorry, we encountered a problem on our side while completing your action.
+ +
+ + Sorry, a problem on our side prevented some of the information on this + page from loading. + +
From 0acacff4eef5031f8922872a7a4db01f721c0dc0 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 27 Jul 2016 17:33:44 +0200 Subject: [PATCH 43/64] Only set actions to not be busy once success or failure callback has completed --- static/coffee/controllers.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index fabe42353..91d948d90 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -845,7 +845,7 @@ controllers.controller('DateRangeController', ['$scope', ($scope) -> #============================================================================ # Pod controller #============================================================================ -controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> +controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, PodApi) -> $scope.init = (podId, caseId, podConfig) -> $scope.podId = podId $scope.caseId = caseId @@ -865,11 +865,12 @@ controllers.controller('PodController', ['$scope', 'PodApi', ($scope, PodApi) -> onTriggerDone = (type, {success, payload}) -> if success - onTriggerSuccess() + p = onTriggerSuccess() else - onTriggerFailure(payload) + p = onTriggerFailure(payload) - $scope.podData.actions = updateAction(type, {isBusy: false}) + $q.resolve(p) + .then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) onTriggerFailure = (payload) -> $scope.$emit('podActionFailure', payload) From 4e5407793a1599d1c555e040f4461f207dc3fdd3 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 27 Jul 2016 17:36:12 +0200 Subject: [PATCH 44/64] Fix scope soup in cp-case-notifications directive --- karma/test-directives.coffee | 5 ++++- static/coffee/directives.coffee | 3 ++- templates/cases/case_read.haml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index fb9c31227..6c4610976 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -193,7 +193,10 @@ describe('directives:', () -> payload: {message: 'Bar'} }] - template = $compile('') + template = $compile(' + + + ') el = template($rootScope)[0] $rootScope.$digest() diff --git a/static/coffee/directives.coffee b/static/coffee/directives.coffee index 298dace2b..fddba0f7a 100644 --- a/static/coffee/directives.coffee +++ b/static/coffee/directives.coffee @@ -62,7 +62,8 @@ directives.directive('cpAlert', -> { directives.directive('cpCaseNotifications', -> { - templateUrl: '/sitestatic/templates/case-notifications.html' + templateUrl: '/sitestatic/templates/case-notifications.html', + scope: {notifications: '='} }) #===================================================================== diff --git a/templates/cases/case_read.haml b/templates/cases/case_read.haml index e31569731..36c721290 100644 --- a/templates/cases/case_read.haml +++ b/templates/cases/case_read.haml @@ -66,7 +66,7 @@ .row .col-md-8 - + %form.sendform{ ng-if:"!caseObj.is_closed" } // TODO maxlength not working From 4afda006f440b351e719ecd68c35c74695447228 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 27 Jul 2016 17:37:28 +0200 Subject: [PATCH 45/64] Fix failing test after merging in updates to #56 --- karma/test-directives.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index c4b669444..42b5ed214 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -297,7 +297,10 @@ describe('directives:', () -> type: 'pod_action_api_failure' }] - template = $compile('') + template = $compile(' + + + ') el = template($rootScope)[0] $rootScope.$digest() From 658938d6d78a967bf087c0f13a0fc6ea6619183e Mon Sep 17 00:00:00 2001 From: justinvdm Date: Wed, 27 Jul 2016 18:39:45 +0200 Subject: [PATCH 46/64] More consistent naming convention for pod status scope property --- karma/test-controllers.coffee | 4 ++-- karma/test-directives.coffee | 2 +- static/coffee/controllers.coffee | 2 +- static/templates/pod.html | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index efd173ff5..41fa83294 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -834,7 +834,7 @@ describe('controllers:', () -> expect($scope.status).toEqual('idle') ) - it("should set the pod status to loading-failed if loading fails", () -> + it("should set the pod status to loading_failed if loading fails", () -> spyOn(PodApi, 'get').and.returnValue($q.reject(new PodApiError(null))) $controller('PodController', { @@ -844,7 +844,7 @@ describe('controllers:', () -> $scope.init(21, 23, {title: 'Baz'}) $scope.$apply() - expect($scope.status).toEqual('loading-failed') + expect($scope.status).toEqual('loading_failed') ) ) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 53c75c48e..b7c3cea05 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -249,7 +249,7 @@ describe('directives:', () -> ) it('should draw whether loading has failed', () -> - $rootScope.status = 'loading-failed' + $rootScope.status = 'loading_failed' el = $compile('')($rootScope)[0] $rootScope.$digest() diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 563fd9faa..b95bc3cde 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -891,7 +891,7 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, .then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) onLoadApiFailure = -> - $scope.status = 'loading-failed' + $scope.status = 'loading_failed' $scope.$emit('notification', { type: 'pod_load_api_failure' diff --git a/static/templates/pod.html b/static/templates/pod.html index 4443392a9..8561e88f1 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -5,7 +5,7 @@ Loading...
-
+
Could not load
From a4ebc33c8c2304ab8ce17e46eb8f56f824242e75 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 28 Jul 2016 16:04:28 +0200 Subject: [PATCH 47/64] Add pod action confirm modal --- karma/test-services.coffee | 73 ++++++++++++++++++++++- static/coffee/services.coffee | 17 ++++++ static/templates/case-confirm-modals.html | 16 +++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 static/templates/case-confirm-modals.html diff --git a/karma/test-services.coffee b/karma/test-services.coffee index 824f4b09b..675108702 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -3,14 +3,17 @@ describe('services:', () -> $httpBackend = null $window = null + $rootScope = null test = null beforeEach(() -> + module('templates') module('cases') - inject((_$httpBackend_, _$window_) -> + inject((_$httpBackend_, _$window_, _$rootScope_) -> $httpBackend = _$httpBackend_ $window = _$window_ + $rootScope = _$rootScope_ ) test = { @@ -690,4 +693,72 @@ describe('services:', () -> ) ) ) + + #======================================================================= + # Tests for CaseModals + #======================================================================= + describe('CaseModals', () -> + CaseModals = null + + beforeEach(inject((_CaseModals_) -> + CaseModals = _CaseModals_ + )) + + describe('confirm', () -> + describe('pod_action_confirm', () -> + it('should draw the modal', () -> + CaseModals.confirm({ + type: 'pod_action_confirm' + payload: {name: 'Opt out'} + }) + + $rootScope.$apply() + + expect(document.querySelector('.modal-header').textContent) + .toMatch('Opt out') + + expect(document.querySelector('.modal-body').textContent) + .toMatch('Are you sure you want to perform this action?') + ) + + it('should fulfill if the modal is accepted', () -> + fulfilled = false + + CaseModals.confirm({ + type: 'pod_action_confirm' + payload: {name: 'Opt out'} + }) + .then(-> fulfilled = true) + + $rootScope.$apply() + expect(fulfilled).toBe(false) + + angular.element(document.querySelector('.btn-modal-accept')) + .triggerHandler('click') + + $rootScope.$apply() + expect(fulfilled).toBe(true) + ) + + it('should reject if the modal is cancelled', () -> + rejected = false + + CaseModals.confirm({ + type: 'pod_action_confirm' + payload: {name: 'Opt out'} + }) + .catch(-> rejected = true) + + $rootScope.$apply() + expect(rejected).toBe(false) + + angular.element(document.querySelector('.btn-modal-cancel')) + .triggerHandler('click') + + $rootScope.$apply() + expect(rejected).toBe(true) + ) + ) + ) + ) ) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index 942b59d5e..ed5918a69 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -551,6 +551,23 @@ services.factory('UtilsService', ['$window', '$uibModal', ($window, $uibModal) - ]) +#===================================================================== +# Case Modals service +#===================================================================== +services.factory('CaseModals', ['$rootScope', '$uibModal', ($rootScope, $uibModal) -> + new class CaseModals + confirm: (ctx) -> + $uibModal.open({ + templateUrl: '/sitestatic/templates/case-confirm-modals.html', + scope: angular.extend($rootScope.$new(true), ctx), + controller: ($scope, $uibModalInstance) -> + $scope.ok = -> $uibModalInstance.close() + $scope.cancel = -> $uibModalInstance.dismiss() + }) + .result +]) + + #===================================================================== # Pod API service #===================================================================== diff --git a/static/templates/case-confirm-modals.html b/static/templates/case-confirm-modals.html new file mode 100644 index 000000000..49b71a43a --- /dev/null +++ b/static/templates/case-confirm-modals.html @@ -0,0 +1,16 @@ +
+
+ +
+
From bbd9d49ca00e8e747aaa20089dcce98e870ebf72 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 28 Jul 2016 16:54:43 +0200 Subject: [PATCH 48/64] Change PodController to support showing confirmation modal for actions --- karma/test-controllers.coffee | 202 +++++++++++++++++-------------- karma/test-directives.coffee | 11 +- static/coffee/controllers.coffee | 20 ++- static/templates/pod.html | 2 +- 4 files changed, 130 insertions(+), 105 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index efd173ff5..8ad1b618c 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -755,11 +755,27 @@ describe('controllers:', () -> describe('PodController', () -> $scope = null PodApi = null + CaseModals = null class PodApiError + bindController = (deps) -> + $controller('PodController', angular.extend({}, deps, { + $scope, + PodApi, + CaseModals + })) + beforeEach(() -> $scope = $rootScope.$new() + $scope.podData = { + items: [], + actions: [] + } + + CaseModals = new class CaseModals + confirm: -> $q.resolve() + PodApi = new class PodApi PodApiError: PodApiError, @@ -785,10 +801,7 @@ describe('controllers:', () -> }] })) - $controller('PodController', { - $scope - PodApi - }) + bindController() $scope.init(21, 23, {title: 'Baz'}) $scope.$apply() @@ -801,14 +814,14 @@ describe('controllers:', () -> items: [{ name: 'Foo', value: 'Bar' - }] - actions: [{ - type: 'baz', - name: 'Baz', - busyText: 'Baz', - isBusy: false, - payload: {} - }] + }], + actions: [ + jasmine.objectContaining({ + type: 'baz' + name: 'Baz', + payload: {} + }) + ] }) ) @@ -820,10 +833,7 @@ describe('controllers:', () -> actions: [] }))) - $controller('PodController', { - $scope - PodApi - }) + bindController() $scope.init(21, 23, {title: 'Baz'}) expect($scope.status).toEqual('loading') @@ -837,10 +847,7 @@ describe('controllers:', () -> it("should set the pod status to loading-failed if loading fails", () -> spyOn(PodApi, 'get').and.returnValue($q.reject(new PodApiError(null))) - $controller('PodController', { - $scope - PodApi - }) + bindController() $scope.init(21, 23, {title: 'Baz'}) $scope.$apply() @@ -865,10 +872,7 @@ describe('controllers:', () -> }] })) - $controller('PodController', { - $scope - PodApi - }) + bindController() $scope.update() $scope.$apply() @@ -879,14 +883,14 @@ describe('controllers:', () -> items: [{ name: 'Foo', value: 'Bar' - }] - actions: [{ - type: 'baz', - name: 'Baz', - busyText: 'Baz', - isBusy: false, - payload: {} - }] + }], + actions: [ + jasmine.objectContaining({ + type: 'baz' + name: 'Baz', + payload: {} + }) + ] }) ) @@ -911,35 +915,23 @@ describe('controllers:', () -> }] })) - $controller('PodController', { - $scope - PodApi - }) + bindController() $scope.update() $scope.$apply() expect(PodApi.get).toHaveBeenCalledWith(21, 23) - expect($scope.podData).toEqual({ - items: [{ - name: 'Foo', - value: 'Bar' - }] - actions: [{ - type: 'baz', - name: 'Baz', - busyText: 'Bazzing', - isBusy: false, - payload: {} - }, { + expect($scope.podData.actions).toEqual([ + jasmine.objectContaining({ + type: 'baz' + busyText: 'Bazzing' + }), + jasmine.objectContaining({ type: 'quux', - name: 'Quux', - busyText: 'Quux', - isBusy: false, - payload: {} - }] - }) + busyText: 'Quux' + }) + ]) ) ) @@ -954,14 +946,15 @@ describe('controllers:', () -> actions: [] } - $controller('PodController', { - $scope - PodApi - }) + bindController() spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) - $scope.trigger('grault', {garply: 'waldo'}) + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) + $scope.$apply() expect(PodApi.trigger) @@ -986,10 +979,7 @@ describe('controllers:', () -> }] } - $controller('PodController', { - $scope - PodApi - }) + bindController() # defer getting new data indefinitely to prevent isBusy being set to # false when we retrieve new data @@ -997,10 +987,14 @@ describe('controllers:', () -> spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) - $scope.trigger('grault', {garply: 'waldo'}) - expect($scope.podData.actions[0].isBusy).toBe(true) + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) $scope.$apply() + + expect($scope.podData.actions[0].isBusy).toBe(true) ) it('should emit a notification event if unsuccessful', (done) -> @@ -1013,22 +1007,23 @@ describe('controllers:', () -> actions: [] } - $controller('PodController', { - $scope - PodApi - }) + bindController() spyOn(PodApi, 'trigger').and.returnValue($q.resolve({ success: false, payload: {fred: 'xxyyxx'} })) - $scope.trigger('grault', {garply: 'waldo'}) + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) $scope.$on('notification', (e, {type, payload}) -> expect(type).toEqual('pod_action_failure') expect(payload).toEqual({fred: 'xxyyxx'}) done()) + $scope.$apply() ) @@ -1042,15 +1037,15 @@ describe('controllers:', () -> actions: [] } - $controller('PodController', { - $scope - PodApi - }) + bindController() spyOn(PodApi, 'trigger') .and.returnValue($q.reject(new PodApiError(null))) - $scope.trigger('grault', {garply: 'waldo'}) + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) $scope.$on('notification', (e, {type, payload}) -> expect(type).toEqual('pod_action_api_failure') @@ -1069,15 +1064,15 @@ describe('controllers:', () -> actions: [] } - $controller('PodController', { - $scope - PodApi - }) + bindController() spyOn(PodApi, 'get') .and.returnValue($q.reject(new PodApiError(null))) - $scope.trigger('grault', {garply: 'waldo'}) + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) $scope.$on('notification', (e, {type, payload}) -> expect(type).toEqual('pod_action_api_failure') @@ -1096,10 +1091,7 @@ describe('controllers:', () -> actions: [] } - $controller('PodController', { - $scope - PodApi - }) + bindController() spyOn(PodApi, 'get').and.returnValue($q.resolve({ items: [{ @@ -1115,23 +1107,47 @@ describe('controllers:', () -> spyOn(PodApi, 'trigger').and.returnValue($q.resolve({success: true})) - $scope.trigger('grault', {garply: 'waldo'}) + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) + $scope.$apply() expect($scope.podData).toEqual({ items: [{ name: 'Foo', value: 'Bar' - }] - actions: [{ - type: 'baz', - name: 'Baz', - busyText: 'Baz', - isBusy: false, - payload: {} - }] + }], + actions: [ + jasmine.objectContaining({ + type: 'baz' + name: 'Baz', + payload: {} + }) + ] }) ) + + it('should show a confirmation model if the action requires it', () -> + bindController() + + spyOn(CaseModals, 'confirm') + + $scope.trigger({ + type: 'grault', + name: 'Grault', + confirm: true, + payload: {garply: 'waldo'} + }) + + $scope.$apply() + + expect(CaseModals.confirm.calls.allArgs()).toEqual([[{ + type: 'pod_action_confirm', + payload: {name: 'Grault'} + }]]) + ) ) ) ) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 53c75c48e..23ee2b56a 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -206,7 +206,7 @@ describe('directives:', () -> ) it('should call trigger() when an action button is clicked', -> - $rootScope.podData.actions = [{ + $rootScope.podData.actions = [datum1, datum2] = [{ type: 'foo', name: 'Foo', busyText: 'Foo', @@ -228,15 +228,16 @@ describe('directives:', () -> action1 = el.querySelectorAll('.pod-action')[0] action2 = el.querySelectorAll('.pod-action')[1] - expect($rootScope.trigger).not.toHaveBeenCalledWith('foo', {a: 'b'}) + expect($rootScope.trigger).not.toHaveBeenCalledWith(datum1) angular.element(action1).triggerHandler('click') - expect($rootScope.trigger).toHaveBeenCalledWith('foo', {a: 'b'}) - expect($rootScope.trigger).not.toHaveBeenCalledWith('bar', {c: 'd'}) + expect($rootScope.trigger).toHaveBeenCalledWith(datum1) + expect($rootScope.trigger).not.toHaveBeenCalledWith(datum2) angular.element(action2).triggerHandler('click') - expect($rootScope.trigger).toHaveBeenCalledWith('bar', {c: 'd'}) + + expect($rootScope.trigger).toHaveBeenCalledWith(datum2) ) it('should draw whether it is loading', () -> diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 563fd9faa..549584243 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -858,7 +858,7 @@ controllers.controller('DateRangeController', ['$scope', ($scope) -> #============================================================================ # Pod controller #============================================================================ -controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, PodApi) -> +controllers.controller('PodController', ['$q', '$scope', 'PodApi', 'CaseModals', ($q, $scope, PodApi, CaseModals) -> $scope.init = (podId, caseId, podConfig) -> $scope.podId = podId $scope.caseId = caseId @@ -874,10 +874,11 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, .then(parsePodData) .then((d) -> $scope.podData = d) - $scope.trigger = (type, payload) -> - $scope.podData.actions = updateAction(type, {isBusy: true}) - - PodApi.trigger($scope.podId, $scope.caseId, type, payload) + $scope.trigger = ({type, name, payload, confirm}) -> + $q.resolve() + .then(-> confirmAction(name) if confirm) + .then(-> $scope.podData.actions = updateAction(type, {isBusy: true})) + .then(-> PodApi.trigger($scope.podId, $scope.caseId, type, payload)) .then((res) -> onTriggerDone(type, res)) .catch(utils.trap(PodApi.PodApiError, onTriggerApiFailure, $q.reject)) @@ -890,6 +891,12 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, $q.resolve(p) .then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) + confirmAction = (name) -> + CaseModals.confirm({ + type: 'pod_action_confirm', + payload: {name} + }) + onLoadApiFailure = -> $scope.status = 'loading-failed' @@ -927,10 +934,11 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApi', ($q, $scope, d - parsePodAction = ({type, name, busy_text, payload}) -> { + parsePodAction = ({type, name, busy_text, confirm, payload}) -> { type, name, payload, + confirm: confirm ? false, busyText: busy_text ? name, isBusy: false } diff --git a/static/templates/pod.html b/static/templates/pod.html index 4443392a9..cfd2aa92d 100644 --- a/static/templates/pod.html +++ b/static/templates/pod.html @@ -35,7 +35,7 @@ class="pod-action list-group-item" ng-repeat="action in podData.actions track by action.type" ng-class="{disabled: action.isBusy}" - ng-click="trigger(action.type, action.payload)"> + ng-click="trigger(action)"> [[ action.name ]] [[ action.busyText ]]
From 341e95cbde46fdd2499d7d775ee4a198bddde87e Mon Sep 17 00:00:00 2001 From: justinvdm Date: Thu, 28 Jul 2016 17:13:56 +0200 Subject: [PATCH 49/64] Clean up PodController tests --- karma/test-controllers.coffee | 71 +++++++---------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 8ad1b618c..b197f2ae4 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -768,6 +768,10 @@ describe('controllers:', () -> beforeEach(() -> $scope = $rootScope.$new() + $scope.podId = 21 + $scope.caseId = 23 + $scope.podConfig = {title: 'Foo'} + $scope.podData = { items: [], actions: [] @@ -939,12 +943,6 @@ describe('controllers:', () -> it('should trigger the given action', () -> $scope.podId = 21 $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [] - } bindController() @@ -962,22 +960,15 @@ describe('controllers:', () -> ) it('should mark the action as busy', () -> - $scope.podId = 21 - $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [{ - type: 'grault' - isBusy: false, - payload: {} - }, { - type: 'fred', - isBusy: false, - payload: {} - }] - } + $scope.podData.actions = [{ + type: 'grault' + isBusy: false, + payload: {} + }, { + type: 'fred', + isBusy: false, + payload: {} + }] bindController() @@ -998,15 +989,6 @@ describe('controllers:', () -> ) it('should emit a notification event if unsuccessful', (done) -> - $scope.podId = 21 - $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [] - } - bindController() spyOn(PodApi, 'trigger').and.returnValue($q.resolve({ @@ -1028,15 +1010,6 @@ describe('controllers:', () -> ) it('should emit a notification if trigger api method fails', (done) -> - $scope.podId = 21 - $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [] - } - bindController() spyOn(PodApi, 'trigger') @@ -1055,15 +1028,6 @@ describe('controllers:', () -> ) it('should emit a notification if get api method fails', (done) -> - $scope.podId = 21 - $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [] - } - bindController() spyOn(PodApi, 'get') @@ -1082,15 +1046,6 @@ describe('controllers:', () -> ) it('should fetch and attach data to the scope if successful', () -> - $scope.podId = 21 - $scope.caseId = 23 - $scope.podConfig = {title: 'Foo'} - - $scope.podData = { - items: [], - actions: [] - } - bindController() spyOn(PodApi, 'get').and.returnValue($q.resolve({ From f689f4884ce10858233405072ec7d832cd9d2593 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 12:56:30 +0200 Subject: [PATCH 50/64] Fix pod action confirm modal template to only include title in header --- static/templates/case-confirm-modals.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/static/templates/case-confirm-modals.html b/static/templates/case-confirm-modals.html index 49b71a43a..0cbe64a22 100644 --- a/static/templates/case-confirm-modals.html +++ b/static/templates/case-confirm-modals.html @@ -2,15 +2,15 @@
- + - +
From ca19784d9bc65e6db36a268be3229158b40ea6b5 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 13:01:28 +0200 Subject: [PATCH 51/64] Fix timelineChanged event proxying and related false positive test (thanks @rudigiesler) --- karma/test-controllers.coffee | 4 +++- static/coffee/controllers.coffee | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index d71757a8a..d4dd0662e 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -94,8 +94,10 @@ describe('controllers:', () -> ) it('should should proxy timelineChanged events from child scopes', (done) -> - $scope.$on('timelineChanged', -> done()) child = $scope.$new(false) + sibling = $scope.$new(false) + + sibling.$on('timelineChanged', -> done()) child.$emit('timelineChanged') ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 034d6d8d5..b24cd7b2f 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -527,7 +527,7 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.$on('notification', (e, notification) -> $scope.addNotification(notification)) - $scope.$on('timelineChange', (e) -> + $scope.$on('timelineChanged', (e) -> $scope.$broadcast('timelineChanged') if e.targetScope != $scope) $scope.addNotification = (notification) -> From c682b52c851de679106ab5be413bfcd075f30d46 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 13:33:33 +0200 Subject: [PATCH 52/64] Add basic ModalService with confirm() method for generic confirm modals --- karma/test-services.coffee | 91 +++++++++++++++++++++++------ static/coffee/services.coffee | 21 +++++-- static/templates/confirm.html | 14 +++++ static/templates/dummy-confirm.html | 12 ++++ 4 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 static/templates/confirm.html create mode 100644 static/templates/dummy-confirm.html diff --git a/karma/test-services.coffee b/karma/test-services.coffee index 30a9b966b..c7c653ed5 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -695,38 +695,38 @@ describe('services:', () -> ) #======================================================================= - # Tests for CaseModals + # Tests for ModalService #======================================================================= - describe('CaseModals', () -> - CaseModals = null + describe('ModalService', () -> + ModalService = null - beforeEach(inject((_CaseModals_) -> - CaseModals = _CaseModals_ + beforeEach(inject((_ModalService_) -> + ModalService = _ModalService_ )) describe('confirm', () -> - describe('pod_action_confirm', () -> + describe('if no template url is given', () -> it('should draw the modal', () -> - CaseModals.confirm({ - type: 'pod_action_confirm' - payload: {name: 'Opt out'} + ModalService.confirm({ + title: 'Foo', + prompt: 'Bar?' }) $rootScope.$apply() - expect(document.querySelector('.modal-header').textContent) - .toMatch('Opt out') + expect(document.querySelector('.modal-title').textContent) + .toMatch('Foo') expect(document.querySelector('.modal-body').textContent) - .toMatch('Are you sure you want to perform this action?') + .toMatch('Bar?') ) it('should fulfill if the modal is accepted', () -> fulfilled = false - CaseModals.confirm({ - type: 'pod_action_confirm' - payload: {name: 'Opt out'} + ModalService.confirm({ + title: 'Foo', + prompt: 'Bar?' }) .then(-> fulfilled = true) @@ -743,9 +743,64 @@ describe('services:', () -> it('should reject if the modal is cancelled', () -> rejected = false - CaseModals.confirm({ - type: 'pod_action_confirm' - payload: {name: 'Opt out'} + ModalService.confirm({ + title: 'Foo', + prompt: 'Bar?' + }) + .catch(-> rejected = true) + + $rootScope.$apply() + expect(rejected).toBe(false) + + angular.element(document.querySelector('.btn-modal-cancel')) + .triggerHandler('click') + + $rootScope.$apply() + expect(rejected).toBe(true) + ) + ) + + describe('if a template url is given', () -> + it('should draw the modal', () -> + ModalService.confirm({ + templateUrl: '/sitestatic/templates/dummy-confirm.html', + context: {title: 'Foo'} + }) + + $rootScope.$apply() + + expect(document.querySelector('.modal-title').textContent) + .toMatch('Foo') + + expect(document.querySelector('.modal-body').textContent) + .toMatch('Are you sure you want to do this?') + ) + + it('should fulfill if the modal is accepted', () -> + fulfilled = false + + ModalService.confirm({ + templateUrl: '/sitestatic/templates/dummy-confirm.html', + context: {title: 'Foo'} + }) + .then(-> fulfilled = true) + + $rootScope.$apply() + expect(fulfilled).toBe(false) + + angular.element(document.querySelector('.btn-modal-accept')) + .triggerHandler('click') + + $rootScope.$apply() + expect(fulfilled).toBe(true) + ) + + it('should reject if the modal is cancelled', () -> + rejected = false + + ModalService.confirm({ + templateUrl: '/sitestatic/templates/dummy-confirm.html', + context: {title: 'Foo'} }) .catch(-> rejected = true) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index e9233cf17..cda8c5b2b 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -552,14 +552,23 @@ services.factory('UtilsService', ['$window', '$uibModal', ($window, $uibModal) - #===================================================================== -# Case Modals service +# Modals service #===================================================================== -services.factory('CaseModals', ['$rootScope', '$uibModal', ($rootScope, $uibModal) -> - new class CaseModals - confirm: (ctx) -> +services.factory('ModalService', ['$rootScope', '$uibModal', ($rootScope, $uibModal) -> + new class ModalService + confirm: ({ + context = {}, + title = null, + prompt = null, + templateUrl = '/sitestatic/templates/confirm.html' + } = {}) -> $uibModal.open({ - templateUrl: '/sitestatic/templates/case-confirm-modals.html', - scope: angular.extend($rootScope.$new(true), ctx), + templateUrl, + scope: angular.extend($rootScope.$new(true), { + title, + prompt, + context + }), controller: ($scope, $uibModalInstance) -> $scope.ok = -> $uibModalInstance.close() $scope.cancel = -> $uibModalInstance.dismiss() diff --git a/static/templates/confirm.html b/static/templates/confirm.html new file mode 100644 index 000000000..18cc2f10f --- /dev/null +++ b/static/templates/confirm.html @@ -0,0 +1,14 @@ +
+ + + + + +
diff --git a/static/templates/dummy-confirm.html b/static/templates/dummy-confirm.html new file mode 100644 index 000000000..384cb1abb --- /dev/null +++ b/static/templates/dummy-confirm.html @@ -0,0 +1,12 @@ + + + + + From 9eb52e4d693534434a19dc434cb6c9bda6db4fbd Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 14:28:10 +0200 Subject: [PATCH 53/64] Add PodUIService.confirmAction() for drawing pod action confirm modals --- karma/test-services.coffee | 54 ++++++++++++++++++++++++ static/coffee/services.coffee | 13 ++++++ static/templates/pod-action-confirm.html | 12 ++++++ 3 files changed, 79 insertions(+) create mode 100644 static/templates/pod-action-confirm.html diff --git a/karma/test-services.coffee b/karma/test-services.coffee index c7c653ed5..89a3b1b09 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -816,4 +816,58 @@ describe('services:', () -> ) ) ) + + #======================================================================= + # Tests for PodUIService + #======================================================================= + describe('PodUIService', () -> + PodUIService = null + + beforeEach(inject((_PodUIService_) -> + PodUIService = _PodUIService_ + )) + + describe('confirmAction', () -> + it('should draw the modal', () -> + PodUIService.confirmAction('Foo') + + $rootScope.$apply() + + expect(document.querySelector('.modal-title').textContent) + .toMatch('Foo') + ) + + it('should fulfill if the modal is accepted', () -> + fulfilled = false + + PodUIService.confirmAction('Foo') + .then(-> fulfilled = true) + + $rootScope.$apply() + expect(fulfilled).toBe(false) + + angular.element(document.querySelector('.btn-modal-accept')) + .triggerHandler('click') + + $rootScope.$apply() + expect(fulfilled).toBe(true) + ) + + it('should reject if the modal is cancelled', () -> + rejected = false + + PodUIService.confirmAction('Foo') + .catch(-> rejected = true) + + $rootScope.$apply() + expect(rejected).toBe(false) + + angular.element(document.querySelector('.btn-modal-cancel')) + .triggerHandler('click') + + $rootScope.$apply() + expect(rejected).toBe(true) + ) + ) + ) ) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index cda8c5b2b..238f21c1b 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -577,6 +577,19 @@ services.factory('ModalService', ['$rootScope', '$uibModal', ($rootScope, $uibMo ]) +#===================================================================== +# PodUIService service +#===================================================================== +services.factory('PodUIService', ['ModalService', (ModalService) -> + new class PodUIService + confirmAction: (name) -> + ModalService.confirm({ + templateUrl: '/sitestatic/templates/pod-action-confirm.html', + context: {name} + }) +]) + + #===================================================================== # Pod API service #===================================================================== diff --git a/static/templates/pod-action-confirm.html b/static/templates/pod-action-confirm.html new file mode 100644 index 000000000..122d5d222 --- /dev/null +++ b/static/templates/pod-action-confirm.html @@ -0,0 +1,12 @@ + + + + + From a7860da4beea05ecbf1e0f33398162f0e683b987 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 14:36:50 +0200 Subject: [PATCH 54/64] Use PodUIService.confirmAction() in pod controller --- karma/test-controllers.coffee | 17 ++++++----------- static/coffee/controllers.coffee | 14 ++++++-------- static/templates/case-confirm-modals.html | 16 ---------------- 3 files changed, 12 insertions(+), 35 deletions(-) delete mode 100644 static/templates/case-confirm-modals.html diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index fd5c0776f..5c7f53c14 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -762,7 +762,7 @@ describe('controllers:', () -> describe('PodController', () -> $scope = null - CaseModals = null + PodUIService = null PodApiService = null class PodApiServiceError @@ -770,7 +770,7 @@ describe('controllers:', () -> $controller('PodController', angular.extend({}, deps, { $scope, PodApiService, - CaseModals + PodUIService })) beforeEach(() -> @@ -785,8 +785,8 @@ describe('controllers:', () -> actions: [] } - CaseModals = new class CaseModals - confirm: -> $q.resolve() + PodUIService = new class PodUIService + confirmAction: -> $q.resolve() PodApiService = new class PodApiService PodApiServiceError: PodApiServiceError, @@ -1108,8 +1108,7 @@ describe('controllers:', () -> it('should show a confirmation model if the action requires it', () -> bindController() - - spyOn(CaseModals, 'confirm') + spyOn(PodUIService, 'confirmAction') $scope.trigger({ type: 'grault', @@ -1119,11 +1118,7 @@ describe('controllers:', () -> }) $scope.$apply() - - expect(CaseModals.confirm.calls.allArgs()).toEqual([[{ - type: 'pod_action_confirm', - payload: {name: 'Grault'} - }]]) + expect(PodUIService.confirmAction.calls.allArgs()).toEqual([['Grault']]) ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 80ff9ecc0..edc122730 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -861,7 +861,11 @@ controllers.controller('DateRangeController', ['$scope', ($scope) -> #============================================================================ # Pod controller #============================================================================ -controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'CaseModals', ($q, $scope, PodApiService, CaseModals) -> +controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'PodUIService', ($q, $scope, PodApiService, PodUIService) -> + TEMPLATE_URLS = { + ACTION_CONFIRM: '/sitestatic/templates/pod-action-confirm.html' + } + $scope.init = (podId, caseId, podConfig) -> $scope.podId = podId $scope.caseId = caseId @@ -879,7 +883,7 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'CaseM $scope.trigger = ({type, name, payload, confirm}) -> $q.resolve() - .then(-> confirmAction(name) if confirm) + .then(-> PodUIService.confirmAction(name) if confirm) .then(-> $scope.podData.actions = updateAction(type, {isBusy: true})) .then(-> PodApiService.trigger($scope.podId, $scope.caseId, type, payload)) .then((res) -> onTriggerDone(type, res)) @@ -894,12 +898,6 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'CaseM $q.resolve(p) .then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) - confirmAction = (name) -> - CaseModals.confirm({ - type: 'pod_action_confirm', - payload: {name} - }) - onLoadApiFailure = -> $scope.status = 'loading_failed' diff --git a/static/templates/case-confirm-modals.html b/static/templates/case-confirm-modals.html deleted file mode 100644 index 49b71a43a..000000000 --- a/static/templates/case-confirm-modals.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
- -
-
From a3693f036a749ef64d19135917a9d71d0e61a692 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 14:46:17 +0200 Subject: [PATCH 55/64] Move modal templates into subdirectory --- karma/test-services.coffee | 6 +++--- static/coffee/services.coffee | 4 ++-- static/templates/{ => modals}/confirm.html | 0 static/templates/{ => modals}/dummy-confirm.html | 0 static/templates/{ => modals}/pod-action-confirm.html | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename static/templates/{ => modals}/confirm.html (100%) rename static/templates/{ => modals}/dummy-confirm.html (100%) rename static/templates/{ => modals}/pod-action-confirm.html (100%) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index 89a3b1b09..44ad4c67d 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -763,7 +763,7 @@ describe('services:', () -> describe('if a template url is given', () -> it('should draw the modal', () -> ModalService.confirm({ - templateUrl: '/sitestatic/templates/dummy-confirm.html', + templateUrl: '/sitestatic/templates/modals/dummy-confirm.html', context: {title: 'Foo'} }) @@ -780,7 +780,7 @@ describe('services:', () -> fulfilled = false ModalService.confirm({ - templateUrl: '/sitestatic/templates/dummy-confirm.html', + templateUrl: '/sitestatic/templates/modals/dummy-confirm.html', context: {title: 'Foo'} }) .then(-> fulfilled = true) @@ -799,7 +799,7 @@ describe('services:', () -> rejected = false ModalService.confirm({ - templateUrl: '/sitestatic/templates/dummy-confirm.html', + templateUrl: '/sitestatic/templates/modals/dummy-confirm.html', context: {title: 'Foo'} }) .catch(-> rejected = true) diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index 238f21c1b..cbf0bdf8a 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -560,7 +560,7 @@ services.factory('ModalService', ['$rootScope', '$uibModal', ($rootScope, $uibMo context = {}, title = null, prompt = null, - templateUrl = '/sitestatic/templates/confirm.html' + templateUrl = '/sitestatic/templates/modals/confirm.html' } = {}) -> $uibModal.open({ templateUrl, @@ -584,7 +584,7 @@ services.factory('PodUIService', ['ModalService', (ModalService) -> new class PodUIService confirmAction: (name) -> ModalService.confirm({ - templateUrl: '/sitestatic/templates/pod-action-confirm.html', + templateUrl: '/sitestatic/templates/modals/pod-action-confirm.html', context: {name} }) ]) diff --git a/static/templates/confirm.html b/static/templates/modals/confirm.html similarity index 100% rename from static/templates/confirm.html rename to static/templates/modals/confirm.html diff --git a/static/templates/dummy-confirm.html b/static/templates/modals/dummy-confirm.html similarity index 100% rename from static/templates/dummy-confirm.html rename to static/templates/modals/dummy-confirm.html diff --git a/static/templates/pod-action-confirm.html b/static/templates/modals/pod-action-confirm.html similarity index 100% rename from static/templates/pod-action-confirm.html rename to static/templates/modals/pod-action-confirm.html From d4cb017307ab75d8a7166cbdfd6c391f6bc837b2 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 16:04:55 +0200 Subject: [PATCH 56/64] Rename notifications to alerts --- karma/test-controllers.coffee | 44 +++++++++---------- karma/test-directives.coffee | 40 ++++++++--------- static/coffee/controllers.coffee | 30 ++++++------- static/coffee/directives.coffee | 4 +- .../{case-notifications.html => alerts.html} | 4 +- 5 files changed, 61 insertions(+), 61 deletions(-) rename static/templates/{case-notifications.html => alerts.html} (80%) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 5c7f53c14..85cc5512c 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -143,28 +143,28 @@ describe('controllers:', () -> expect(CaseService.unwatch).toHaveBeenCalledWith(test.case1) ) - it('should should add a notification on notification events', () -> - $scope.notifications = [] - $scope.$emit('notification', {type: 'foo'}) - expect($scope.notifications).toEqual([{type: 'foo'}]) + it('should should add a alert on alert events', () -> + $scope.alerts = [] + $scope.$emit('alert', {type: 'foo'}) + expect($scope.alerts).toEqual([{type: 'foo'}]) ) - it('should should ignore duplicate pod_load_api_failure notifications', () -> - $scope.notifications = [] + it('should should ignore duplicate pod_load_api_failure alerts', () -> + $scope.alerts = [] - $scope.$emit('notification', {type: 'pod_load_api_failure'}) - expect($scope.notifications).toEqual([{type: 'pod_load_api_failure'}]) + $scope.$emit('alert', {type: 'pod_load_api_failure'}) + expect($scope.alerts).toEqual([{type: 'pod_load_api_failure'}]) - $scope.$emit('notification', {type: 'pod_load_api_failure'}) - $scope.$emit('notification', {type: 'pod_load_api_failure'}) - expect($scope.notifications).toEqual([{type: 'pod_load_api_failure'}]) + $scope.$emit('alert', {type: 'pod_load_api_failure'}) + $scope.$emit('alert', {type: 'pod_load_api_failure'}) + expect($scope.alerts).toEqual([{type: 'pod_load_api_failure'}]) ) - describe('addNotification', () -> - it('should add the given notification', () -> - $scope.notifications = [] - $scope.addNotification({type: 'foo'}) - expect($scope.notifications).toEqual([{type: 'foo'}]) + describe('addAlert', () -> + it('should add the given alert', () -> + $scope.alerts = [] + $scope.addAlert({type: 'foo'}) + expect($scope.alerts).toEqual([{type: 'foo'}]) ) ) ) @@ -999,7 +999,7 @@ describe('controllers:', () -> expect($scope.podData.actions[0].isBusy).toBe(true) ) - it('should emit a notification event if unsuccessful', (done) -> + it('should emit a alert event if unsuccessful', (done) -> bindController() spyOn(PodApiService, 'trigger').and.returnValue($q.resolve({ @@ -1012,7 +1012,7 @@ describe('controllers:', () -> payload: {garply: 'waldo'} }) - $scope.$on('notification', (e, {type, payload}) -> + $scope.$on('alert', (e, {type, payload}) -> expect(type).toEqual('pod_action_failure') expect(payload).toEqual({fred: 'xxyyxx'}) done()) @@ -1031,7 +1031,7 @@ describe('controllers:', () -> $scope.$apply() ) - it('should emit a notification if trigger api method fails', (done) -> + it('should emit a alert if trigger api method fails', (done) -> bindController() spyOn(PodApiService, 'trigger') @@ -1042,14 +1042,14 @@ describe('controllers:', () -> payload: {garply: 'waldo'} }) - $scope.$on('notification', (e, {type, payload}) -> + $scope.$on('alert', (e, {type, payload}) -> expect(type).toEqual('pod_action_api_failure') done()) $scope.$apply() ) - it('should emit a notification if get api method fails', (done) -> + it('should emit a alert if get api method fails', (done) -> bindController() spyOn(PodApiService, 'get') @@ -1060,7 +1060,7 @@ describe('controllers:', () -> payload: {garply: 'waldo'} }) - $scope.$on('notification', (e, {type, payload}) -> + $scope.$on('alert', (e, {type, payload}) -> expect(type).toEqual('pod_action_api_failure') done()) diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 13ca31f84..1b9597419 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -263,7 +263,7 @@ describe('directives:', () -> #======================================================================= describe('cpAlert', () -> beforeEach(() -> - $rootScope.notifications = [] + $rootScope.alerts = [] ) it('should draw the alert', () -> @@ -278,15 +278,15 @@ describe('directives:', () -> ) #======================================================================= - # Tests for cpCaseNotifications + # Tests for cpAlerts #======================================================================= - describe('cpCaseNotifications', () -> + describe('cpAlerts', () -> beforeEach(() -> - $rootScope.notifications = [] + $rootScope.alerts = [] ) - it('should draw pod_action_failure notifications', () -> - $rootScope.notifications = [{ + it('should draw pod_action_failure alerts', () -> + $rootScope.alerts = [{ type: 'pod_action_failure', payload: {message: 'Foo'} }, { @@ -295,39 +295,39 @@ describe('directives:', () -> }] template = $compile(' - - + + ') el = template($rootScope)[0] $rootScope.$digest() - [notification1, notification2] = el.querySelectorAll('.alert') + [alert1, alert2] = el.querySelectorAll('.alert') - expect(notification1.classList.contains('alert-danger')).toBe(true) - expect(notification1.textContent).toMatch('Foo') + expect(alert1.classList.contains('alert-danger')).toBe(true) + expect(alert1.textContent).toMatch('Foo') - expect(notification2.classList.contains('alert-danger')).toBe(true) - expect(notification2.textContent).toMatch('Bar') + expect(alert2.classList.contains('alert-danger')).toBe(true) + expect(alert2.textContent).toMatch('Bar') ) - it('should draw pod_action_api_failure notifications', () -> - $rootScope.notifications = [{ + it('should draw pod_action_api_failure alerts', () -> + $rootScope.alerts = [{ type: 'pod_action_api_failure' }, { type: 'pod_action_api_failure' }] template = $compile(' - - + + ') el = template($rootScope)[0] $rootScope.$digest() - [notification1, notification2] = el.querySelectorAll('.alert') + [alert1, alert2] = el.querySelectorAll('.alert') - expect(notification1.classList.contains('alert-danger')).toBe(true) - expect(notification2.classList.contains('alert-danger')).toBe(true) + expect(alert1.classList.contains('alert-danger')).toBe(true) + expect(alert2.classList.contains('alert-danger')).toBe(true) ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index edc122730..dac2fb742 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -517,7 +517,7 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.newMessage = '' $scope.sending = false - $scope.notifications = [] + $scope.alerts = [] $scope.init = (caseId, maxMsgChars) -> $scope.caseId = caseId @@ -525,19 +525,19 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.refresh() - $scope.$on('notification', (e, notification) -> - $scope.addNotification(notification)) + $scope.$on('alert', (e, alert) -> + $scope.addAlert(alert)) $scope.$on('timelineChanged', (e) -> $scope.$broadcast('timelineChanged') if e.targetScope != $scope) - $scope.addNotification = (notification) -> - if (not shouldIgnoreNotification(notification)) - $scope.notifications.push(notification) + $scope.addAlert = (alert) -> + if (not shouldIgnoreAlert(alert)) + $scope.alerts.push(alert) - shouldIgnoreNotification = ({type}) -> + shouldIgnoreAlert = ({type}) -> type in SINGLETON_NOTIFICATIONS and - $scope.notifications.some((d) -> type == d.type) + $scope.alerts.some((d) -> type == d.type) $scope.refresh = () -> CaseService.fetchSingle($scope.caseId).then((caseObj) -> @@ -589,12 +589,12 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case #---------------------------------------------------------------------------- $scope.onWatch = () -> - UtilsService.confirmModal("Receive notifications for activity in this case?").then(() -> + UtilsService.confirmModal("Receive alerts for activity in this case?").then(() -> CaseService.watch($scope.caseObj) ) $scope.onUnwatch = () -> - UtilsService.confirmModal("Stop receiving notifications for activity in this case?").then(() -> + UtilsService.confirmModal("Stop receiving alerts for activity in this case?").then(() -> CaseService.unwatch($scope.caseObj) ) @@ -698,12 +698,12 @@ controllers.controller('LabelController', ['$scope', '$window', '$controller', ' ) $scope.onWatch = () -> - UtilsService.confirmModal("Receive notifications for new messages with this label?").then(() -> + UtilsService.confirmModal("Receive alerts for new messages with this label?").then(() -> LabelService.watch($scope.label) ) $scope.onUnwatch = () -> - UtilsService.confirmModal("Stop receiving notifications for new messages with this label?").then(() -> + UtilsService.confirmModal("Stop receiving alerts for new messages with this label?").then(() -> LabelService.unwatch($scope.label) ) @@ -901,17 +901,17 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'PodUI onLoadApiFailure = -> $scope.status = 'loading_failed' - $scope.$emit('notification', { + $scope.$emit('alert', { type: 'pod_load_api_failure' }) onTriggerApiFailure = -> - $scope.$emit('notification', { + $scope.$emit('alert', { type: 'pod_action_api_failure' }) onTriggerFailure = (payload) -> - $scope.$emit('notification', { + $scope.$emit('alert', { type: 'pod_action_failure', payload }) diff --git a/static/coffee/directives.coffee b/static/coffee/directives.coffee index fddba0f7a..c4b75941e 100644 --- a/static/coffee/directives.coffee +++ b/static/coffee/directives.coffee @@ -61,8 +61,8 @@ directives.directive('cpAlert', -> { }) -directives.directive('cpCaseNotifications', -> { - templateUrl: '/sitestatic/templates/case-notifications.html', +directives.directive('cpAlerts', -> { + templateUrl: '/sitestatic/templates/alerts.html', scope: {notifications: '='} }) diff --git a/static/templates/case-notifications.html b/static/templates/alerts.html similarity index 80% rename from static/templates/case-notifications.html rename to static/templates/alerts.html index 037716ac7..a89c55c57 100644 --- a/static/templates/case-notifications.html +++ b/static/templates/alerts.html @@ -1,7 +1,7 @@ -
+
- [[ notification.payload.message ]] + [[ alert.payload.message ]]
From 2c828bb53e82993264d832e323ac378720b09e1c Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 20:36:53 +0200 Subject: [PATCH 57/64] Remove stray template url in pod controller --- static/coffee/controllers.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index dac2fb742..639671762 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -862,10 +862,6 @@ controllers.controller('DateRangeController', ['$scope', ($scope) -> # Pod controller #============================================================================ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'PodUIService', ($q, $scope, PodApiService, PodUIService) -> - TEMPLATE_URLS = { - ACTION_CONFIRM: '/sitestatic/templates/pod-action-confirm.html' - } - $scope.init = (podId, caseId, podConfig) -> $scope.podId = podId $scope.caseId = caseId From 4a622be6a22d9545d9d810d1db28d7177576c7d1 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 20:49:49 +0200 Subject: [PATCH 58/64] Change alerts approach to display alerts that have their own markup using a template url --- karma/test-controllers.coffee | 36 ++++++++---- karma/test-directives.coffee | 25 +++++---- karma/test-services.coffee | 55 ++++++++++++++++++- static/coffee/controllers.coffee | 25 +++------ static/coffee/directives.coffee | 4 +- static/coffee/services.coffee | 13 +++++ static/templates/alerts.html | 21 ++----- static/templates/alerts/dummy-alert.html | 3 + .../alerts/pod-action-api-failure.html | 3 + .../templates/alerts/pod-action-failure.html | 3 + .../alerts/pod-load-api-failure.html | 3 + templates/cases/case_read.haml | 2 +- 12 files changed, 135 insertions(+), 58 deletions(-) create mode 100644 static/templates/alerts/dummy-alert.html create mode 100644 static/templates/alerts/pod-action-api-failure.html create mode 100644 static/templates/alerts/pod-action-failure.html create mode 100644 static/templates/alerts/pod-load-api-failure.html diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index 85cc5512c..e2598cd9e 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -787,6 +787,9 @@ describe('controllers:', () -> PodUIService = new class PodUIService confirmAction: -> $q.resolve() + alertActionFailure: () -> null + alertActionApiFailure: () -> null + alertLoadApiFailure: () -> null PodApiService = new class PodApiService PodApiServiceError: PodApiServiceError, @@ -999,22 +1002,27 @@ describe('controllers:', () -> expect($scope.podData.actions[0].isBusy).toBe(true) ) - it('should emit a alert event if unsuccessful', (done) -> + it('should emit an alert event if unsuccessful', (done) -> bindController() spyOn(PodApiService, 'trigger').and.returnValue($q.resolve({ success: false, - payload: {fred: 'xxyyxx'} + payload: {message: 'Foo'} })) + spyOn(PodUIService, 'alertActionFailure').and.returnValue('fakeResult') + $scope.trigger({ type: 'grault', payload: {garply: 'waldo'} }) - $scope.$on('alert', (e, {type, payload}) -> - expect(type).toEqual('pod_action_failure') - expect(payload).toEqual({fred: 'xxyyxx'}) + $scope.$on('alert', (e, res) -> + expect(res).toEqual('fakeResult') + + expect(PodUIService.alertActionFailure.calls.allArgs()) + .toEqual([['Foo']]) + done()) $scope.$apply() @@ -1031,37 +1039,43 @@ describe('controllers:', () -> $scope.$apply() ) - it('should emit a alert if trigger api method fails', (done) -> + it('should emit an alert if trigger api method fails', (done) -> bindController() spyOn(PodApiService, 'trigger') .and.returnValue($q.reject(new PodApiServiceError(null))) + spyOn(PodUIService, 'alertActionApiFailure') + .and.returnValue('fakeResult') + $scope.trigger({ type: 'grault', payload: {garply: 'waldo'} }) - $scope.$on('alert', (e, {type, payload}) -> - expect(type).toEqual('pod_action_api_failure') + $scope.$on('alert', (e, res) -> + expect(res).toEqual('fakeResult') done()) $scope.$apply() ) - it('should emit a alert if get api method fails', (done) -> + it('should emit an alert if get api method fails', (done) -> bindController() spyOn(PodApiService, 'get') .and.returnValue($q.reject(new PodApiServiceError(null))) + spyOn(PodUIService, 'alertActionApiFailure') + .and.returnValue('fakeResult') + $scope.trigger({ type: 'grault', payload: {garply: 'waldo'} }) - $scope.$on('alert', (e, {type, payload}) -> - expect(type).toEqual('pod_action_api_failure') + $scope.$on('alert', (e, res) -> + expect(res).toEqual('fakeResult') done()) $scope.$apply() diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index 1b9597419..a1b14f56f 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -285,19 +285,20 @@ describe('directives:', () -> $rootScope.alerts = [] ) - it('should draw pod_action_failure alerts', () -> + it('should draw alerts with no template urls', () -> $rootScope.alerts = [{ - type: 'pod_action_failure', - payload: {message: 'Foo'} + type: 'danger', + message: 'Foo' }, { - type: 'pod_action_failure', - payload: {message: 'Bar'} + type: 'danger', + message: 'Bar' }] template = $compile(' ') + el = template($rootScope)[0] $rootScope.$digest() @@ -310,24 +311,26 @@ describe('directives:', () -> expect(alert2.textContent).toMatch('Bar') ) - it('should draw pod_action_api_failure alerts', () -> + it('should draw alert with template urls', () -> $rootScope.alerts = [{ - type: 'pod_action_api_failure' + templateUrl: '/sitestatic/templates/alerts/dummy-alert.html' + context: {message: 'Foo'} }, { - type: 'pod_action_api_failure' + templateUrl: '/sitestatic/templates/alerts/dummy-alert.html' + context: {message: 'Bar'} }] template = $compile(' ') + el = template($rootScope)[0] $rootScope.$digest() [alert1, alert2] = el.querySelectorAll('.alert') - - expect(alert1.classList.contains('alert-danger')).toBe(true) - expect(alert2.classList.contains('alert-danger')).toBe(true) + expect(alert1.textContent).toMatch('Foo') + expect(alert2.textContent).toMatch('Bar') ) ) ) diff --git a/karma/test-services.coffee b/karma/test-services.coffee index 44ad4c67d..e68a51a61 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -821,9 +821,11 @@ describe('services:', () -> # Tests for PodUIService #======================================================================= describe('PodUIService', () -> + $compile = null PodUIService = null - beforeEach(inject((_PodUIService_) -> + beforeEach(inject((_$compile_, _PodUIService_) -> + $compile = _$compile_ PodUIService = _PodUIService_ )) @@ -869,5 +871,56 @@ describe('services:', () -> expect(rejected).toBe(true) ) ) + + describe('alertActionFailure', () -> + it('should draw the alert', () -> + $rootScope.alerts = [PodUIService.alertActionFailure('Foo')] + + template = $compile(' + + + ') + + el = template($rootScope)[0] + $rootScope.$digest() + + alert = el.querySelector('.alert') + expect(alert.textContent).toMatch('Foo') + ) + ) + + describe('alertActionApiFailure', () -> + it('should draw the alert', () -> + $rootScope.alerts = [PodUIService.alertActionApiFailure()] + + template = $compile(' + + + ') + + el = template($rootScope)[0] + $rootScope.$digest() + + alert = el.querySelector('.alert') + expect(alert.textContent).toMatch('action') + ) + ) + + describe('alertLoadApiFailure', () -> + it('should draw the alert', () -> + $rootScope.alerts = [PodUIService.alertLoadApiFailure()] + + template = $compile(' + + + ') + + el = template($rootScope)[0] + $rootScope.$digest() + + alert = el.querySelector('.alert') + expect(alert.textContent).toMatch('load') + ) + ) ) ) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 639671762..ff80e785d 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -16,7 +16,7 @@ CASE_SUMMARY_MAX_LEN = 255 CASE_NOTE_MAX_LEN = 1024 OUTGOING_TEXT_MAX_LEN = 480 -SINGLETON_NOTIFICATIONS = ['pod_load_api_failure'] +SINGLETON_ALERTS = ['pod_load_api_failure'] #============================================================================ # Inbox controller (DOM parent of messages and cases) @@ -532,11 +532,10 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case $scope.$broadcast('timelineChanged') if e.targetScope != $scope) $scope.addAlert = (alert) -> - if (not shouldIgnoreAlert(alert)) - $scope.alerts.push(alert) + $scope.alerts.push(alert) if (not shouldIgnoreAlert(alert)) shouldIgnoreAlert = ({type}) -> - type in SINGLETON_NOTIFICATIONS and + type in SINGLETON_ALERTS and $scope.alerts.some((d) -> type == d.type) $scope.refresh = () -> @@ -896,21 +895,13 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'PodUI onLoadApiFailure = -> $scope.status = 'loading_failed' - - $scope.$emit('alert', { - type: 'pod_load_api_failure' - }) + $scope.$emit('alert', PodUIService.alertLoadApiFailure()) onTriggerApiFailure = -> - $scope.$emit('alert', { - type: 'pod_action_api_failure' - }) - - onTriggerFailure = (payload) -> - $scope.$emit('alert', { - type: 'pod_action_failure', - payload - }) + $scope.$emit('alert', PodUIService.alertActionApiFailure()) + + onTriggerFailure = ({message}) -> + $scope.$emit('alert', PodUIService.alertActionFailure(message)) onTriggerSuccess = () -> $scope.$emit('timelineChanged') diff --git a/static/coffee/directives.coffee b/static/coffee/directives.coffee index c4b75941e..2cd665113 100644 --- a/static/coffee/directives.coffee +++ b/static/coffee/directives.coffee @@ -53,6 +53,7 @@ directives.directive('cpFieldvalue', () -> } ) + directives.directive('cpAlert', -> { restrict: 'E', transclude: true, @@ -63,9 +64,10 @@ directives.directive('cpAlert', -> { directives.directive('cpAlerts', -> { templateUrl: '/sitestatic/templates/alerts.html', - scope: {notifications: '='} + scope: {alerts: '='} }) + #===================================================================== # Pod directive #===================================================================== diff --git a/static/coffee/services.coffee b/static/coffee/services.coffee index cbf0bdf8a..0685e2ac4 100644 --- a/static/coffee/services.coffee +++ b/static/coffee/services.coffee @@ -587,6 +587,19 @@ services.factory('PodUIService', ['ModalService', (ModalService) -> templateUrl: '/sitestatic/templates/modals/pod-action-confirm.html', context: {name} }) + + alertActionFailure: (message) -> { + templateUrl: '/sitestatic/templates/alerts/pod-action-failure.html', + context: {message} + } + + alertActionApiFailure: () -> { + templateUrl: '/sitestatic/templates/alerts/pod-action-api-failure.html' + } + + alertLoadApiFailure: () -> { + templateUrl: '/sitestatic/templates/alerts/pod-load-api-failure.html' + } ]) diff --git a/static/templates/alerts.html b/static/templates/alerts.html index a89c55c57..9e75889ca 100644 --- a/static/templates/alerts.html +++ b/static/templates/alerts.html @@ -1,20 +1,9 @@ -
-
- - [[ alert.payload.message ]] - +
+
+ [[ alert.message ]]
-
- - Sorry, we encountered a problem on our side while completing your action. - -
- -
- - Sorry, a problem on our side prevented some of the information on this - page from loading. - +
+
diff --git a/static/templates/alerts/dummy-alert.html b/static/templates/alerts/dummy-alert.html new file mode 100644 index 000000000..047843b9e --- /dev/null +++ b/static/templates/alerts/dummy-alert.html @@ -0,0 +1,3 @@ + + [[ alert.context.message ]] + diff --git a/static/templates/alerts/pod-action-api-failure.html b/static/templates/alerts/pod-action-api-failure.html new file mode 100644 index 000000000..6f568104c --- /dev/null +++ b/static/templates/alerts/pod-action-api-failure.html @@ -0,0 +1,3 @@ + + Sorry, we encountered a problem on our side while completing your action. + diff --git a/static/templates/alerts/pod-action-failure.html b/static/templates/alerts/pod-action-failure.html new file mode 100644 index 000000000..572f88408 --- /dev/null +++ b/static/templates/alerts/pod-action-failure.html @@ -0,0 +1,3 @@ + + [[ alert.context.message ]] + diff --git a/static/templates/alerts/pod-load-api-failure.html b/static/templates/alerts/pod-load-api-failure.html new file mode 100644 index 000000000..fef164b05 --- /dev/null +++ b/static/templates/alerts/pod-load-api-failure.html @@ -0,0 +1,3 @@ + + Sorry, a problem on our side prevented some of the information on this page from loading. + diff --git a/templates/cases/case_read.haml b/templates/cases/case_read.haml index 678d01e7d..b2317051a 100644 --- a/templates/cases/case_read.haml +++ b/templates/cases/case_read.haml @@ -66,7 +66,7 @@ .row .col-md-8 - + %form.sendform{ ng-if:"!caseObj.is_closed" } // TODO maxlength not working From a894215bb469dc7cfda6aee901a251f017b1f94e Mon Sep 17 00:00:00 2001 From: justinvdm Date: Mon, 1 Aug 2016 21:15:23 +0200 Subject: [PATCH 59/64] Undisable pod actions after error --- karma/test-controllers.coffee | 26 ++++++++++++++++++++++++++ static/coffee/controllers.coffee | 14 +++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/karma/test-controllers.coffee b/karma/test-controllers.coffee index fd5c0776f..b3de4aa55 100644 --- a/karma/test-controllers.coffee +++ b/karma/test-controllers.coffee @@ -999,6 +999,32 @@ describe('controllers:', () -> expect($scope.podData.actions[0].isBusy).toBe(true) ) + it('should mark the action as not busy after api failure', () -> + $scope.podData.actions = [{ + type: 'grault' + isBusy: false, + payload: {} + }, { + type: 'fred', + isBusy: false, + payload: {} + }] + + bindController() + + spyOn(PodApiService, 'get') + .and.returnValue($q.reject(new PodApiServiceError(null))) + + $scope.trigger({ + type: 'grault', + payload: {garply: 'waldo'} + }) + + $scope.$apply() + + expect($scope.podData.actions[0].isBusy).toBe(false) + ) + it('should emit a notification event if unsuccessful', (done) -> bindController() diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index 80ff9ecc0..81d2046c0 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -862,6 +862,8 @@ controllers.controller('DateRangeController', ['$scope', ($scope) -> # Pod controller #============================================================================ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'CaseModals', ($q, $scope, PodApiService, CaseModals) -> + {PodApiServiceError} = PodApiService + $scope.init = (podId, caseId, podConfig) -> $scope.podId = podId $scope.caseId = caseId @@ -870,7 +872,7 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'CaseM $scope.update() .then(-> $scope.status = 'idle') - .catch(utils.trap(PodApiService.PodApiServiceError, onLoadApiFailure, $q.reject)) + .catch(utils.trap(PodApiServiceError, onLoadApiFailure, $q.reject)) $scope.update = -> PodApiService.get($scope.podId, $scope.caseId) @@ -883,16 +885,14 @@ controllers.controller('PodController', ['$q', '$scope', 'PodApiService', 'CaseM .then(-> $scope.podData.actions = updateAction(type, {isBusy: true})) .then(-> PodApiService.trigger($scope.podId, $scope.caseId, type, payload)) .then((res) -> onTriggerDone(type, res)) - .catch(utils.trap(PodApiService.PodApiServiceError, onTriggerApiFailure, $q.reject)) + .catch(utils.trap(PodApiServiceError, onTriggerApiFailure, $q.reject)) + .then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) onTriggerDone = (type, {success, payload}) -> if success - p = onTriggerSuccess() + onTriggerSuccess() else - p = onTriggerFailure(payload) - - $q.resolve(p) - .then(-> $scope.podData.actions = updateAction(type, {isBusy: false})) + onTriggerFailure(payload) confirmAction = (name) -> CaseModals.confirm({ From d48c4c37b7ee5e4e667c0e9a08ccdcfeb4a54a7d Mon Sep 17 00:00:00 2001 From: justinvdm Date: Tue, 2 Aug 2016 18:11:46 +0200 Subject: [PATCH 60/64] Include user's name on case action when pod action is triggered --- casepro/pods/tests/test_views.py | 6 +++++- casepro/pods/views.py | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/casepro/pods/tests/test_views.py b/casepro/pods/tests/test_views.py index 1b03db300..3b726ceb8 100644 --- a/casepro/pods/tests/test_views.py +++ b/casepro/pods/tests/test_views.py @@ -166,6 +166,7 @@ def test_case_action_note_created_on_successful_action(self): }) self.assertEqual(response.status_code, 200) + message = "Type foo Params {u'foo': u'bar'}" self.assertEqual(response.json, { 'success': True, @@ -173,5 +174,8 @@ def test_case_action_note_created_on_successful_action(self): 'message': message } }) + [caseaction] = CaseAction.objects.all() - self.assertEqual(caseaction.note, message) + self.assertEqual( + caseaction.note, + "%s %s" % (self.admin.username, message)) diff --git a/casepro/pods/views.py b/casepro/pods/views.py index 445da709a..2323d33b2 100644 --- a/casepro/pods/views.py +++ b/casepro/pods/views.py @@ -7,6 +7,9 @@ from casepro.pods import registry +ACTION_NOTE_CONTENT = "%(username)s %(message)s" + + def read_pod_data(request, index): """Delegates to the `read_data` function of the correct pod.""" if request.method != 'GET': @@ -47,6 +50,10 @@ def perform_pod_action(request, index): success, payload = pod.perform_action(action_data.get('type'), action_data.get('payload', {})) if success is True: case = Case.objects.get(id=case_id) - CaseAction.create(case, request.user, CaseAction.ADD_NOTE, note=payload.get('message')) + note = ACTION_NOTE_CONTENT % { + 'username': request.user.username, + 'message': payload.get('message'), + } + CaseAction.create(case, request.user, CaseAction.ADD_NOTE, note=note) return JsonResponse({'success': success, 'payload': payload}) From 89b2cb3e09c7c34612deac914a0fd5172384e88e Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 5 Aug 2016 12:11:23 +0200 Subject: [PATCH 61/64] Update pod docstrings --- casepro/pods/base.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/casepro/pods/base.py b/casepro/pods/base.py index aa14bab59..8718c0997 100644 --- a/casepro/pods/base.py +++ b/casepro/pods/base.py @@ -44,6 +44,15 @@ def read_data(self, params): payload is what is sent to the 'perform_action' function to determine which button has been pressed, and 'name' is the text that is displayed on the button. + Each action may include the following optional fields: + - ``busy_text``: used as the action's corresponding + button's text while waiting on a response from the pod's api side + when the action is triggered. Defaults to the value of the ``name`` + field. + - ``confirm``: whether a confirmation modal should be shown to + confirm whether the user would like to perform the action. Defaults + to ``false``. + Example: { 'items': [ @@ -57,6 +66,8 @@ def read_data(self, params): 'type': 'remove_edd', 'name': 'Remove EDD', 'payload': {}, + 'busy_text': 'Removing EDD', + 'confirm': True }, ], } From b92c1ab60622f1f6cd7091607cc6396b54b8ab0b Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 5 Aug 2016 15:26:45 +0200 Subject: [PATCH 62/64] Fix bad search-and-replace for notification -> alert (thanks @rudigiesler) --- static/coffee/controllers.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/coffee/controllers.coffee b/static/coffee/controllers.coffee index efbc64a0e..975933400 100644 --- a/static/coffee/controllers.coffee +++ b/static/coffee/controllers.coffee @@ -588,12 +588,12 @@ controllers.controller('CaseController', ['$scope', '$window', '$timeout', 'Case #---------------------------------------------------------------------------- $scope.onWatch = () -> - UtilsService.confirmModal("Receive alerts for activity in this case?").then(() -> + UtilsService.confirmModal("Receive notifications for activity in this case?").then(() -> CaseService.watch($scope.caseObj) ) $scope.onUnwatch = () -> - UtilsService.confirmModal("Stop receiving alerts for activity in this case?").then(() -> + UtilsService.confirmModal("Stop receiving notifications for activity in this case?").then(() -> CaseService.unwatch($scope.caseObj) ) @@ -697,12 +697,12 @@ controllers.controller('LabelController', ['$scope', '$window', '$controller', ' ) $scope.onWatch = () -> - UtilsService.confirmModal("Receive alerts for new messages with this label?").then(() -> + UtilsService.confirmModal("Receive notifications for new messages with this label?").then(() -> LabelService.watch($scope.label) ) $scope.onUnwatch = () -> - UtilsService.confirmModal("Stop receiving alerts for new messages with this label?").then(() -> + UtilsService.confirmModal("Stop receiving notifications for new messages with this label?").then(() -> LabelService.unwatch($scope.label) ) From 52d6c3ec657b431495c8aa1fb8f671e5bbc6693c Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 5 Aug 2016 16:19:02 +0200 Subject: [PATCH 63/64] Move dummy templates to karma directory --- karma.conf.coffee | 6 ++++-- {static => karma}/templates/alerts/dummy-alert.html | 0 {static => karma}/templates/modals/dummy-confirm.html | 0 karma/test-directives.coffee | 4 ++-- karma/test-services.coffee | 6 +++--- 5 files changed, 9 insertions(+), 7 deletions(-) rename {static => karma}/templates/alerts/dummy-alert.html (100%) rename {static => karma}/templates/modals/dummy-confirm.html (100%) diff --git a/karma.conf.coffee b/karma.conf.coffee index f375ce0ec..fe5763ec9 100644 --- a/karma.conf.coffee +++ b/karma.conf.coffee @@ -22,6 +22,7 @@ module.exports = (config) -> # templates 'static/templates/**/*.html', + 'karma/templates/**/*.html', # the code we are testing 'static/coffee/*.coffee', @@ -47,6 +48,7 @@ module.exports = (config) -> '**/*.coffee': ['coffee'], 'static/**/*.coffee': ['coverage'] 'static/templates/**/*.html': ['ng-html2js'] + 'karma/templates/**/*.html': ['ng-html2js'] } # this makes sure that we get coffeescript line numbers instead @@ -59,8 +61,8 @@ module.exports = (config) -> path.replace /\.js$/, '.coffee' ngHtml2JsPreprocessor: - stripPrefix: 'static' - prependPrefix: '/sitestatic' + stripPrefix: 'static/' + prependPrefix: '/sitestatic/' moduleName: 'templates' # test results reporter to use diff --git a/static/templates/alerts/dummy-alert.html b/karma/templates/alerts/dummy-alert.html similarity index 100% rename from static/templates/alerts/dummy-alert.html rename to karma/templates/alerts/dummy-alert.html diff --git a/static/templates/modals/dummy-confirm.html b/karma/templates/modals/dummy-confirm.html similarity index 100% rename from static/templates/modals/dummy-confirm.html rename to karma/templates/modals/dummy-confirm.html diff --git a/karma/test-directives.coffee b/karma/test-directives.coffee index a1b14f56f..402943f19 100644 --- a/karma/test-directives.coffee +++ b/karma/test-directives.coffee @@ -313,10 +313,10 @@ describe('directives:', () -> it('should draw alert with template urls', () -> $rootScope.alerts = [{ - templateUrl: '/sitestatic/templates/alerts/dummy-alert.html' + templateUrl: '/sitestatic/karma/templates/alerts/dummy-alert.html' context: {message: 'Foo'} }, { - templateUrl: '/sitestatic/templates/alerts/dummy-alert.html' + templateUrl: '/sitestatic/karma/templates/alerts/dummy-alert.html' context: {message: 'Bar'} }] diff --git a/karma/test-services.coffee b/karma/test-services.coffee index e68a51a61..37df606da 100644 --- a/karma/test-services.coffee +++ b/karma/test-services.coffee @@ -763,7 +763,7 @@ describe('services:', () -> describe('if a template url is given', () -> it('should draw the modal', () -> ModalService.confirm({ - templateUrl: '/sitestatic/templates/modals/dummy-confirm.html', + templateUrl: '/sitestatic/karma/templates/modals/dummy-confirm.html', context: {title: 'Foo'} }) @@ -780,7 +780,7 @@ describe('services:', () -> fulfilled = false ModalService.confirm({ - templateUrl: '/sitestatic/templates/modals/dummy-confirm.html', + templateUrl: '/sitestatic/karma/templates/modals/dummy-confirm.html', context: {title: 'Foo'} }) .then(-> fulfilled = true) @@ -799,7 +799,7 @@ describe('services:', () -> rejected = false ModalService.confirm({ - templateUrl: '/sitestatic/templates/modals/dummy-confirm.html', + templateUrl: '/sitestatic/karma/templates/modals/dummy-confirm.html', context: {title: 'Foo'} }) .catch(-> rejected = true) From b7e7792109df276e9dc180f858f988ac9e3fc33b Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 5 Aug 2016 17:57:34 +0200 Subject: [PATCH 64/64] Restrict access for case views --- casepro/pods/tests/test_views.py | 146 ++++++++++++++++++++++++------- casepro/pods/views.py | 46 +++++++++- 2 files changed, 156 insertions(+), 36 deletions(-) diff --git a/casepro/pods/tests/test_views.py b/casepro/pods/tests/test_views.py index 3b726ceb8..4eb4290f8 100644 --- a/casepro/pods/tests/test_views.py +++ b/casepro/pods/tests/test_views.py @@ -14,6 +14,17 @@ class ViewPodDataView(BaseCasesTest): """ Tests relating to the view_pod_data view. """ + def setUp(self): + super(ViewPodDataView, self).setUp() + contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name') + msg = self.create_message(self.unicef, 0, contact, 'Test message') + self.case = self.create_case(self.unicef, contact, self.moh, msg) + self.login(self.admin) + + with self.settings(PODS=[{'label': 'base_pod'}]): + from casepro.pods import registry + reload(registry) + def test_invalid_method(self): """ If the request method is not GET, an appropriate error should be @@ -31,11 +42,9 @@ def test_pod_doesnt_exist(self): If the requested pod id is invalid, an appropriate 404 error should be returned. """ - with self.settings(PODS=[]): - from casepro.pods import registry - reload(registry) response = self.url_get( - 'unicef', reverse('read_pod_data', args=('0',))) + 'unicef', reverse('read_pod_data', args=('1',)), params={'case_id': self.case.id}) + self.assertEqual(response.status_code, 404) self.assertEqual(response.json, { 'reason': 'Pod does not exist'} @@ -45,14 +54,53 @@ def test_pod_valid_request(self): """ If it is a valid get request, the data from the pod should be returned. """ - with self.settings(PODS=[{'label': 'base_pod'}]): - from casepro.pods import registry - reload(registry) response = self.url_get( - 'unicef', reverse('read_pod_data', args=('0',))) + 'unicef', reverse('read_pod_data', args=('0',)), params={'case_id': self.case.id}) + self.assertEqual(response.status_code, 200) self.assertEqual(response.json, {}) + def test_case_id_required(self): + ''' + If the case id is not present in the request, an error response should be returned. + ''' + response = self.url_get( + 'unicef', reverse('read_pod_data', args=('0',))) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json, { + 'reason': 'Request needs "case_id" query parameter' + }) + + def test_case_not_found(self): + ''' + If the case is not found, an error response should be returned. + ''' + response = self.url_get( + 'unicef', reverse('read_pod_data', args=('0',)), + params={'case_id': 23}) + + self.assertEqual(response.status_code, 404) + self.assertEqual(response.json, { + 'reason': 'Case with id 23 not found' + }) + + def test_unauthorized(self): + ''' + If the user does not have read permission, the request should be denied. + ''' + self.login(self.user4) + + response = self.url_get( + 'unicef', reverse('read_pod_data', args=('0',)), + params={'case_id': self.case.id}) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.json, { + 'reason': ( + "The request's authentication details do not corresond " + "to the required access level for accessing this resource") + }) + @modify_settings(INSTALLED_APPS={ 'append': 'casepro.pods.PodPlugin', @@ -61,6 +109,19 @@ class PerformPodActionView(BaseCasesTest): """ Tests relating to the perform_pod_action view. """ + def setUp(self): + super(PerformPodActionView, self).setUp() + contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name') + msg = self.create_message(self.unicef, 0, contact, 'Test message') + self.case = self.create_case(self.unicef, contact, self.moh, msg) + self.login(self.admin) + + CaseAction.objects.all().delete() + + with self.settings(PODS=[{'label': 'base_pod'}]): + from casepro.pods import registry + reload(registry) + def test_invalid_method(self): """ If the request method is not POST, an appropriate error should be @@ -78,11 +139,8 @@ def test_pod_doesnt_exist(self): If the requested pod id is invalid, an appropriate 404 error should be returned. """ - with self.settings(PODS=[]): - from casepro.pods import registry - reload(registry) response = self.url_post_json( - 'unicef', reverse('perform_pod_action', args=('0',)), {}) + 'unicef', reverse('perform_pod_action', args=('23',)), {'case_id': self.case.id}) self.assertEqual(response.status_code, 404) self.assertEqual(response.json, { 'reason': 'Pod does not exist'} @@ -105,10 +163,6 @@ def test_case_id_required(self): ''' If the case id is not present in the request, an error response should be returned. ''' - with self.settings(PODS=[{'label': 'base_pod'}]): - from casepro.pods import registry - reload(registry) - response = self.url_post_json( 'unicef', reverse('perform_pod_action', args=('0',)), {}) self.assertEqual(response.status_code, 400) @@ -120,16 +174,8 @@ def test_pod_valid_request(self): """ If it is a valid post request, the action should be performed. """ - with self.settings(PODS=[{'label': 'base_pod'}]): - from casepro.pods import registry - reload(registry) - - contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name') - msg = self.create_message(self.unicef, 0, contact, 'Test message') - case = self.create_case(self.unicef, contact, self.moh, msg) - response = self.url_post_json( - 'unicef', reverse('perform_pod_action', args=('0',)), {'case_id': case.id}) + 'unicef', reverse('perform_pod_action', args=('0',)), {'case_id': self.case.id}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json, { 'success': False, @@ -145,20 +191,13 @@ def test_case_action_note_created_on_successful_action(self): ''' If the action is successful, a case action note should be created. ''' - CaseAction.objects.all().delete() - self.login(self.admin) - with self.settings(PODS=[{'label': 'success_pod'}]): from casepro.pods import registry reload(registry) - contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name') - msg = self.create_message(self.unicef, 0, contact, 'Test message') - case = self.create_case(self.unicef, contact, self.moh, msg) - response = self.url_post_json( 'unicef', reverse('perform_pod_action', args=('0',)), { - 'case_id': case.id, + 'case_id': self.case.id, 'action': { 'type': 'foo', 'payload': {'foo': 'bar'}, @@ -179,3 +218,44 @@ def test_case_action_note_created_on_successful_action(self): self.assertEqual( caseaction.note, "%s %s" % (self.admin.username, message)) + + def test_case_not_found(self): + ''' + If the case is not found, an error response should be returned. + ''' + + response = self.url_post_json( + 'unicef', reverse('perform_pod_action', args=('0',)), { + 'case_id': 23, + 'action': { + 'type': 'foo', + 'payload': {'foo': 'bar'}, + }, + }) + + self.assertEqual(response.status_code, 404) + self.assertEqual(response.json, { + 'reason': 'Case with id 23 not found' + }) + + def test_unauthorized(self): + ''' + If the user does not have update permission, the request should be denied. + ''' + self.login(self.user4) + + response = self.url_post_json( + 'unicef', reverse('perform_pod_action', args=('0',)), { + 'case_id': self.case.id, + 'action': { + 'type': 'foo', + 'payload': {'foo': 'bar'}, + }, + }) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.json, { + 'reason': ( + "The request's authentication details do not corresond " + "to the required access level for accessing this resource") + }) diff --git a/casepro/pods/views.py b/casepro/pods/views.py index 2323d33b2..a4902595d 100644 --- a/casepro/pods/views.py +++ b/casepro/pods/views.py @@ -3,10 +3,9 @@ import json from django.http import JsonResponse -from casepro.cases.models import Case, CaseAction +from casepro.cases.models import Case, CaseAction, AccessLevel from casepro.pods import registry - ACTION_NOTE_CONTENT = "%(username)s %(message)s" @@ -15,6 +14,19 @@ def read_pod_data(request, index): if request.method != 'GET': return JsonResponse({'reason': 'Method not allowed'}, status=405) + case_id = request.GET.get('case_id') + if case_id is None: + return JsonResponse(status=400, data={ + 'reason': 'Request needs "case_id" query parameter' + }) + + case = get_case(case_id) + if case is None: + return case_not_found_response(case_id) + + if not has_case_access(request.user, case, AccessLevel.read): + return authorization_failure_response() + try: pod = registry.pods[int(index)] except IndexError: @@ -46,10 +58,16 @@ def perform_pod_action(request, index): return JsonResponse( {'reason': 'Request object needs to have a "case_id" field'}, status=400) + case = get_case(case_id) + if case is None: + return case_not_found_response(case_id) + + if not has_case_access(request.user, case, AccessLevel.update): + return authorization_failure_response() + action_data = data.get('action', {}) success, payload = pod.perform_action(action_data.get('type'), action_data.get('payload', {})) if success is True: - case = Case.objects.get(id=case_id) note = ACTION_NOTE_CONTENT % { 'username': request.user.username, 'message': payload.get('message'), @@ -57,3 +75,25 @@ def perform_pod_action(request, index): CaseAction.create(case, request.user, CaseAction.ADD_NOTE, note=note) return JsonResponse({'success': success, 'payload': payload}) + + +def has_case_access(user, case, level): + return case.access_level(user) >= level + + +def get_case(case_id): + return Case.objects.filter(id=case_id).first() + + +def case_not_found_response(id): + return JsonResponse(status=404, data={ + 'reason': "Case with id %s not found" % id + }) + + +def authorization_failure_response(): + return JsonResponse(status=403, data={ + 'reason': ( + "The request's authentication details do not corresond " + "to the required access level for accessing this resource") + })