diff --git a/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.js b/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.js
index 65287bdd4e..4abcb77f2c 100644
--- a/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.js
+++ b/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.js
@@ -1,66 +1,52 @@
-angular
- .module('campusContactsApp')
- .factory('groupMembersModalService', groupMembersModalService);
+angular.module('campusContactsApp').factory('groupMembersModalService', groupMembersModalService);
-function groupMembersModalService(
- httpProxy,
- JsonApiDataStore,
- modelsService,
- _,
-) {
- return {
- // Load another chunk of group members
- loadMoreGroupMembers: function (group, listLoader) {
- // All of the group's group_memberships should have already been loaded by groupsService.loadLeaders,
- // but load the group_memberships anyway, just in case that request has not completed yet
- return listLoader.loadMore({
- include: 'email_addresses,phone_numbers,group_memberships',
- 'filters[organization_ids]': group.organization.id,
- 'filters[group_ids]': group.id,
- });
- },
+function groupMembersModalService(httpProxy, JsonApiDataStore, modelsService, _) {
+ return {
+ // Load another chunk of group members
+ loadMoreGroupMembers: function (group, listLoader) {
+ // All of the group's group_memberships should have already been loaded by groupsService.loadLeaders,
+ // but load the group_memberships anyway, just in case that request has not completed yet
+ return listLoader.loadMore({
+ include: 'email_addresses,phone_numbers,group_memberships',
+ 'filters[organization_ids]': group.organization.id,
+ 'filters[group_ids]': group.id,
+ });
+ },
- // Add a member to the group
- addMember: function (group, person) {
- var membership = new JsonApiDataStore.Model('group_membership');
- membership.setAttribute('role', 'member');
- membership.setAttribute('group_id', group.id);
- membership.setAttribute('person_id', person.id);
+ // Add a member to the group
+ addMember: function (group, person) {
+ const membership = new JsonApiDataStore.Model('group_membership');
+ membership.setAttribute('role', 'member');
+ membership.setAttribute('group_id', group.id);
+ membership.setAttribute('person_id', person.id);
- var payload = membership.serialize();
- return httpProxy
- .post(
- modelsService.getModelMetadata('group_membership').url.all,
- payload,
- {
- errorMessage: 'error.messages.group_members.add_member',
- },
- )
- .then(httpProxy.extractModel)
- .then(function (membership) {
- group.group_memberships.push(membership);
- return membership;
- });
- },
+ const payload = membership.serialize();
+ return httpProxy
+ .post(modelsService.getModelMetadata('group_membership').url.all, payload, {
+ errorMessage: 'error.messages.group_members.add_member',
+ })
+ .then(httpProxy.extractModel)
+ .then(function (membership) {
+ group.group_memberships.push(membership);
+ return membership;
+ });
+ },
- // Remove a member from a group
- removeMember: function (group, membership) {
- var url = modelsService
- .getModelMetadata('group_membership')
- .url.single(membership.id);
- return httpProxy
- .delete(
- url,
- {},
- {
- errorMessage:
- 'error.messages.group_members.remove_member',
- },
- )
- .then(function () {
- _.pull(group.group_memberships, membership);
- return membership;
- });
- },
- };
+ // Remove a member from a group
+ removeMember: function (group, membership) {
+ const url = modelsService.getModelMetadata('group_membership').url.single(membership.id);
+ return httpProxy
+ .delete(
+ url,
+ {},
+ {
+ errorMessage: 'error.messages.group_members.remove_member',
+ },
+ )
+ .then(function () {
+ _.pull(group.group_memberships, membership);
+ return membership;
+ });
+ },
+ };
}
diff --git a/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.spec.js b/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.spec.js
index 20c9d330cb..41811adce3 100644
--- a/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.spec.js
+++ b/app/assets/javascripts/angular/components/groupMembersModal/groupMembersModal.service.spec.js
@@ -1,82 +1,68 @@
import 'angular-mocks';
// Constants
-var groupMembersModalService, $q, $rootScope, httpProxy, _;
+let groupMembersModalService, $q, $rootScope, httpProxy, _;
// Add better asynchronous support to a test function
// The test function must return a promise
// The promise will automatically be bound to "done" and the $rootScope will be automatically digested
function asynchronous(fn) {
- return function (done) {
- var returnValue = fn.call(this, done);
- returnValue
- .then(function () {
- done();
- })
- .catch(function (err) {
- done.fail(err);
- });
- $rootScope.$apply();
- return returnValue;
- };
+ return function (done) {
+ const returnValue = fn.call(this, done);
+ returnValue
+ .then(function () {
+ done();
+ })
+ .catch(function (err) {
+ done.fail(err);
+ });
+ $rootScope.$apply();
+ return returnValue;
+ };
}
describe('groupMembersModalService service', function () {
- beforeEach(inject(function (
- _groupMembersModalService_,
- _$q_,
- _$rootScope_,
- _httpProxy_,
- ___,
- ) {
- groupMembersModalService = _groupMembersModalService_;
- $q = _$q_;
- $rootScope = _$rootScope_;
- httpProxy = _httpProxy_;
- _ = ___;
+ beforeEach(inject(function (_groupMembersModalService_, _$q_, _$rootScope_, _httpProxy_, ___) {
+ groupMembersModalService = _groupMembersModalService_;
+ $q = _$q_;
+ $rootScope = _$rootScope_;
+ httpProxy = _httpProxy_;
+ _ = ___;
- this.group = {
- id: 1,
- group_memberships: [{ id: 11 }],
- };
- }));
+ this.group = {
+ id: 1,
+ group_memberships: [{ id: 11 }],
+ };
+ }));
- describe('groupMembersModalService.addMember', function () {
- it(
- 'should add the new membership to the group',
- asynchronous(function () {
- spyOn(httpProxy, 'extractModel').and.returnValue({ id: 12 });
- spyOn(httpProxy, 'callHttp').and.returnValue($q.resolve());
+ describe('groupMembersModalService.addMember', function () {
+ it(
+ 'should add the new membership to the group',
+ asynchronous(function () {
+ spyOn(httpProxy, 'extractModel').and.returnValue({ id: 12 });
+ spyOn(httpProxy, 'callHttp').and.returnValue($q.resolve());
- var _this = this;
- var person = { id: 21 };
- return groupMembersModalService
- .addMember(this.group, person)
- .then(function () {
- expect(
- _.map(_this.group.group_memberships, 'id'),
- ).toEqual([11, 12]);
- });
- }),
- );
- });
+ const _this = this;
+ const person = { id: 21 };
+ return groupMembersModalService.addMember(this.group, person).then(function () {
+ expect(_.map(_this.group.group_memberships, 'id')).toEqual([11, 12]);
+ });
+ }),
+ );
+ });
- describe('groupMembersModalService.removeMember', function () {
- it(
- 'should remove the membership from the group',
- asynchronous(function () {
- spyOn(httpProxy, 'callHttp').and.returnValue($q.resolve());
+ describe('groupMembersModalService.removeMember', function () {
+ it(
+ 'should remove the membership from the group',
+ asynchronous(function () {
+ spyOn(httpProxy, 'callHttp').and.returnValue($q.resolve());
- var _this = this;
- var membership = this.group.group_memberships[0];
- return groupMembersModalService
- .removeMember(this.group, membership)
- .then(function () {
- expect(
- _.map(_this.group.group_memberships, 'id'),
- ).toEqual([]);
- });
- }),
- );
- });
+ const _this = this;
+ const membership = this.group.group_memberships[0];
+ return groupMembersModalService.removeMember(this.group, membership).then(function () {
+ expect(_.map(_this.group.group_memberships, 'id')).toEqual([]);
+ });
+ }),
+ );
+ });
});
diff --git a/app/assets/javascripts/angular/components/iconButton/iconButton.component.js b/app/assets/javascripts/angular/components/iconButton/iconButton.component.js
index ba3d801d3a..a6179c56b9 100644
--- a/app/assets/javascripts/angular/components/iconButton/iconButton.component.js
+++ b/app/assets/javascripts/angular/components/iconButton/iconButton.component.js
@@ -8,24 +8,24 @@ import './iconButton.scss';
*/
angular.module('campusContactsApp').component('iconButton', {
- controller: iconButtonController,
- bindings: {
- disabled: '<',
- icon: '<',
- size: '<',
- },
- template: template,
+ controller: iconButtonController,
+ bindings: {
+ disabled: '<',
+ icon: '<',
+ size: '<',
+ },
+ template,
});
function iconButtonController() {
- var vm = this;
+ const vm = this;
- vm.handleClick = handleClick;
+ vm.handleClick = handleClick;
- function handleClick(event) {
- if (vm.disabled) {
- event.preventDefault();
- event.stopPropagation();
- }
+ function handleClick(event) {
+ if (vm.disabled) {
+ event.preventDefault();
+ event.stopPropagation();
}
+ }
}
diff --git a/app/assets/javascripts/angular/components/iconButton/iconButton.html b/app/assets/javascripts/angular/components/iconButton/iconButton.html
index 3f549d4475..9b3140af0d 100644
--- a/app/assets/javascripts/angular/components/iconButton/iconButton.html
+++ b/app/assets/javascripts/angular/components/iconButton/iconButton.html
@@ -1,7 +1,7 @@
diff --git a/app/assets/javascripts/angular/components/iconModal/iconModal.component.js b/app/assets/javascripts/angular/components/iconModal/iconModal.component.js
index 8591d43b01..d6e52cb28d 100644
--- a/app/assets/javascripts/angular/components/iconModal/iconModal.component.js
+++ b/app/assets/javascripts/angular/components/iconModal/iconModal.component.js
@@ -2,10 +2,10 @@ import template from './iconModal.html';
import './iconModal.scss';
angular.module('campusContactsApp').component('iconModal', {
- template: template,
- bindings: {
- resolve: '<',
- close: '&',
- dismiss: '&',
- },
+ template,
+ bindings: {
+ resolve: '<',
+ close: '&',
+ dismiss: '&',
+ },
});
diff --git a/app/assets/javascripts/angular/components/iconModal/iconModal.html b/app/assets/javascripts/angular/components/iconModal/iconModal.html
index 0682d73e91..bad2a70a73 100644
--- a/app/assets/javascripts/angular/components/iconModal/iconModal.html
+++ b/app/assets/javascripts/angular/components/iconModal/iconModal.html
@@ -1,44 +1,34 @@
-
-
-
-
- {{ $ctrl.resolve.title }}
-
-
-
-
- {{ paragraph }}
-
-
-
diff --git a/app/assets/javascripts/angular/components/interaction/interaction.component.js b/app/assets/javascripts/angular/components/interaction/interaction.component.js
index 6336a3cf68..c1af728de7 100644
--- a/app/assets/javascripts/angular/components/interaction/interaction.component.js
+++ b/app/assets/javascripts/angular/components/interaction/interaction.component.js
@@ -4,65 +4,59 @@ import template from './interaction.html';
import './interaction.scss';
angular.module('campusContactsApp').component('interaction', {
- controller: interactionController,
- template: template,
- bindings: {
- person: '<',
- interaction: '<',
- onDelete: '&',
- },
+ controller: interactionController,
+ template,
+ bindings: {
+ person: '<',
+ interaction: '<',
+ onDelete: '&',
+ },
});
-function interactionController(
- interactionsService,
- confirmModalService,
- $filter,
-) {
- var vm = this;
-
- vm.editInteraction = false;
- vm.moment = moment;
-
- vm.$onInit = activate;
- vm.updateInteraction = updateInteraction;
- vm.deleteInteraction = deleteInteraction;
-
- vm.modifyInteractionState = 'view';
-
- function activate() {
- vm.interactionType = interactionsService.getInteractionType(
- vm.interaction.interaction_type_id,
- );
- }
-
- function updateInteraction() {
- vm.modifyInteractionState = 'saving';
- return interactionsService
- .updateInteraction(vm.interaction)
- .then(function () {
- vm.modifyInteractionState = 'view';
- })
- .catch(function () {
- vm.modifyInteractionState = 'edit';
- });
- }
-
- function deleteInteraction() {
- vm.modifyInteractionState = 'saving';
- return confirmModalService
- .create($filter('t')('interactions.delete_confirmation'))
- .then(function () {
- return interactionsService.deleteInteraction(vm.interaction);
- })
- .then(function () {
- vm.onDelete({
- $event: {
- interaction: vm.interaction,
- },
- });
- })
- .catch(function () {
- vm.modifyInteractionState = 'view';
- });
- }
+function interactionController(interactionsService, confirmModalService, $filter) {
+ const vm = this;
+
+ vm.editInteraction = false;
+ vm.moment = moment;
+
+ vm.$onInit = activate;
+ vm.updateInteraction = updateInteraction;
+ vm.deleteInteraction = deleteInteraction;
+
+ vm.modifyInteractionState = 'view';
+
+ function activate() {
+ vm.interactionType = interactionsService.getInteractionType(vm.interaction.interaction_type_id);
+ }
+
+ function updateInteraction() {
+ vm.modifyInteractionState = 'saving';
+ return interactionsService
+ .updateInteraction(vm.interaction)
+ .then(function () {
+ vm.modifyInteractionState = 'view';
+ })
+ .catch(function () {
+ vm.modifyInteractionState = 'edit';
+ });
+ }
+
+ function deleteInteraction() {
+ vm.modifyInteractionState = 'saving';
+ return confirmModalService
+ .create($filter('t')('interactions.delete_confirmation'))
+ .then(function () {
+ return interactionsService.deleteInteraction(vm.interaction);
+ })
+ .then(function () {
+ vm.onDelete({
+ $event: {
+ interaction: vm.interaction,
+ },
+ });
+ })
+ .catch(function () {
+ vm.modifyInteractionState = 'view';
+ });
+ }
}
diff --git a/app/assets/javascripts/angular/components/interaction/interaction.component.spec.js b/app/assets/javascripts/angular/components/interaction/interaction.component.spec.js
index 029da0619b..12911e8a04 100644
--- a/app/assets/javascripts/angular/components/interaction/interaction.component.spec.js
+++ b/app/assets/javascripts/angular/components/interaction/interaction.component.spec.js
@@ -1,130 +1,109 @@
describe('interaction component', function () {
- var $ctrl, $q, $rootScope, interactionsService, confirmModalService;
-
- beforeEach(inject(function (
- $componentController,
- _$q_,
- _$timeout_,
- _$rootScope_,
- ) {
- interactionsService = jasmine.createSpyObj('interactionsService', [
- 'getInteractionType',
- 'updateInteraction',
- 'deleteInteraction',
- ]);
- confirmModalService = jasmine.createSpyObj('confirmModalService', [
- 'create',
- ]);
-
- $ctrl = $componentController(
- 'interaction',
- {
- interactionsService: interactionsService,
- confirmModalService: confirmModalService,
- },
- {
- interaction: {
- id: '1',
- interaction_type_id: '4',
- },
- onDelete: jasmine.createSpy('onDelete'),
- },
- );
- $q = _$q_;
- $rootScope = _$rootScope_;
- }));
-
- describe('$onInit', function () {
- it('should populate interactionType', function () {
- interactionsService.getInteractionType.and.returnValue('Test type');
-
- $ctrl.$onInit();
-
- expect(interactionsService.getInteractionType).toHaveBeenCalledWith(
- $ctrl.interaction.interaction_type_id,
- );
- expect($ctrl.interactionType).toEqual('Test type');
- });
+ let $ctrl, $q, $rootScope, interactionsService, confirmModalService;
+
+ beforeEach(inject(function ($componentController, _$q_, _$timeout_, _$rootScope_) {
+ interactionsService = jasmine.createSpyObj('interactionsService', [
+ 'getInteractionType',
+ 'updateInteraction',
+ 'deleteInteraction',
+ ]);
+ confirmModalService = jasmine.createSpyObj('confirmModalService', ['create']);
+
+ $ctrl = $componentController(
+ 'interaction',
+ {
+ interactionsService,
+ confirmModalService,
+ },
+ {
+ interaction: {
+ id: '1',
+ interaction_type_id: '4',
+ },
+ onDelete: jasmine.createSpy('onDelete'),
+ },
+ );
+ $q = _$q_;
+ $rootScope = _$rootScope_;
+ }));
+
+ describe('$onInit', function () {
+ it('should populate interactionType', function () {
+ interactionsService.getInteractionType.and.returnValue('Test type');
+
+ $ctrl.$onInit();
+
+ expect(interactionsService.getInteractionType).toHaveBeenCalledWith($ctrl.interaction.interaction_type_id);
+ expect($ctrl.interactionType).toEqual('Test type');
});
+ });
- describe('updateInteraction', function () {
- it('should update the interaction', function () {
- interactionsService.updateInteraction.and.returnValue($q.resolve());
+ describe('updateInteraction', function () {
+ it('should update the interaction', function () {
+ interactionsService.updateInteraction.and.returnValue($q.resolve());
- $ctrl.updateInteraction();
+ $ctrl.updateInteraction();
- expect($ctrl.modifyInteractionState).toEqual('saving');
- expect(interactionsService.updateInteraction).toHaveBeenCalledWith(
- $ctrl.interaction,
- );
+ expect($ctrl.modifyInteractionState).toEqual('saving');
+ expect(interactionsService.updateInteraction).toHaveBeenCalledWith($ctrl.interaction);
- $rootScope.$digest();
+ $rootScope.$digest();
- expect($ctrl.modifyInteractionState).toEqual('view');
- });
- it('should handle an error updating the interaction', function () {
- interactionsService.updateInteraction.and.returnValue($q.reject());
+ expect($ctrl.modifyInteractionState).toEqual('view');
+ });
+ it('should handle an error updating the interaction', function () {
+ interactionsService.updateInteraction.and.returnValue($q.reject());
- $ctrl.updateInteraction();
+ $ctrl.updateInteraction();
- expect($ctrl.modifyInteractionState).toEqual('saving');
- expect(interactionsService.updateInteraction).toHaveBeenCalledWith(
- $ctrl.interaction,
- );
+ expect($ctrl.modifyInteractionState).toEqual('saving');
+ expect(interactionsService.updateInteraction).toHaveBeenCalledWith($ctrl.interaction);
- $rootScope.$digest();
+ $rootScope.$digest();
- expect($ctrl.modifyInteractionState).toEqual('edit');
- });
+ expect($ctrl.modifyInteractionState).toEqual('edit');
});
+ });
- describe('deleteInteraction', function () {
- it('should delete the interaction', function () {
- confirmModalService.create.and.returnValue($q.resolve());
+ describe('deleteInteraction', function () {
+ it('should delete the interaction', function () {
+ confirmModalService.create.and.returnValue($q.resolve());
- $ctrl.deleteInteraction();
+ $ctrl.deleteInteraction();
- expect($ctrl.modifyInteractionState).toEqual('saving');
- expect(confirmModalService.create).toHaveBeenCalledWith(
- jasmine.any(String),
- );
+ expect($ctrl.modifyInteractionState).toEqual('saving');
+ expect(confirmModalService.create).toHaveBeenCalledWith(jasmine.any(String));
- interactionsService.deleteInteraction.and.returnValue($q.resolve());
- $rootScope.$digest();
+ interactionsService.deleteInteraction.and.returnValue($q.resolve());
+ $rootScope.$digest();
- expect(interactionsService.deleteInteraction).toHaveBeenCalledWith(
- $ctrl.interaction,
- );
- $rootScope.$digest();
+ expect(interactionsService.deleteInteraction).toHaveBeenCalledWith($ctrl.interaction);
+ $rootScope.$digest();
- expect($ctrl.onDelete).toHaveBeenCalledWith({
- $event: {
- interaction: $ctrl.interaction,
- },
- });
- });
- it('should handle an error deleting the interaction', function () {
- confirmModalService.create.and.returnValue($q.resolve());
+ expect($ctrl.onDelete).toHaveBeenCalledWith({
+ $event: {
+ interaction: $ctrl.interaction,
+ },
+ });
+ });
+ it('should handle an error deleting the interaction', function () {
+ confirmModalService.create.and.returnValue($q.resolve());
- $ctrl.deleteInteraction();
+ $ctrl.deleteInteraction();
- expect($ctrl.modifyInteractionState).toEqual('saving');
- expect(confirmModalService.create).toHaveBeenCalledWith(
- jasmine.any(String),
- );
+ expect($ctrl.modifyInteractionState).toEqual('saving');
+ expect(confirmModalService.create).toHaveBeenCalledWith(jasmine.any(String));
- interactionsService.deleteInteraction.and.returnValue($q.reject());
+ interactionsService.deleteInteraction.and.returnValue($q.reject());
- $rootScope.$digest();
+ $rootScope.$digest();
- expect(interactionsService.deleteInteraction).toHaveBeenCalledWith(
- $ctrl.interaction,
- );
+ expect(interactionsService.deleteInteraction).toHaveBeenCalledWith($ctrl.interaction);
- $rootScope.$digest();
+ $rootScope.$digest();
- expect($ctrl.onDelete).not.toHaveBeenCalled();
- expect($ctrl.modifyInteractionState).toEqual('view');
- });
+ expect($ctrl.onDelete).not.toHaveBeenCalled();
+ expect($ctrl.modifyInteractionState).toEqual('view');
});
+ });
});
diff --git a/app/assets/javascripts/angular/components/interaction/interaction.html b/app/assets/javascripts/angular/components/interaction/interaction.html
index df4d8e7481..f3e354e075 100644
--- a/app/assets/javascripts/angular/components/interaction/interaction.html
+++ b/app/assets/javascripts/angular/components/interaction/interaction.html
@@ -1,67 +1,34 @@
-
+
-
-
- {{ initiator === $ctrl.personTab.person ?
- ('ministries.people.history.me' | t) : (initiator | personName)
- }},
-
- {{'ministries.people.history.anonymous' | t}}
- >>
- {{$ctrl.moment($ctrl.interaction.timestamp).fromNow()}}
-
-
{{$ctrl.interactionType.title | t}}
-
-
-
-
+
+
+ {{ initiator === $ctrl.personTab.person ? ('ministries.people.history.me' | t) : (initiator | personName)
+ }},
+
+ {{'ministries.people.history.anonymous' | t}}
+ >>
+ {{$ctrl.moment($ctrl.interaction.timestamp).fromNow()}}
+
+
{{$ctrl.interactionType.title | t}}
+
+
+
+
-
-
-
-
-
+
+
+
+
+
diff --git a/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.component.js b/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.component.js
index 9eaed19cb0..4d817e2adb 100644
--- a/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.component.js
+++ b/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.component.js
@@ -2,8 +2,8 @@ import template from './loadingSpinner.html';
import './loadingSpinner.scss';
angular.module('campusContactsApp').component('loadingSpinner', {
- bindings: {
- size: '<',
- },
- template: template,
+ bindings: {
+ size: '<',
+ },
+ template,
});
diff --git a/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.html b/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.html
index ef959bb325..ed311f8be5 100644
--- a/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.html
+++ b/app/assets/javascripts/angular/components/loadingSpinner/loadingSpinner.html
@@ -1,3 +1,3 @@
-
+
diff --git a/app/assets/javascripts/angular/components/massEdit/massEdit.component.js b/app/assets/javascripts/angular/components/massEdit/massEdit.component.js
index c1fb74521e..ec2bd15aea 100644
--- a/app/assets/javascripts/angular/components/massEdit/massEdit.component.js
+++ b/app/assets/javascripts/angular/components/massEdit/massEdit.component.js
@@ -2,89 +2,85 @@ import template from './massEdit.html';
import './massEdit.scss';
angular.module('campusContactsApp').component('massEdit', {
- controller: massEditController,
- bindings: {
- resolve: '<',
- close: '&',
- dismiss: '&',
- },
- template: template,
+ controller: massEditController,
+ bindings: {
+ resolve: '<',
+ close: '&',
+ dismiss: '&',
+ },
+ template,
});
function massEditController(massEditService, personService) {
- var vm = this;
+ const vm = this;
- vm.saving = false;
+ vm.saving = false;
- vm.save = save;
- vm.cancel = cancel;
+ vm.save = save;
+ vm.cancel = cancel;
- vm.changes = {};
+ vm.changes = {};
- vm.selectFields = [
- {
- name: 'followup_status',
- options: personService.getFollowupStatusOptions(),
- },
- { name: 'cru_status', options: personService.getCruStatusOptions() },
- {
- name: 'student_status',
- options: personService.getEnrollmentOptions(),
- },
- { name: 'gender', options: personService.getGenderOptions() },
- ];
+ vm.selectFields = [
+ {
+ name: 'followup_status',
+ options: personService.getFollowupStatusOptions(),
+ },
+ { name: 'cru_status', options: personService.getCruStatusOptions() },
+ {
+ name: 'student_status',
+ options: personService.getEnrollmentOptions(),
+ },
+ { name: 'gender', options: personService.getGenderOptions() },
+ ];
- var unchangedOption = { id: null, i18n: 'mass_edit.unchanged' };
- vm.selectFields.forEach(function (selectField) {
- // Prepend the unchanged option to the list of options
- selectField.options.unshift(unchangedOption);
+ const unchangedOption = { id: null, i18n: 'mass_edit.unchanged' };
+ vm.selectFields.forEach(function (selectField) {
+ // Prepend the unchanged option to the list of options
+ selectField.options.unshift(unchangedOption);
- vm.changes[selectField.name] = null;
- });
+ vm.changes[selectField.name] = null;
+ });
- vm.multiselectFields = massEditService.statDefinitions.map(function (
- statDefinition,
- ) {
- return {
- name: statDefinition.type,
- options: [],
- selection: {},
- ready: false,
- };
- });
- vm.multiselectFields.forEach(function (multiselectField) {
- vm.changes[multiselectField.name + '_added'] = [];
- vm.changes[multiselectField.name + '_removed'] = [];
- });
+ vm.multiselectFields = massEditService.statDefinitions.map(function (statDefinition) {
+ return {
+ name: statDefinition.type,
+ options: [],
+ selection: {},
+ ready: false,
+ };
+ });
+ vm.multiselectFields.forEach(function (multiselectField) {
+ vm.changes[multiselectField.name + '_added'] = [];
+ vm.changes[multiselectField.name + '_removed'] = [];
+ });
- vm.$onInit = activate;
+ vm.$onInit = activate;
- function activate() {
- var selection = vm.resolve.selection;
+ function activate() {
+ const selection = vm.resolve.selection;
- // Load the relationships needed to determine which options are selected and the options themselves
- massEditService.loadOptions(selection).then(function (options) {
- vm.multiselectFields.forEach(function (multiselectField) {
- multiselectField.options = options[multiselectField.name];
- multiselectField.selection = massEditService.selectionFromOptions(
- multiselectField.options,
- );
- multiselectField.ready = true;
- });
- });
- }
+ // Load the relationships needed to determine which options are selected and the options themselves
+ massEditService.loadOptions(selection).then(function (options) {
+ vm.multiselectFields.forEach(function (multiselectField) {
+ multiselectField.options = options[multiselectField.name];
+ multiselectField.selection = massEditService.selectionFromOptions(multiselectField.options);
+ multiselectField.ready = true;
+ });
+ });
+ }
- function save() {
- vm.saving = true;
- massEditService
- .applyChanges(vm.resolve.selection, vm.changes)
- .then(vm.close)
- .catch(function () {
- vm.saving = false;
- });
- }
+ function save() {
+ vm.saving = true;
+ massEditService
+ .applyChanges(vm.resolve.selection, vm.changes)
+ .then(vm.close)
+ .catch(function () {
+ vm.saving = false;
+ });
+ }
- function cancel() {
- vm.dismiss();
- }
+ function cancel() {
+ vm.dismiss();
+ }
}
diff --git a/app/assets/javascripts/angular/components/massEdit/massEdit.html b/app/assets/javascripts/angular/components/massEdit/massEdit.html
index d71b878093..dc1a42df68 100644
--- a/app/assets/javascripts/angular/components/massEdit/massEdit.html
+++ b/app/assets/javascripts/angular/components/massEdit/massEdit.html
@@ -1,57 +1,39 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/app/assets/javascripts/angular/components/massEdit/massEdit.service.js b/app/assets/javascripts/angular/components/massEdit/massEdit.service.js
index b5d44236ac..6b5faff4ab 100644
--- a/app/assets/javascripts/angular/components/massEdit/massEdit.service.js
+++ b/app/assets/javascripts/angular/components/massEdit/massEdit.service.js
@@ -1,503 +1,416 @@
angular.module('campusContactsApp').factory('massEditService', massEditService);
-function massEditService(
- $q,
- httpProxy,
- JsonApiDataStore,
- modelsService,
- personSelectionService,
- _,
-) {
- // Return a boolean indicating whether a particular person is assigned to the team member
- function personHasAssignment(person, orgId, teamMember) {
- return Boolean(
- _.find(person.reverse_contact_assignments, function (assignment) {
- return (
- assignment.organization.id === orgId &&
- assignment.assigned_to.id === teamMember.id
- );
- }),
- );
- }
-
- // Return a boolean indicating whether a particular person has the label
- function personHasLabel(person, orgId, label) {
- return Boolean(
- _.find(person.organizational_labels, function (
- organizationalLabel,
- ) {
- return (
- organizationalLabel.organization_id === orgId &&
- organizationalLabel.label.id === label.id
- );
- }),
- );
- }
-
- // Return a boolean indicating whether a particular person has the group
- function personHasGroup(person, orgId, group) {
- return Boolean(
- _.find(person.group_memberships, ['group.id', group.id]),
- );
- }
-
- // Define the mass editable attributes that are simple values (i.e. a number or a string)
- var simpleChangeManifests = {
- student_status: { type: 'person' },
- gender: { type: 'person' },
- followup_status: { type: 'permission' },
- cru_status: { type: 'permission' },
- archived: { type: null },
- delete: { type: null },
- };
-
- // List of mass editable attributes that are simple values
- var simpleChangeFields = _.keys(simpleChangeManifests);
-
- // List of mass editable attributes that are on the person model
- var personChangeFields = _.keys(
- _.pickBy(simpleChangeManifests, { type: 'person' }),
+function massEditService($q, httpProxy, JsonApiDataStore, modelsService, personSelectionService, _) {
+ // Return a boolean indicating whether a particular person is assigned to the team member
+ function personHasAssignment(person, orgId, teamMember) {
+ return Boolean(
+ _.find(person.reverse_contact_assignments, function (assignment) {
+ return assignment.organization.id === orgId && assignment.assigned_to.id === teamMember.id;
+ }),
);
-
- // List of mass editable attributes that are on the organizational permission model
- var permissionChangeFields = _.keys(
- _.pickBy(simpleChangeManifests, { type: 'permission' }),
+ }
+
+ // Return a boolean indicating whether a particular person has the label
+ function personHasLabel(person, orgId, label) {
+ return Boolean(
+ _.find(person.organizational_labels, function (organizationalLabel) {
+ return organizationalLabel.organization_id === orgId && organizationalLabel.label.id === label.id;
+ }),
);
+ }
+
+ // Return a boolean indicating whether a particular person has the group
+ function personHasGroup(person, orgId, group) {
+ return Boolean(_.find(person.group_memberships, ['group.id', group.id]));
+ }
+
+ // Define the mass editable attributes that are simple values (i.e. a number or a string)
+ const simpleChangeManifests = {
+ student_status: { type: 'person' },
+ gender: { type: 'person' },
+ followup_status: { type: 'permission' },
+ cru_status: { type: 'permission' },
+ archived: { type: null },
+ delete: { type: null },
+ };
+
+ // List of mass editable attributes that are simple values
+ const simpleChangeFields = _.keys(simpleChangeManifests);
+
+ // List of mass editable attributes that are on the person model
+ const personChangeFields = _.keys(_.pickBy(simpleChangeManifests, { type: 'person' }));
+
+ // List of mass editable attributes that are on the organizational permission model
+ const permissionChangeFields = _.keys(_.pickBy(simpleChangeManifests, { type: 'permission' }));
+
+ // Define the mass editable attributes that are complex values (i.e. an { id: Boolean } hash of added/removed
+ // models)
+ // The manifest is in the format expected by applyComplexChanges
+ const complexChangeManifests = {
+ assigned_tos: {
+ personAttribute: 'reverse_contact_assignments',
+ modelType: 'contact_assignment',
+ getModelPredicate: function (assignedToId) {
+ return ['assigned_to.id', assignedToId];
+ },
+ getModelAttributes: function (assignedToId, person, organizationId) {
+ return {
+ assigned_to: JsonApiDataStore.store.find('person', assignedToId),
+ organization: JsonApiDataStore.store.find('organization', organizationId),
+ person_id: person.id,
+ };
+ },
+ },
+ labels: {
+ personAttribute: 'organizational_labels',
+ modelType: 'organizational_label',
+ getModelPredicate: function (labelId) {
+ return ['label.id', labelId];
+ },
+ getModelAttributes: function (labelId, person, organizationId) {
+ return {
+ label: JsonApiDataStore.store.find('label', labelId),
+ organization_id: organizationId,
+ person_id: person.id,
+ };
+ },
+ },
+ groups: {
+ personAttribute: 'group_memberships',
+ modelType: 'group_membership',
+ getModelPredicate: function (groupId) {
+ return ['group.id', groupId];
+ },
+ getModelAttributes: function (groupId, person, organizationId) {
+ // eslint-disable-line no-unused-vars
+ return {
+ group: JsonApiDataStore.store.find('group', groupId),
+ person,
+ role: 'member',
+ };
+ },
+ },
+ };
+
+ // List of mass editable attributes that are complex values
+ const complexChangeFields = _.keys(complexChangeManifests);
+
+ var massEditService = {
+ // Convert a list of added and removed ids into a hash with keys matching the ids and a value of true for
+ // added and false for removed
+ hashFromAddedRemoved: function (addedIds, removedIds) {
+ return _.extend(
+ _.zipObject(addedIds, _.fill(Array(addedIds.length), true)),
+ _.zipObject(removedIds, _.fill(Array(removedIds.length), false)),
+ );
+ },
+
+ // Convert a changes object from the massEdit component into the form expected by the bulk people changes
+ // endpoint
+ prepareChanges: function (changes) {
+ const filteredChanges = {};
+ simpleChangeFields.forEach(function (field) {
+ // A value of null or undefined means no change, so ignore those changes
+ if (!_.isNil(changes[field])) {
+ filteredChanges[field] = changes[field];
+ }
+ });
+ complexChangeFields.forEach(function (field) {
+ const addedIds = changes[field + '_added'] || [];
+ const removedIds = changes[field + '_removed'] || [];
+
+ // Empty added ids and removed ids means no change, so ignore those changes
+ if (addedIds.length + removedIds.length > 0) {
+ // Turn a list of added and removed fields into an object
+ filteredChanges[field] = massEditService.hashFromAddedRemoved(addedIds, removedIds);
+ }
+ });
+ return filteredChanges;
+ },
+
+ // Apply the changes on the server
+ applyChanges: function (selection, changes) {
+ const filteredChanges = massEditService.prepareChanges(changes);
+ massEditService.applyChangesLocally(selection, filteredChanges);
+
+ const normalizedFilters = personSelectionService.convertToFilters(selection);
+
+ const payload = {
+ data: {
+ type: 'bulk_people_change',
+ attributes: _.extend(
+ {
+ organization_id: selection.orgId,
- // Define the mass editable attributes that are complex values (i.e. an { id: Boolean } hash of added/removed
- // models)
- // The manifest is in the format expected by applyComplexChanges
- var complexChangeManifests = {
- assigned_tos: {
- personAttribute: 'reverse_contact_assignments',
- modelType: 'contact_assignment',
- getModelPredicate: function (assignedToId) {
- return ['assigned_to.id', assignedToId];
- },
- getModelAttributes: function (
- assignedToId,
- person,
- organizationId,
- ) {
- return {
- assigned_to: JsonApiDataStore.store.find(
- 'person',
- assignedToId,
- ),
- organization: JsonApiDataStore.store.find(
- 'organization',
- organizationId,
- ),
- person_id: person.id,
- };
- },
- },
- labels: {
- personAttribute: 'organizational_labels',
- modelType: 'organizational_label',
- getModelPredicate: function (labelId) {
- return ['label.id', labelId];
+ // Prefer using person_ids over selection.ids because the former does validation that the
+ // latter does not. If person_ids is truthy, it will override the selection.
+ person_ids: normalizedFilters.ids,
},
- getModelAttributes: function (labelId, person, organizationId) {
- return {
- label: JsonApiDataStore.store.find('label', labelId),
- organization_id: organizationId,
- person_id: person.id,
- };
- },
- },
- groups: {
- personAttribute: 'group_memberships',
- modelType: 'group_membership',
- getModelPredicate: function (groupId) {
- return ['group.id', groupId];
- },
- getModelAttributes: function (groupId, person, organizationId) {
- // eslint-disable-line no-unused-vars
- return {
- group: JsonApiDataStore.store.find('group', groupId),
- person: person,
- role: 'member',
- };
- },
- },
- };
-
- // List of mass editable attributes that are complex values
- var complexChangeFields = _.keys(complexChangeManifests);
-
- var massEditService = {
- // Convert a list of added and removed ids into a hash with keys matching the ids and a value of true for
- // added and false for removed
- hashFromAddedRemoved: function (addedIds, removedIds) {
- return _.extend(
- _.zipObject(addedIds, _.fill(Array(addedIds.length), true)),
- _.zipObject(
- removedIds,
- _.fill(Array(removedIds.length), false),
- ),
- );
+ filteredChanges,
+ ),
},
-
- // Convert a changes object from the massEdit component into the form expected by the bulk people changes
- // endpoint
- prepareChanges: function (changes) {
- var filteredChanges = {};
- simpleChangeFields.forEach(function (field) {
- // A value of null or undefined means no change, so ignore those changes
- if (!_.isNil(changes[field])) {
- filteredChanges[field] = changes[field];
- }
- });
- complexChangeFields.forEach(function (field) {
- var addedIds = changes[field + '_added'] || [];
- var removedIds = changes[field + '_removed'] || [];
-
- // Empty added ids and removed ids means no change, so ignore those changes
- if (addedIds.length + removedIds.length > 0) {
- // Turn a list of added and removed fields into an object
- filteredChanges[
- field
- ] = massEditService.hashFromAddedRemoved(
- addedIds,
- removedIds,
- );
- }
- });
- return filteredChanges;
- },
-
- // Apply the changes on the server
- applyChanges: function (selection, changes) {
- var filteredChanges = massEditService.prepareChanges(changes);
- massEditService.applyChangesLocally(selection, filteredChanges);
-
- var normalizedFilters = personSelectionService.convertToFilters(
- selection,
- );
-
- var payload = {
- data: {
- type: 'bulk_people_change',
- attributes: _.extend(
- {
- organization_id: selection.orgId,
-
- // Prefer using person_ids over selection.ids because the former does validation that the
- // latter does not. If person_ids is truthy, it will override the selection.
- person_ids: normalizedFilters.ids,
- },
- filteredChanges,
- ),
- },
- filters: normalizedFilters,
- };
- return httpProxy
- .post('/bulk_people_changes', payload, {
- errorMessage: 'error.messages.mass_edit.apply_mass_edit',
- })
- .then(httpProxy.extractModels);
- },
-
- // Apply changes that involve modifying a one-to-many relationship (like assignments, group memberships,
- // or label applications) to a person
- /*
- * manifest schema:
- * {
- * // The attribute on the person model with the related models can be found
- * personAttribute,
- *
- * // The type of the related models
- * modelType,
- *
- * // Returns the predicate used by _.find to locate an existing related model
- * getModelPredicate(id),
- *
- * // Returns the attributes to be applied to a new related model
- * getModelAttributes(id, person)
- * }
- */
- applyComplexChanges: function (
- changes,
- person,
- organizationId,
- manifest,
- ) {
- _.forEach(changes, function (change, id) {
- var existingModel = _.find(
- person[manifest.personAttribute],
- manifest.getModelPredicate(id),
- );
-
- if (change === true && !existingModel) {
- // Add a model
- var addedAssignment = new JsonApiDataStore.Model(
- manifest.modelType,
- );
- _.extend(
- addedAssignment,
- manifest.getModelAttributes(id, person, organizationId),
- );
- person[manifest.personAttribute].push(addedAssignment);
- } else if (change === false && existingModel) {
- // Remove a model
- JsonApiDataStore.store.destroy(existingModel);
- _.pull(person[manifest.personAttribute], existingModel);
- }
- });
- },
-
- // Apply the changes locally
- applyChangesLocally: function (selection, changes) {
- var orgId = selection.orgId;
-
- selection.selectedPeople.forEach(function (personId) {
- var person = JsonApiDataStore.store.find('person', personId);
- var orgPermission = _.find(person.organizational_permissions, {
- organization_id: orgId,
- });
-
- // Changes to person fields can be simply copied to the person model
- _.extend(person, _.pick(changes, personChangeFields));
-
- // Changes to permission fields can be simply copied to the organizational permission model
- if (orgPermission) {
- _.extend(
- orgPermission,
- _.pick(changes, permissionChangeFields),
- );
- }
-
- _.each(complexChangeManifests, function (
- changeManifest,
- changeField,
- ) {
- massEditService.applyComplexChanges(
- changes[changeField],
- person,
- orgId,
- changeManifest,
- );
- });
- });
- },
-
- statDefinitions: [
+ filters: normalizedFilters,
+ };
+ return httpProxy
+ .post('/bulk_people_changes', payload, {
+ errorMessage: 'error.messages.mass_edit.apply_mass_edit',
+ })
+ .then(httpProxy.extractModels);
+ },
+
+ // Apply changes that involve modifying a one-to-many relationship (like assignments, group memberships,
+ // or label applications) to a person
+ /*
+ * manifest schema:
+ * {
+ * // The attribute on the person model with the related models can be found
+ * personAttribute,
+ *
+ * // The type of the related models
+ * modelType,
+ *
+ * // Returns the predicate used by _.find to locate an existing related model
+ * getModelPredicate(id),
+ *
+ * // Returns the attributes to be applied to a new related model
+ * getModelAttributes(id, person)
+ * }
+ */
+ applyComplexChanges: function (changes, person, organizationId, manifest) {
+ _.forEach(changes, function (change, id) {
+ const existingModel = _.find(person[manifest.personAttribute], manifest.getModelPredicate(id));
+
+ if (change === true && !existingModel) {
+ // Add a model
+ const addedAssignment = new JsonApiDataStore.Model(manifest.modelType);
+ _.extend(addedAssignment, manifest.getModelAttributes(id, person, organizationId));
+ person[manifest.personAttribute].push(addedAssignment);
+ } else if (change === false && existingModel) {
+ // Remove a model
+ JsonApiDataStore.store.destroy(existingModel);
+ _.pull(person[manifest.personAttribute], existingModel);
+ }
+ });
+ },
+
+ // Apply the changes locally
+ applyChangesLocally: function (selection, changes) {
+ const orgId = selection.orgId;
+
+ selection.selectedPeople.forEach(function (personId) {
+ const person = JsonApiDataStore.store.find('person', personId);
+ const orgPermission = _.find(person.organizational_permissions, {
+ organization_id: orgId,
+ });
+
+ // Changes to person fields can be simply copied to the person model
+ _.extend(person, _.pick(changes, personChangeFields));
+
+ // Changes to permission fields can be simply copied to the organizational permission model
+ if (orgPermission) {
+ _.extend(orgPermission, _.pick(changes, permissionChangeFields));
+ }
+
+ _.each(complexChangeManifests, function (changeManifest, changeField) {
+ massEditService.applyComplexChanges(changes[changeField], person, orgId, changeManifest);
+ });
+ });
+ },
+
+ statDefinitions: [
+ {
+ type: 'assigned_tos',
+ idField: 'person_id',
+ personHasModel: personHasAssignment,
+ },
+ {
+ type: 'labels',
+ idField: 'label_id',
+ personHasModel: personHasLabel,
+ },
+ {
+ type: 'groups',
+ idField: 'group_id',
+ personHasModel: personHasGroup,
+ },
+ ],
+
+ // Return a boolean indicating whether a person has any unloaded relationships
+ personHasUnloadedRelationships: function (person, relationshipNames) {
+ // Consider the person to be missing if any of its relationships are missing
+ return relationshipNames.some(function (relationshipName) {
+ return massEditService.relationshipHasUnloadedModels(person[relationshipName]);
+ });
+ },
+
+ // Return a boolean indicating whether a relationship has any unloaded models
+ relationshipHasUnloadedModels: function (relationship) {
+ return relationship.some(_.negate(httpProxy.isLoaded));
+ },
+
+ // Load the necessary relationships for a selection
+ loadPeopleRelationships: function (selection) {
+ // Optimize by only loading relationships when optionStateFromModel will actually use those
+ // relationships when determining option selection states
+ if (personSelectionService.containsUnincludedPeople(selection)) {
+ return $q.resolve();
+ }
+
+ // The "reverse_contact_assignments" relationship is needed also, but it is already being loaded by
+ // organizationOverviewPeopleService.loadMoreOrgPeople
+ const relationships = ['organizational_labels', 'group_memberships'];
+
+ // Only load people for whom at least one of their relationships contains a placeholder model
+ const peopleMissingRelationships = selection.selectedPeople.filter(function (personId) {
+ const person = JsonApiDataStore.store.find('person', personId);
+ return massEditService.personHasUnloadedRelationships(person, relationships);
+ });
+
+ // Only make a network request if there are people that need their relationships loaded
+ return peopleMissingRelationships === 0
+ ? $q.resolve()
+ : httpProxy.get(
+ modelsService.getModelMetadata('person').url.all,
{
- type: 'assigned_tos',
- idField: 'person_id',
- personHasModel: personHasAssignment,
+ include: relationships.join(','),
+ 'filters[organization_ids]': selection.orgId,
+ 'filters[ids]': peopleMissingRelationships.join(','),
},
{
- type: 'labels',
- idField: 'label_id',
- personHasModel: personHasLabel,
+ errorMessage: 'error.messages.mass_edit.load_relationships',
},
- {
- type: 'groups',
- idField: 'group_id',
- personHasModel: personHasGroup,
- },
- ],
-
- // Return a boolean indicating whether a person has any unloaded relationships
- personHasUnloadedRelationships: function (person, relationshipNames) {
- // Consider the person to be missing if any of its relationships are missing
- return relationshipNames.some(function (relationshipName) {
- return massEditService.relationshipHasUnloadedModels(
- person[relationshipName],
- );
- });
- },
-
- // Return a boolean indicating whether a relationship has any unloaded models
- relationshipHasUnloadedModels: function (relationship) {
- return relationship.some(_.negate(httpProxy.isLoaded));
- },
-
- // Load the necessary relationships for a selection
- loadPeopleRelationships: function (selection) {
- // Optimize by only loading relationships when optionStateFromModel will actually use those
- // relationships when determining option selection states
- if (personSelectionService.containsUnincludedPeople(selection)) {
- return $q.resolve();
- }
-
- // The "reverse_contact_assignments" relationship is needed also, but it is already being loaded by
- // organizationOverviewPeopleService.loadMoreOrgPeople
- var relationships = ['organizational_labels', 'group_memberships'];
-
- // Only load people for whom at least one of their relationships contains a placeholder model
- var peopleMissingRelationships = selection.selectedPeople.filter(
- function (personId) {
- var person = JsonApiDataStore.store.find(
- 'person',
- personId,
- );
- return massEditService.personHasUnloadedRelationships(
- person,
- relationships,
- );
- },
- );
-
- // Only make a network request if there are people that need their relationships loaded
- return peopleMissingRelationships === 0
- ? $q.resolve()
- : httpProxy.get(
- modelsService.getModelMetadata('person').url.all,
- {
- include: relationships.join(','),
- 'filters[organization_ids]': selection.orgId,
- 'filters[ids]': peopleMissingRelationships.join(','),
- },
- {
- errorMessage:
- 'error.messages.mass_edit.load_relationships',
- },
- );
- },
-
- // Load the filter stats for an organization
- loadFilterStats: function (orgId) {
- return httpProxy
- .get(
- modelsService
- .getModelMetadata('filter_stats')
- .url.single('people'),
- {
- organization_id: orgId,
- include_zeros: true,
- },
- {
- errorMessage: 'error.messages.mass_edit.load_options',
- },
- )
- .then(httpProxy.extractModel);
- },
-
- // Return an array of options generated from a particular type of stats
- optionsForStatsType: function (stats, statType, selection) {
- var statDefinition = _.find(massEditService.statDefinitions, {
- type: statType,
- });
- return stats[statDefinition.type].map(function (stat) {
- var id = stat[statDefinition.idField];
- var state;
- if (stat.count === 0) {
- // This stat is empty, so automatically treat this option as unselected
- state = false;
- } else {
- state = massEditService.optionStateFromModel(
- { id: id, count: stat.count },
- selection,
- statDefinition.personHasModel,
- );
- }
- return {
- id: id,
- name: stat.name,
- state: state,
- };
- });
- },
-
- // Return the options generated from a stats model
- optionsFromStats: function (stats, selection) {
- var statTypes = _.map(massEditService.statDefinitions, 'type');
- return _.fromPairs(
- statTypes.map(function (statType) {
- // Key is the type, value is the options
- return [
- statType,
- massEditService.optionsForStatsType(
- stats,
- statType,
- selection,
- ),
- ];
- }),
- );
- },
-
- // Return a promise that resolves to the available multiselect options
- loadOptions: function (selection) {
- return $q
- .all([
- // Load the filter stats because they include the id and name of all possible assignments, labels,
- // and groups and can therefore be used as the basis for calculating the options
- massEditService.loadFilterStats(selection.orgId),
-
- // In parallel, load the relationships needed to determine which options are selected
- massEditService.loadPeopleRelationships(selection),
- ])
- .then(function (results) {
- var stats = results[0];
- return massEditService.optionsFromStats(stats, selection);
- });
- },
-
- // Determine the right state for a generic model option
- // If all people "have" the model, return true, if no people "have" the model, return false, and if some
- // people "have" the model, return indeterminate.
- // "model" is the model option in question (such as a label, group, or contact assignment, for example).
- // "personHasModel" is expected to implement the algorithm for determining whether a person "has" a model.
- // It is called with the model and the person in question.
- optionStateFromModel: function (model, selection, personHasModel) {
- if (personSelectionService.containsUnincludedPeople(selection)) {
- // When unincluded contacts are selected, all options are automatically treated as indeterminate
- return null;
- }
- if (selection.selectedPeople.length === 0) {
- // When no contacts are selected, all options are automatically treated as unselected
- return false;
- }
-
- // Convert the array of person ids into an array of people
- var selectedPeople = selection.selectedPeople.map(function (
- personId,
- ) {
- return JsonApiDataStore.store.find('person', personId);
- });
-
- // Gather a list of the people with the model
- var matchingPeople = selectedPeople.filter(function (person) {
- return personHasModel(person, selection.orgId, model);
- });
-
- // Determine whether all of the selected people have the model
- var allSelected = matchingPeople.length === selectedPeople.length;
-
- // Determine whether none of the selected people have the models
- var noneSelected = matchingPeople.length === 0;
-
- if (noneSelected) {
- // Unselected
- return false;
- }
- if (allSelected) {
- // Selected
- return true;
- }
-
- // Indeterminate
- return null;
- },
-
- personHasAssignment: personHasAssignment,
- personHasLabel: personHasLabel,
- personHasGroup: personHasGroup,
-
- // Return an option selection object from an array of options
- selectionFromOptions: function (options) {
- return _.chain(options)
- .map(function (option) {
- return [option.id, option.state];
- })
- .fromPairs()
- .value();
- },
- };
-
- return massEditService;
+ );
+ },
+
+ // Load the filter stats for an organization
+ loadFilterStats: function (orgId) {
+ return httpProxy
+ .get(
+ modelsService.getModelMetadata('filter_stats').url.single('people'),
+ {
+ organization_id: orgId,
+ include_zeros: true,
+ },
+ {
+ errorMessage: 'error.messages.mass_edit.load_options',
+ },
+ )
+ .then(httpProxy.extractModel);
+ },
+
+ // Return an array of options generated from a particular type of stats
+ optionsForStatsType: function (stats, statType, selection) {
+ const statDefinition = _.find(massEditService.statDefinitions, {
+ type: statType,
+ });
+ return stats[statDefinition.type].map(function (stat) {
+ const id = stat[statDefinition.idField];
+ let state;
+ if (stat.count === 0) {
+ // This stat is empty, so automatically treat this option as unselected
+ state = false;
+ } else {
+ state = massEditService.optionStateFromModel(
+ { id, count: stat.count },
+ selection,
+ statDefinition.personHasModel,
+ );
+ }
+ return {
+ id,
+ name: stat.name,
+ state,
+ };
+ });
+ },
+
+ // Return the options generated from a stats model
+ optionsFromStats: function (stats, selection) {
+ const statTypes = _.map(massEditService.statDefinitions, 'type');
+ return _.fromPairs(
+ statTypes.map(function (statType) {
+ // Key is the type, value is the options
+ return [statType, massEditService.optionsForStatsType(stats, statType, selection)];
+ }),
+ );
+ },
+
+ // Return a promise that resolves to the available multiselect options
+ loadOptions: function (selection) {
+ return $q
+ .all([
+ // Load the filter stats because they include the id and name of all possible assignments, labels,
+ // and groups and can therefore be used as the basis for calculating the options
+ massEditService.loadFilterStats(selection.orgId),
+
+ // In parallel, load the relationships needed to determine which options are selected
+ massEditService.loadPeopleRelationships(selection),
+ ])
+ .then(function (results) {
+ const stats = results[0];
+ return massEditService.optionsFromStats(stats, selection);
+ });
+ },
+
+ // Determine the right state for a generic model option
+ // If all people "have" the model, return true, if no people "have" the model, return false, and if some
+ // people "have" the model, return indeterminate.
+ // "model" is the model option in question (such as a label, group, or contact assignment, for example).
+ // "personHasModel" is expected to implement the algorithm for determining whether a person "has" a model.
+ // It is called with the model and the person in question.
+ optionStateFromModel: function (model, selection, personHasModel) {
+ if (personSelectionService.containsUnincludedPeople(selection)) {
+ // When unincluded contacts are selected, all options are automatically treated as indeterminate
+ return null;
+ }
+ if (selection.selectedPeople.length === 0) {
+ // When no contacts are selected, all options are automatically treated as unselected
+ return false;
+ }
+
+ // Convert the array of person ids into an array of people
+ const selectedPeople = selection.selectedPeople.map(function (personId) {
+ return JsonApiDataStore.store.find('person', personId);
+ });
+
+ // Gather a list of the people with the model
+ const matchingPeople = selectedPeople.filter(function (person) {
+ return personHasModel(person, selection.orgId, model);
+ });
+
+ // Determine whether all of the selected people have the model
+ const allSelected = matchingPeople.length === selectedPeople.length;
+
+ // Determine whether none of the selected people have the models
+ const noneSelected = matchingPeople.length === 0;
+
+ if (noneSelected) {
+ // Unselected
+ return false;
+ }
+ if (allSelected) {
+ // Selected
+ return true;
+ }
+
+ // Indeterminate
+ return null;
+ },
+
+ personHasAssignment,
+ personHasLabel,
+ personHasGroup,
+
+ // Return an option selection object from an array of options
+ selectionFromOptions: function (options) {
+ return _.chain(options)
+ .map(function (option) {
+ return [option.id, option.state];
+ })
+ .fromPairs()
+ .value();
+ },
+ };
+
+ return massEditService;
}
diff --git a/app/assets/javascripts/angular/components/massEdit/massEdit.service.spec.js b/app/assets/javascripts/angular/components/massEdit/massEdit.service.spec.js
index 6dc7b33da9..fe0ff5369d 100644
--- a/app/assets/javascripts/angular/components/massEdit/massEdit.service.spec.js
+++ b/app/assets/javascripts/angular/components/massEdit/massEdit.service.spec.js
@@ -1,646 +1,519 @@
-var massEditService, _;
+let massEditService, _;
describe('massEditService', function () {
- beforeEach(inject(function (_massEditService_, ___) {
- massEditService = _massEditService_;
- _ = ___;
-
- this.loadedModel = {};
- this.unloadedModel = { _placeHolder: true };
- this.loadedRelationship = [this.loadedModel];
- this.unloadedRelationship = [this.loadedModel, this.unloadedModel];
+ beforeEach(inject(function (_massEditService_, ___) {
+ massEditService = _massEditService_;
+ _ = ___;
+
+ this.loadedModel = {};
+ this.unloadedModel = { _placeHolder: true };
+ this.loadedRelationship = [this.loadedModel];
+ this.unloadedRelationship = [this.loadedModel, this.unloadedModel];
+ }));
+
+ describe('hashFromAddedRemoved', function () {
+ it('should generate a hash of added and removed fields', function () {
+ expect(massEditService.hashFromAddedRemoved([1, 4, 5], [2, 3])).toEqual({
+ 1: true,
+ 2: false,
+ 3: false,
+ 4: true,
+ 5: true,
+ });
+ });
+ });
+
+ describe('prepareChanges', function () {
+ it('should filter out non-whitelisted fields', function () {
+ expect(
+ massEditService.prepareChanges({
+ foo: true,
+ bar: false,
+ }),
+ ).toEqual({});
+ });
+
+ it('should filter out unchanged simple fields', function () {
+ expect(
+ massEditService.prepareChanges({
+ followup_status: 1,
+ cru_status: null,
+ student_status: 1,
+ gender: null,
+ }),
+ ).toEqual({ followup_status: 1, student_status: 1 });
+ });
+
+ it('should filter out unchanged complex fields', function () {
+ expect(
+ massEditService.prepareChanges({
+ assigned_tos_added: [],
+ assigned_tos_removed: [],
+ }),
+ ).toEqual({});
+ });
+
+ it('should prepare complex fields', function () {
+ expect(
+ massEditService.prepareChanges({
+ assigned_tos_added: [1],
+ assigned_tos_removed: [2, 3],
+ }),
+ ).toEqual({
+ assigned_tos: { 1: true, 2: false, 3: false },
+ });
+ });
+ });
+
+ describe('applyComplexChanges', function () {
+ beforeEach(function () {
+ this.manifest = {
+ personAttribute: 'reverse_contact_assignments',
+ modelType: 'contact_assignment',
+ getModelPredicate: function (id) {
+ return ['assigned_to.id', id];
+ },
+ getModelAttributes: function (assignedToId, person, orgId) {
+ return {
+ assigned_to: { id: assignedToId },
+ organization_id: orgId,
+ person_id: person.id,
+ };
+ },
+ };
+
+ this.person = {
+ id: 1,
+ reverse_contact_assignments: [{ assigned_to: { id: '123' } }],
+ };
+
+ this.orgId = 2;
+ });
+
+ it('should add new related models', function () {
+ massEditService.applyComplexChanges(
+ {
+ 456: true,
+ },
+ this.person,
+ this.orgId,
+ this.manifest,
+ );
+ expect(_.map(this.person.reverse_contact_assignments, 'assigned_to.id')).toEqual(['123', '456']);
+ });
+
+ it('should not add existing related models', function () {
+ massEditService.applyComplexChanges(
+ {
+ 123: true,
+ },
+ this.person,
+ this.orgId,
+ this.manifest,
+ );
+ expect(_.map(this.person.reverse_contact_assignments, 'assigned_to.id')).toEqual(['123']);
+ });
+
+ it('should remove existing related models', inject(function (JsonApiDataStore) {
+ spyOn(JsonApiDataStore.store, 'destroy');
+ massEditService.applyComplexChanges(
+ {
+ 123: false,
+ },
+ this.person,
+ this.orgId,
+ this.manifest,
+ );
+ expect(_.map(this.person.reverse_contact_assignments, 'assigned_to.id')).toEqual([]);
+ expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
+ }));
+
+ it('should not remove non-existing related models', inject(function (JsonApiDataStore) {
+ spyOn(JsonApiDataStore.store, 'destroy');
+ massEditService.applyComplexChanges(
+ {
+ 456: false,
+ },
+ this.person,
+ this.orgId,
+ this.manifest,
+ );
+ expect(_.map(this.person.reverse_contact_assignments, 'assigned_to.id')).toEqual(['123']);
+ expect(JsonApiDataStore.store.destroy).not.toHaveBeenCalled();
+ }));
+ });
+
+ describe('applyChangesLocally', function () {
+ beforeEach(inject(function (JsonApiDataStore) {
+ this.people = _.range(3).map(function (personId) {
+ return { id: personId };
+ });
+
+ const _this = this;
+ spyOn(JsonApiDataStore.store, 'find').and.callFake(function (modelType, modelId) {
+ return (
+ {
+ person: _this.people[modelId],
+ organization: { id: modelId },
+ label: { id: modelId },
+ group: { id: modelId },
+ }[modelType] || null
+ );
+ });
}));
- describe('hashFromAddedRemoved', function () {
- it('should generate a hash of added and removed fields', function () {
- expect(
- massEditService.hashFromAddedRemoved([1, 4, 5], [2, 3]),
- ).toEqual({
- 1: true,
- 2: false,
- 3: false,
- 4: true,
- 5: true,
- });
- });
- });
-
- describe('prepareChanges', function () {
- it('should filter out non-whitelisted fields', function () {
- expect(
- massEditService.prepareChanges({
- foo: true,
- bar: false,
- }),
- ).toEqual({});
- });
-
- it('should filter out unchanged simple fields', function () {
- expect(
- massEditService.prepareChanges({
- followup_status: 1,
- cru_status: null,
- student_status: 1,
- gender: null,
- }),
- ).toEqual({ followup_status: 1, student_status: 1 });
- });
-
- it('should filter out unchanged complex fields', function () {
- expect(
- massEditService.prepareChanges({
- assigned_tos_added: [],
- assigned_tos_removed: [],
- }),
- ).toEqual({});
- });
-
- it('should prepare complex fields', function () {
- expect(
- massEditService.prepareChanges({
- assigned_tos_added: [1],
- assigned_tos_removed: [2, 3],
- }),
- ).toEqual({
- assigned_tos: { 1: true, 2: false, 3: false },
- });
- });
- });
-
- describe('applyComplexChanges', function () {
- beforeEach(function () {
- this.manifest = {
- personAttribute: 'reverse_contact_assignments',
- modelType: 'contact_assignment',
- getModelPredicate: function (id) {
- return ['assigned_to.id', id];
- },
- getModelAttributes: function (assignedToId, person, orgId) {
- return {
- assigned_to: { id: assignedToId },
- organization_id: orgId,
- person_id: person.id,
- };
- },
- };
-
- this.person = {
- id: 1,
- reverse_contact_assignments: [{ assigned_to: { id: '123' } }],
- };
-
- this.orgId = 2;
- });
-
- it('should add new related models', function () {
- massEditService.applyComplexChanges(
- {
- 456: true,
- },
- this.person,
- this.orgId,
- this.manifest,
- );
- expect(
- _.map(
- this.person.reverse_contact_assignments,
- 'assigned_to.id',
- ),
- ).toEqual(['123', '456']);
- });
-
- it('should not add existing related models', function () {
- massEditService.applyComplexChanges(
- {
- 123: true,
- },
- this.person,
- this.orgId,
- this.manifest,
- );
- expect(
- _.map(
- this.person.reverse_contact_assignments,
- 'assigned_to.id',
- ),
- ).toEqual(['123']);
- });
-
- it('should remove existing related models', inject(function (
- JsonApiDataStore,
- ) {
- spyOn(JsonApiDataStore.store, 'destroy');
- massEditService.applyComplexChanges(
- {
- 123: false,
- },
- this.person,
- this.orgId,
- this.manifest,
- );
- expect(
- _.map(
- this.person.reverse_contact_assignments,
- 'assigned_to.id',
- ),
- ).toEqual([]);
- expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
- }));
-
- it('should not remove non-existing related models', inject(function (
- JsonApiDataStore,
- ) {
- spyOn(JsonApiDataStore.store, 'destroy');
- massEditService.applyComplexChanges(
- {
- 456: false,
- },
- this.person,
- this.orgId,
- this.manifest,
- );
- expect(
- _.map(
- this.person.reverse_contact_assignments,
- 'assigned_to.id',
- ),
- ).toEqual(['123']);
- expect(JsonApiDataStore.store.destroy).not.toHaveBeenCalled();
- }));
- });
-
- describe('applyChangesLocally', function () {
- beforeEach(inject(function (JsonApiDataStore) {
- this.people = _.range(3).map(function (personId) {
- return { id: personId };
- });
-
- var _this = this;
- spyOn(JsonApiDataStore.store, 'find').and.callFake(function (
- modelType,
- modelId,
- ) {
- return (
- {
- person: _this.people[modelId],
- organization: { id: modelId },
- label: { id: modelId },
- group: { id: modelId },
- }[modelType] || null
- );
- });
- }));
-
- it('should copy person changes to changed people', function () {
- massEditService.applyChangesLocally(
- {
- selectedPeople: [0, 2],
- },
- { student_status: 1 },
- );
- expect(this.people[0].student_status).toBe(1);
- expect(this.people[1].student_status).toBeUndefined();
- expect(this.people[2].student_status).toBe(1);
- });
-
- it('should copy permission changes to changed people organizational permissions', function () {
- var orgId = 123;
- this.people[0].organizational_permissions = [
- { id: 100, organization_id: orgId },
- ];
- this.people[1].organizational_permissions = [];
- this.people[2].organizational_permissions = [
- { id: 200, organization_id: orgId + 1 },
- { id: 201, organization_id: orgId },
- ];
- massEditService.applyChangesLocally(
- {
- orgId: orgId,
- selectedPeople: [0, 2],
- },
- { followup_status: 1 },
- );
- expect(
- this.people[0].organizational_permissions[0].followup_status,
- ).toBe(1);
- expect(
- this.people[1].organizational_permissions[0],
- ).toBeUndefined();
- expect(
- this.people[2].organizational_permissions[1].followup_status,
- ).toBe(1);
- });
-
- it('should add and remove assignments', inject(function (
- JsonApiDataStore,
- ) {
- this.people[0].reverse_contact_assignments = [
- { assigned_to: { id: '1' } },
- ];
- spyOn(JsonApiDataStore.store, 'destroy');
- massEditService.applyChangesLocally(
- {
- selectedPeople: [0],
- },
- { assigned_tos: { 1: false, 2: true } },
- );
- expect(
- _.map(
- this.people[0].reverse_contact_assignments,
- 'assigned_to.id',
- ),
- ).toEqual([2]);
- expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
- }));
-
- it('should add and remove labels', inject(function (JsonApiDataStore) {
- this.people[0].organizational_labels = [{ label: { id: '1' } }];
- spyOn(JsonApiDataStore.store, 'destroy');
- massEditService.applyChangesLocally(
- {
- selectedPeople: [0],
- },
- { labels: { 1: false, 2: true } },
- );
- expect(
- _.map(this.people[0].organizational_labels, 'label.id'),
- ).toEqual(['2']);
- expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
- }));
-
- it('should add and remove groups', inject(function (JsonApiDataStore) {
- this.people[0].group_memberships = [{ group: { id: '1' } }];
- spyOn(JsonApiDataStore.store, 'destroy');
- massEditService.applyChangesLocally(
- {
- selectedPeople: [0],
- },
- { groups: { 1: false, 2: true } },
- );
- expect(
- _.map(this.people[0].group_memberships, 'group.id'),
- ).toEqual(['2']);
- expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
- }));
- });
-
- describe('personHasUnloadedRelationships', function () {
- it('should should return true for unloaded relationships', function () {
- expect(
- massEditService.personHasUnloadedRelationships(
- {
- relationship1: this.loadedRelationship,
- relationship2: this.unloadedRelationship,
- },
- ['relationship1', 'relationship2'],
- ),
- ).toBe(true);
- });
-
- it('should should return false for loaded relationships', function () {
- expect(
- massEditService.personHasUnloadedRelationships(
- {
- relationship1: this.loadedRelationship,
- relationship2: this.loadedRelationship,
- },
- ['relationship1', 'relationship2'],
- ),
- ).toBe(false);
- });
- });
-
- describe('relationshipHasUnloadedModels', function () {
- it('should return true for unloaded models', function () {
- expect(
- massEditService.relationshipHasUnloadedModels(
- this.unloadedRelationship,
- ),
- ).toBe(true);
- });
-
- it('should return false for loaded models', function () {
- expect(
- massEditService.relationshipHasUnloadedModels(
- this.loadedRelationship,
- ),
- ).toBe(false);
- });
- });
-
- describe('optionsForStatsType', function () {
- beforeEach(function () {
- this.stats = {
- assigned_tos: [
- { person_id: 1, name: 'Adam', count: 1 },
- { person_id: 2, name: 'Bill', count: 1 },
- ],
- };
- this.selection = {};
- spyOn(massEditService, 'optionStateFromModel').and.returnValue(
- true,
- );
- });
-
- it('should generate options from the stats', function () {
- expect(
- massEditService.optionsForStatsType(
- this.stats,
- 'assigned_tos',
- this.select,
- ),
- ).toEqual([
- { id: 1, name: 'Adam', state: true },
- { id: 2, name: 'Bill', state: true },
- ]);
- });
-
- it('should set the state to unselected for empty stats', function () {
- this.stats.assigned_tos[0].count = 0;
- expect(
- massEditService.optionsForStatsType(
- this.stats,
- 'assigned_tos',
- this.select,
- ),
- ).toEqual([
- { id: 1, name: 'Adam', state: false },
- { id: 2, name: 'Bill', state: true },
- ]);
- });
- });
-
- describe('optionsFromStats', function () {
- it('should generate options for all types from the stats', function () {
- this.stats = {};
- this.selection = {};
- this.assignedTos = [];
- this.labels = [];
- this.groups = [];
- spyOn(massEditService, 'optionsForStatsType').and.returnValues(
- this.assignedTos,
- this.labels,
- this.groups,
- );
- expect(
- massEditService.optionsFromStats(this.stats, this.selection),
- ).toEqual({
- assigned_tos: this.assignedTos,
- labels: this.labels,
- groups: this.groups,
- });
- });
- });
-
- describe('optionStateFromModel', function () {
- beforeEach(inject(function (JsonApiDataStore) {
- this.people = _.range(3).map(function (personId) {
- return { id: personId };
- });
-
- var people = this.people;
- spyOn(JsonApiDataStore.store, 'find').and.callFake(function (
- modelType,
- modelId,
- ) {
- return people[modelId];
- });
-
- this.model = {};
-
- this.selection = {
- allSelected: false,
- allIncluded: true,
- orgId: 1,
- selectedPeople: [0, 1],
- };
- }));
-
- it('should return indeterminate when some unincluded people are selected', function () {
- this.selection.allSelected = true;
- this.selection.allIncluded = false;
- expect(
- massEditService.optionStateFromModel(
- this.model,
- this.selection,
- _.noop,
- ),
- ).toBe(null);
- });
-
- it('should return unselected when no people are selected', function () {
- this.selection.selectedPeople = [];
- expect(
- massEditService.optionStateFromModel(
- this.model,
- this.selection,
- _.noop,
- ),
- ).toBe(false);
- });
-
- it('should call personHasModel once per selected person', function () {
- var personHasModel = jasmine.createSpy();
- massEditService.optionStateFromModel(
- this.model,
- this.selection,
- personHasModel,
- );
- expect(personHasModel.calls.allArgs()).toEqual([
- [this.people[0], this.selection.orgId, this.model],
- [this.people[1], this.selection.orgId, this.model],
- ]);
- });
-
- it('should return selected when all selected people have the model', function () {
- var personHasModel = jasmine
- .createSpy()
- .and.returnValues(true, true);
- expect(
- massEditService.optionStateFromModel(
- this.model,
- this.selection,
- personHasModel,
- ),
- ).toBe(true);
- });
-
- it('should return unselected when no selected people have the model', function () {
- var personHasModel = jasmine
- .createSpy()
- .and.returnValues(false, false);
- expect(
- massEditService.optionStateFromModel(
- this.model,
- this.selection,
- personHasModel,
- ),
- ).toBe(false);
- });
-
- it('should return indeterminate when some selected people have the model', function () {
- var personHasModel = jasmine
- .createSpy()
- .and.returnValues(true, false);
- expect(
- massEditService.optionStateFromModel(
- this.model,
- this.selection,
- personHasModel,
- ),
- ).toBe(null);
- });
- });
-
- describe('personHasAssignment', function () {
- beforeEach(function () {
- this.teamMember = { id: 1 };
- this.wrongTeamMember = { id: 2 };
- this.orgId = 123;
- this.org = { id: this.orgId };
- this.wrongOrg = { id: 234 };
- this.matchingAssignment = {
- assigned_to: this.teamMember,
- organization: this.org,
- };
- this.wrongPersonAssignment = {
- assigned_to: this.wrongTeamMember,
- organization: this.org,
- };
- this.wrongOrgAssignment = {
- assigned_to: this.teamMember,
- organization: this.wrongOrg,
- };
- });
-
- it('should return true if the person is assigned to the team member', function () {
- expect(
- massEditService.personHasAssignment(
- {
- reverse_contact_assignments: [this.matchingAssignment],
- },
- this.orgId,
- this.teamMember,
- ),
- ).toBe(true);
- });
-
- it('should return false if the person is not assigned to the team member', function () {
- expect(
- massEditService.personHasAssignment(
- {
- reverse_contact_assignments: [
- this.wrongPersonAssignment,
- this.wrongOrgAssignment,
- ],
- },
- this.orgId,
- this.teamMember,
- ),
- ).toBe(false);
- });
- });
-
- describe('personHasLabel', function () {
- beforeEach(function () {
- this.personId = 1;
- this.label = { id: 123 };
- this.wrongLabel = { id: 234 };
- this.orgId = 456;
- this.wrongOrgId = 567;
- this.matchingLabel = {
- organization_id: this.orgId,
- label: this.label,
- };
- this.wrongIdLabel = {
- organization_id: this.orgId,
- label: this.wrongLabel,
- };
- this.wrongOrgLabel = {
- organization_id: this.wrongOrgId,
- label: this.label,
- };
- });
-
- it('should return true if the person has the label', function () {
- expect(
- massEditService.personHasLabel(
- {
- id: this.personId,
- organizational_labels: [this.matchingLabel],
- },
- this.orgId,
- this.label,
- ),
- ).toBe(true);
- });
-
- it('should return false if the person does not have the label', function () {
- expect(
- massEditService.personHasLabel(
- {
- id: this.personId,
- organizational_labels: [
- this.wrongIdLabel,
- this.wrongOrgLabel,
- ],
- },
- this.orgId,
- this.label,
- ),
- ).toBe(false);
- });
- });
-
- describe('personHasGroup', function () {
- beforeEach(function () {
- this.personId = 1;
- this.group = { id: 123 };
- this.wrongGroup = { id: 234 };
- this.matchingGroup = { group: this.group };
- this.wrongIdGroup = { group: this.wrongGroup };
- });
-
- it('should return true if the person has the group', function () {
- expect(
- massEditService.personHasGroup(
- {
- id: this.personId,
- group_memberships: [this.matchingGroup],
- },
- this.orgId,
- this.group,
- ),
- ).toBe(true);
- });
-
- it('should return false if the person does not have the group', function () {
- expect(
- massEditService.personHasGroup(
- {
- id: this.personId,
- group_memberships: [this.wrongIdGroup],
- },
- this.orgId,
- this.group,
- ),
- ).toBe(false);
- });
- });
-
- describe('selectionFromOptions', function () {
- it('should return a selection object', function () {
- expect(
- massEditService.selectionFromOptions([
- { id: 1, state: true },
- { id: 2, state: false },
- { id: 3, state: true },
- ]),
- ).toEqual({
- 1: true,
- 2: false,
- 3: true,
- });
- });
+ it('should copy person changes to changed people', function () {
+ massEditService.applyChangesLocally(
+ {
+ selectedPeople: [0, 2],
+ },
+ { student_status: 1 },
+ );
+ expect(this.people[0].student_status).toBe(1);
+ expect(this.people[1].student_status).toBeUndefined();
+ expect(this.people[2].student_status).toBe(1);
+ });
+
+ it('should copy permission changes to changed people organizational permissions', function () {
+ const orgId = 123;
+ this.people[0].organizational_permissions = [{ id: 100, organization_id: orgId }];
+ this.people[1].organizational_permissions = [];
+ this.people[2].organizational_permissions = [
+ { id: 200, organization_id: orgId + 1 },
+ { id: 201, organization_id: orgId },
+ ];
+ massEditService.applyChangesLocally(
+ {
+ orgId,
+ selectedPeople: [0, 2],
+ },
+ { followup_status: 1 },
+ );
+ expect(this.people[0].organizational_permissions[0].followup_status).toBe(1);
+ expect(this.people[1].organizational_permissions[0]).toBeUndefined();
+ expect(this.people[2].organizational_permissions[1].followup_status).toBe(1);
+ });
+
+ it('should add and remove assignments', inject(function (JsonApiDataStore) {
+ this.people[0].reverse_contact_assignments = [{ assigned_to: { id: '1' } }];
+ spyOn(JsonApiDataStore.store, 'destroy');
+ massEditService.applyChangesLocally(
+ {
+ selectedPeople: [0],
+ },
+ { assigned_tos: { 1: false, 2: true } },
+ );
+ expect(_.map(this.people[0].reverse_contact_assignments, 'assigned_to.id')).toEqual([2]);
+ expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
+ }));
+
+ it('should add and remove labels', inject(function (JsonApiDataStore) {
+ this.people[0].organizational_labels = [{ label: { id: '1' } }];
+ spyOn(JsonApiDataStore.store, 'destroy');
+ massEditService.applyChangesLocally(
+ {
+ selectedPeople: [0],
+ },
+ { labels: { 1: false, 2: true } },
+ );
+ expect(_.map(this.people[0].organizational_labels, 'label.id')).toEqual(['2']);
+ expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
+ }));
+
+ it('should add and remove groups', inject(function (JsonApiDataStore) {
+ this.people[0].group_memberships = [{ group: { id: '1' } }];
+ spyOn(JsonApiDataStore.store, 'destroy');
+ massEditService.applyChangesLocally(
+ {
+ selectedPeople: [0],
+ },
+ { groups: { 1: false, 2: true } },
+ );
+ expect(_.map(this.people[0].group_memberships, 'group.id')).toEqual(['2']);
+ expect(JsonApiDataStore.store.destroy).toHaveBeenCalled();
+ }));
+ });
+
+ describe('personHasUnloadedRelationships', function () {
+ it('should should return true for unloaded relationships', function () {
+ expect(
+ massEditService.personHasUnloadedRelationships(
+ {
+ relationship1: this.loadedRelationship,
+ relationship2: this.unloadedRelationship,
+ },
+ ['relationship1', 'relationship2'],
+ ),
+ ).toBe(true);
+ });
+
+ it('should should return false for loaded relationships', function () {
+ expect(
+ massEditService.personHasUnloadedRelationships(
+ {
+ relationship1: this.loadedRelationship,
+ relationship2: this.loadedRelationship,
+ },
+ ['relationship1', 'relationship2'],
+ ),
+ ).toBe(false);
+ });
+ });
+
+ describe('relationshipHasUnloadedModels', function () {
+ it('should return true for unloaded models', function () {
+ expect(massEditService.relationshipHasUnloadedModels(this.unloadedRelationship)).toBe(true);
+ });
+
+ it('should return false for loaded models', function () {
+ expect(massEditService.relationshipHasUnloadedModels(this.loadedRelationship)).toBe(false);
+ });
+ });
+
+ describe('optionsForStatsType', function () {
+ beforeEach(function () {
+ this.stats = {
+ assigned_tos: [
+ { person_id: 1, name: 'Adam', count: 1 },
+ { person_id: 2, name: 'Bill', count: 1 },
+ ],
+ };
+ this.selection = {};
+ spyOn(massEditService, 'optionStateFromModel').and.returnValue(true);
+ });
+
+ it('should generate options from the stats', function () {
+ expect(massEditService.optionsForStatsType(this.stats, 'assigned_tos', this.select)).toEqual([
+ { id: 1, name: 'Adam', state: true },
+ { id: 2, name: 'Bill', state: true },
+ ]);
+ });
+
+ it('should set the state to unselected for empty stats', function () {
+ this.stats.assigned_tos[0].count = 0;
+ expect(massEditService.optionsForStatsType(this.stats, 'assigned_tos', this.select)).toEqual([
+ { id: 1, name: 'Adam', state: false },
+ { id: 2, name: 'Bill', state: true },
+ ]);
+ });
+ });
+
+ describe('optionsFromStats', function () {
+ it('should generate options for all types from the stats', function () {
+ this.stats = {};
+ this.selection = {};
+ this.assignedTos = [];
+ this.labels = [];
+ this.groups = [];
+ spyOn(massEditService, 'optionsForStatsType').and.returnValues(this.assignedTos, this.labels, this.groups);
+ expect(massEditService.optionsFromStats(this.stats, this.selection)).toEqual({
+ assigned_tos: this.assignedTos,
+ labels: this.labels,
+ groups: this.groups,
+ });
+ });
+ });
+
+ describe('optionStateFromModel', function () {
+ beforeEach(inject(function (JsonApiDataStore) {
+ this.people = _.range(3).map(function (personId) {
+ return { id: personId };
+ });
+
+ const people = this.people;
+ spyOn(JsonApiDataStore.store, 'find').and.callFake(function (modelType, modelId) {
+ return people[modelId];
+ });
+
+ this.model = {};
+
+ this.selection = {
+ allSelected: false,
+ allIncluded: true,
+ orgId: 1,
+ selectedPeople: [0, 1],
+ };
+ }));
+
+ it('should return indeterminate when some unincluded people are selected', function () {
+ this.selection.allSelected = true;
+ this.selection.allIncluded = false;
+ expect(massEditService.optionStateFromModel(this.model, this.selection, _.noop)).toBe(null);
+ });
+
+ it('should return unselected when no people are selected', function () {
+ this.selection.selectedPeople = [];
+ expect(massEditService.optionStateFromModel(this.model, this.selection, _.noop)).toBe(false);
+ });
+
+ it('should call personHasModel once per selected person', function () {
+ const personHasModel = jasmine.createSpy();
+ massEditService.optionStateFromModel(this.model, this.selection, personHasModel);
+ expect(personHasModel.calls.allArgs()).toEqual([
+ [this.people[0], this.selection.orgId, this.model],
+ [this.people[1], this.selection.orgId, this.model],
+ ]);
+ });
+
+ it('should return selected when all selected people have the model', function () {
+ const personHasModel = jasmine.createSpy().and.returnValues(true, true);
+ expect(massEditService.optionStateFromModel(this.model, this.selection, personHasModel)).toBe(true);
+ });
+
+ it('should return unselected when no selected people have the model', function () {
+ const personHasModel = jasmine.createSpy().and.returnValues(false, false);
+ expect(massEditService.optionStateFromModel(this.model, this.selection, personHasModel)).toBe(false);
+ });
+
+ it('should return indeterminate when some selected people have the model', function () {
+ const personHasModel = jasmine.createSpy().and.returnValues(true, false);
+ expect(massEditService.optionStateFromModel(this.model, this.selection, personHasModel)).toBe(null);
+ });
+ });
+
+ describe('personHasAssignment', function () {
+ beforeEach(function () {
+ this.teamMember = { id: 1 };
+ this.wrongTeamMember = { id: 2 };
+ this.orgId = 123;
+ this.org = { id: this.orgId };
+ this.wrongOrg = { id: 234 };
+ this.matchingAssignment = {
+ assigned_to: this.teamMember,
+ organization: this.org,
+ };
+ this.wrongPersonAssignment = {
+ assigned_to: this.wrongTeamMember,
+ organization: this.org,
+ };
+ this.wrongOrgAssignment = {
+ assigned_to: this.teamMember,
+ organization: this.wrongOrg,
+ };
+ });
+
+ it('should return true if the person is assigned to the team member', function () {
+ expect(
+ massEditService.personHasAssignment(
+ {
+ reverse_contact_assignments: [this.matchingAssignment],
+ },
+ this.orgId,
+ this.teamMember,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if the person is not assigned to the team member', function () {
+ expect(
+ massEditService.personHasAssignment(
+ {
+ reverse_contact_assignments: [this.wrongPersonAssignment, this.wrongOrgAssignment],
+ },
+ this.orgId,
+ this.teamMember,
+ ),
+ ).toBe(false);
+ });
+ });
+
+ describe('personHasLabel', function () {
+ beforeEach(function () {
+ this.personId = 1;
+ this.label = { id: 123 };
+ this.wrongLabel = { id: 234 };
+ this.orgId = 456;
+ this.wrongOrgId = 567;
+ this.matchingLabel = {
+ organization_id: this.orgId,
+ label: this.label,
+ };
+ this.wrongIdLabel = {
+ organization_id: this.orgId,
+ label: this.wrongLabel,
+ };
+ this.wrongOrgLabel = {
+ organization_id: this.wrongOrgId,
+ label: this.label,
+ };
+ });
+
+ it('should return true if the person has the label', function () {
+ expect(
+ massEditService.personHasLabel(
+ {
+ id: this.personId,
+ organizational_labels: [this.matchingLabel],
+ },
+ this.orgId,
+ this.label,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if the person does not have the label', function () {
+ expect(
+ massEditService.personHasLabel(
+ {
+ id: this.personId,
+ organizational_labels: [this.wrongIdLabel, this.wrongOrgLabel],
+ },
+ this.orgId,
+ this.label,
+ ),
+ ).toBe(false);
+ });
+ });
+
+ describe('personHasGroup', function () {
+ beforeEach(function () {
+ this.personId = 1;
+ this.group = { id: 123 };
+ this.wrongGroup = { id: 234 };
+ this.matchingGroup = { group: this.group };
+ this.wrongIdGroup = { group: this.wrongGroup };
+ });
+
+ it('should return true if the person has the group', function () {
+ expect(
+ massEditService.personHasGroup(
+ {
+ id: this.personId,
+ group_memberships: [this.matchingGroup],
+ },
+ this.orgId,
+ this.group,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false if the person does not have the group', function () {
+ expect(
+ massEditService.personHasGroup(
+ {
+ id: this.personId,
+ group_memberships: [this.wrongIdGroup],
+ },
+ this.orgId,
+ this.group,
+ ),
+ ).toBe(false);
+ });
+ });
+
+ describe('selectionFromOptions', function () {
+ it('should return a selection object', function () {
+ expect(
+ massEditService.selectionFromOptions([
+ { id: 1, state: true },
+ { id: 2, state: false },
+ { id: 3, state: true },
+ ]),
+ ).toEqual({
+ 1: true,
+ 2: false,
+ 3: true,
+ });
});
+ });
});
diff --git a/app/assets/javascripts/angular/components/messageModal/messageModal.component.js b/app/assets/javascripts/angular/components/messageModal/messageModal.component.js
index ec0aa69ea4..ca58d81403 100644
--- a/app/assets/javascripts/angular/components/messageModal/messageModal.component.js
+++ b/app/assets/javascripts/angular/components/messageModal/messageModal.component.js
@@ -4,157 +4,141 @@ import i18next from 'i18next';
import template from './messageModal.html';
angular.module('campusContactsApp').component('messageModal', {
- controller: messageModalController,
- template: template,
- bindings: {
- resolve: '<',
- close: '&',
- dismiss: '&',
- },
+ controller: messageModalController,
+ template,
+ bindings: {
+ resolve: '<',
+ close: '&',
+ dismiss: '&',
+ },
});
-function messageModalController(
- $filter,
- JsonApiDataStore,
- messageModalService,
- _,
-) {
- var vm = this;
-
- vm.message = '';
- vm.sending = false;
-
- vm.getMaximumLength = getMaximumLength;
- vm.getRemainingLength = getRemainingLength;
- vm.isValid = isValid;
- vm.send = send;
- vm.cancel = cancel;
-
- vm.$onInit = activate;
-
- // The maximum message lengths of the supported media
- var lengthLimits = {
- email: 5000,
- sms: 480,
- };
-
- function activate() {
- vm.medium = vm.resolve.medium;
- if (!_.includes(['email', 'sms'], vm.medium)) {
- throw new Error('Invalid medium: ' + vm.medium);
- }
-
- vm.title = 'messages.' + vm.medium + '_title';
- vm.subtitle = 'messages.' + vm.medium + '_subtitle';
- vm.formattedRecipients = getFormattedRecipients();
- }
-
- // Return the recipients list as a nicely-formatted, human-readable string
- function getFormattedRecipients() {
- var filters = vm.resolve.selection.filters;
- var parts = [];
+function messageModalController($filter, JsonApiDataStore, messageModalService, _) {
+ const vm = this;
- parts.push(
- i18next.t('messages.recipients.contacts', {
- contact_count: vm.resolve.selection.totalSelectedPeople,
- }),
- );
+ vm.message = '';
+ vm.sending = false;
- var organizationName = JsonApiDataStore.store.find(
- 'organization',
- vm.resolve.selection.orgId,
- ).name;
- parts.push(
- i18next.t('messages.recipients.organization', {
- name: organizationName,
- }),
- );
+ vm.getMaximumLength = getMaximumLength;
+ vm.getRemainingLength = getRemainingLength;
+ vm.isValid = isValid;
+ vm.send = send;
+ vm.cancel = cancel;
- if (filters.searchString) {
- parts.push(
- i18next.t('messages.recipients.search', {
- search: filters.searchString,
- }),
- );
- }
-
- var exclusions = vm.resolve.selection.allSelected
- ? vm.resolve.selection.unselectedPeople
- : [];
-
- /*
- * "name" the property on the filters object
- * "type" is the JSON API model type that the filter ids refer to
- * "nameField" is the name of the name attribute on the JSON API model
- * "ids" is the array of model ids and defaults to filters[definition.name]
- */
- var filterDefinitions = [
- { name: 'groups', type: 'group', nameField: 'name' },
- { name: 'labels', type: 'label', nameField: 'name' },
- { name: 'assignedTos', type: 'person', nameField: 'full_name' },
- {
- name: 'exclusions',
- type: 'person',
- nameField: 'full_name',
- ids: exclusions,
- },
- ];
- filterDefinitions.forEach(function (definition) {
- var ids = definition.ids || filters[definition.name] || [];
- if (ids.length > 0) {
- var names = ids
- .map(function (id) {
- return JsonApiDataStore.store.find(
- definition.type,
- id,
- )[definition.nameField];
- })
- .join(', ');
- parts.push(
- i18next.t(
- 'messages.recipients.' + _.snakeCase(definition.name),
- {
- names: names,
- },
- ),
- );
- }
- });
-
- return parts.join(' ');
- }
+ vm.$onInit = activate;
- // Return the number of characters that the message can still hold
- function getMaximumLength() {
- return lengthLimits[vm.medium];
- }
+ // The maximum message lengths of the supported media
+ const lengthLimits = {
+ email: 5000,
+ sms: 480,
+ };
- // Return the number of characters that the message can still hold
- function getRemainingLength() {
- return getMaximumLength() - vm.message.length;
+ function activate() {
+ vm.medium = vm.resolve.medium;
+ if (!_.includes(['email', 'sms'], vm.medium)) {
+ throw new Error('Invalid medium: ' + vm.medium);
}
- function isValid() {
- return vm.message && (vm.medium !== 'email' || vm.subject);
+ vm.title = 'messages.' + vm.medium + '_title';
+ vm.subtitle = 'messages.' + vm.medium + '_subtitle';
+ vm.formattedRecipients = getFormattedRecipients();
+ }
+
+ // Return the recipients list as a nicely-formatted, human-readable string
+ function getFormattedRecipients() {
+ const filters = vm.resolve.selection.filters;
+ const parts = [];
+
+ parts.push(
+ i18next.t('messages.recipients.contacts', {
+ contact_count: vm.resolve.selection.totalSelectedPeople,
+ }),
+ );
+
+ const organizationName = JsonApiDataStore.store.find('organization', vm.resolve.selection.orgId).name;
+ parts.push(
+ i18next.t('messages.recipients.organization', {
+ name: organizationName,
+ }),
+ );
+
+ if (filters.searchString) {
+ parts.push(
+ i18next.t('messages.recipients.search', {
+ search: filters.searchString,
+ }),
+ );
}
- function send() {
- vm.sending = true;
-
- var sendingPromise = messageModalService.sendMessage({
- recipients: vm.resolve.selection,
- medium: vm.medium,
- subject: vm.subject,
- message: vm.message,
- surveyId: vm.resolve.surveyId,
- });
-
- sendingPromise.then(vm.close).catch(function () {
- vm.sending = false;
- });
- }
-
- function cancel() {
- vm.dismiss();
- }
+ const exclusions = vm.resolve.selection.allSelected ? vm.resolve.selection.unselectedPeople : [];
+
+ /*
+ * "name" the property on the filters object
+ * "type" is the JSON API model type that the filter ids refer to
+ * "nameField" is the name of the name attribute on the JSON API model
+ * "ids" is the array of model ids and defaults to filters[definition.name]
+ */
+ const filterDefinitions = [
+ { name: 'groups', type: 'group', nameField: 'name' },
+ { name: 'labels', type: 'label', nameField: 'name' },
+ { name: 'assignedTos', type: 'person', nameField: 'full_name' },
+ {
+ name: 'exclusions',
+ type: 'person',
+ nameField: 'full_name',
+ ids: exclusions,
+ },
+ ];
+ filterDefinitions.forEach(function (definition) {
+ const ids = definition.ids || filters[definition.name] || [];
+ if (ids.length > 0) {
+ const names = ids
+ .map(function (id) {
+ return JsonApiDataStore.store.find(definition.type, id)[definition.nameField];
+ })
+ .join(', ');
+ parts.push(
+ i18next.t('messages.recipients.' + _.snakeCase(definition.name), {
+ names,
+ }),
+ );
+ }
+ });
+
+ return parts.join(' ');
+ }
+
+ // Return the number of characters that the message can still hold
+ function getMaximumLength() {
+ return lengthLimits[vm.medium];
+ }
+
+ // Return the number of characters that the message can still hold
+ function getRemainingLength() {
+ return getMaximumLength() - vm.message.length;
+ }
+
+ function isValid() {
+ return vm.message && (vm.medium !== 'email' || vm.subject);
+ }
+
+ function send() {
+ vm.sending = true;
+
+ const sendingPromise = messageModalService.sendMessage({
+ recipients: vm.resolve.selection,
+ medium: vm.medium,
+ subject: vm.subject,
+ message: vm.message,
+ surveyId: vm.resolve.surveyId,
+ });
+
+ sendingPromise.then(vm.close).catch(function () {
+ vm.sending = false;
+ });
+ }
+
+ function cancel() {
+ vm.dismiss();
+ }
}
diff --git a/app/assets/javascripts/angular/components/messageModal/messageModal.html b/app/assets/javascripts/angular/components/messageModal/messageModal.html
index b4dc4e1004..6899e61d8c 100644
--- a/app/assets/javascripts/angular/components/messageModal/messageModal.html
+++ b/app/assets/javascripts/angular/components/messageModal/messageModal.html
@@ -1,46 +1,34 @@
-
-
-
+
+
+
diff --git a/app/assets/javascripts/angular/components/messageModal/messageModal.service.js b/app/assets/javascripts/angular/components/messageModal/messageModal.service.js
index 74799d5513..7798081846 100644
--- a/app/assets/javascripts/angular/components/messageModal/messageModal.service.js
+++ b/app/assets/javascripts/angular/components/messageModal/messageModal.service.js
@@ -1,41 +1,35 @@
-angular
- .module('campusContactsApp')
- .factory('messageModalService', messageModalService);
+angular.module('campusContactsApp').factory('messageModalService', messageModalService);
function messageModalService(httpProxy, personSelectionService) {
- var messageModalService = {
- sendMessage: function (options) {
- var selection = options.recipients;
- var filters = personSelectionService.convertToFilters(
- selection,
- options.surveyId,
- );
+ const messageModalService = {
+ sendMessage: function (options) {
+ const selection = options.recipients;
+ const filters = personSelectionService.convertToFilters(selection, options.surveyId);
- return httpProxy.post(
- '/bulk_messages',
- {
- data: {
- type: 'bulk_message',
- attributes: {
- organization_id: selection.orgId,
- send_via: options.medium,
- subject: options.subject,
- message: options.message,
+ return httpProxy.post(
+ '/bulk_messages',
+ {
+ data: {
+ type: 'bulk_message',
+ attributes: {
+ organization_id: selection.orgId,
+ send_via: options.medium,
+ subject: options.subject,
+ message: options.message,
- // Prefer using recipient_ids over filters because the former does validation that the
- // latter does not. If recipient_ids is truthy, it will override the filters.
- recipient_ids: filters.ids,
- },
- },
- filters: filters,
- },
- {
- errorMessage:
- 'error.messages.bulk_message_modal.send_message',
- },
- );
+ // Prefer using recipient_ids over filters because the former does validation that the
+ // latter does not. If recipient_ids is truthy, it will override the filters.
+ recipient_ids: filters.ids,
+ },
+ },
+ filters,
},
- };
+ {
+ errorMessage: 'error.messages.bulk_message_modal.send_message',
+ },
+ );
+ },
+ };
- return messageModalService;
+ return messageModalService;
}
diff --git a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.component.js b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.component.js
index 79a1043bf9..b054c1dc39 100644
--- a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.component.js
+++ b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.component.js
@@ -2,88 +2,80 @@ import template from './ministryViewGroup.html';
import './ministryViewGroup.scss';
angular.module('campusContactsApp').component('ministryViewGroup', {
- controller: ministryViewGroupController,
- template: template,
- bindings: {
- group: '<',
- },
+ controller: ministryViewGroupController,
+ template,
+ bindings: {
+ group: '<',
+ },
});
function ministryViewGroupController(
- $scope,
- $uibModal,
- confirmModalService,
- tFilter,
- groupsService,
- ministryViewGroupService,
- _,
+ $scope,
+ $uibModal,
+ confirmModalService,
+ tFilter,
+ groupsService,
+ ministryViewGroupService,
+ _,
) {
- var vm = this;
+ const vm = this;
- vm.getLeaders = getLeaders;
- vm.openGroup = openGroup;
- vm.editGroup = editGroup;
- vm.deleteGroup = deleteGroup;
- vm.$onInit = activate;
- vm.groupLength = groupLength;
+ vm.getLeaders = getLeaders;
+ vm.openGroup = openGroup;
+ vm.editGroup = editGroup;
+ vm.deleteGroup = deleteGroup;
+ vm.$onInit = activate;
+ vm.groupLength = groupLength;
- function groupLength() {
- return vm.group.group_memberships.filter((m) => m.person !== null)
- .length;
- }
+ function groupLength() {
+ return vm.group.group_memberships.filter((m) => m.person !== null).length;
+ }
- function activate() {
- $scope.$watchGroup(
- [
- '$ctrl.group.meets',
- '$ctrl.group.meeting_day',
- '$ctrl.group.start_time',
- '$ctrl.group.end_time',
- ],
- function () {
- vm.meetingTime = ministryViewGroupService.formatMeetingTime(
- vm.group,
- );
- },
- );
- }
+ function activate() {
+ $scope.$watchGroup(
+ ['$ctrl.group.meets', '$ctrl.group.meeting_day', '$ctrl.group.start_time', '$ctrl.group.end_time'],
+ function () {
+ vm.meetingTime = ministryViewGroupService.formatMeetingTime(vm.group);
+ },
+ );
+ }
- function getLeaders() {
- return groupsService.getMembersWithRole(vm.group, 'leader');
- }
+ function getLeaders() {
+ return groupsService.getMembersWithRole(vm.group, 'leader');
+ }
- function openGroup() {
- $uibModal.open({
- component: 'groupMembersModal',
- resolve: {
- group: _.constant(vm.group),
- },
- windowClass: 'pivot_theme',
- size: 'md',
- });
- }
+ function openGroup() {
+ $uibModal.open({
+ component: 'groupMembersModal',
+ resolve: {
+ group: _.constant(vm.group),
+ },
+ windowClass: 'pivot_theme',
+ size: 'md',
+ });
+ }
- function editGroup() {
- $uibModal.open({
- component: 'editGroup',
- resolve: {
- organizationId: _.constant(vm.group.organization.id),
- group: _.constant(vm.group),
- },
- windowClass: 'pivot_theme',
- size: 'md',
- });
- }
+ function editGroup() {
+ $uibModal.open({
+ component: 'editGroup',
+ resolve: {
+ organizationId: _.constant(vm.group.organization.id),
+ group: _.constant(vm.group),
+ },
+ windowClass: 'pivot_theme',
+ size: 'md',
+ });
+ }
- function deleteGroup() {
- confirmModalService
- .create(
- tFilter('groups.confirm_delete_group', {
- group_name: vm.group.name,
- }),
- )
- .then(function () {
- return groupsService.deleteGroup(vm.group);
- });
- }
+ function deleteGroup() {
+ confirmModalService
+ .create(
+ tFilter('groups.confirm_delete_group', {
+ group_name: vm.group.name,
+ }),
+ )
+ .then(function () {
+ return groupsService.deleteGroup(vm.group);
+ });
+ }
}
diff --git a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.html b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.html
index 5f94ac3c56..ef9e790e75 100644
--- a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.html
+++ b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.html
@@ -1,26 +1,22 @@
-
- {{ $ctrl.group.name }} ({{ $ctrl.groupLength() }})
-
-
-
- {{ leader | personName }},
-
-
-
{{ $ctrl.meetingTime }}
-
{{ $ctrl.group.location }}
-
-
-
-
+
{{ $ctrl.group.name }} ({{ $ctrl.groupLength() }})
+
+ {{ leader | personName }},
+
+
{{ $ctrl.meetingTime }}
+
{{ $ctrl.group.location }}
+
+
+
+
diff --git a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.js b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.js
index e70583d754..cccf59777c 100644
--- a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.js
+++ b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.js
@@ -1,53 +1,49 @@
-angular
- .module('campusContactsApp')
- .factory('ministryViewGroupService', ministryViewGroupService);
+angular.module('campusContactsApp').factory('ministryViewGroupService', ministryViewGroupService);
function ministryViewGroupService(groupsService, tFilter, moment, _) {
- // Convert a time in milliseconds since midnight into a human-readable string
- function formatTime(time) {
- return _.isNil(time)
- ? ''
- : moment(groupsService.timeToDate(time)).format('LT');
- }
+ // Convert a time in milliseconds since midnight into a human-readable string
+ function formatTime(time) {
+ return _.isNil(time) ? '' : moment(groupsService.timeToDate(time)).format('LT');
+ }
- // Convert a number into its ordinal form (1st, 2nd, 3rd, etc.)
- function formatOrdinal(number) {
- var onesDigit = number % 10;
- var tensDigit = ((number % 100) - onesDigit) / 10;
- var suffix =
- tensDigit !== 1 && onesDigit >= 1 && onesDigit <= 3
- ? tFilter(`ministries.groups.ordinal_suffixes.${onesDigit}`)
- : tFilter('ministries.groups.ordinal_suffixes.0');
- return number + suffix;
- }
+ // Convert a number into its ordinal form (1st, 2nd, 3rd, etc.)
+ function formatOrdinal(number) {
+ const onesDigit = number % 10;
+ const tensDigit = ((number % 100) - onesDigit) / 10;
+ const suffix =
+ tensDigit !== 1 && onesDigit >= 1 && onesDigit <= 3
+ ? tFilter(`ministries.groups.ordinal_suffixes.${onesDigit}`)
+ : tFilter('ministries.groups.ordinal_suffixes.0');
+ return number + suffix;
+ }
- // Return a human-readable string representing the group's meeting time
- function formatMeetingTime(group) {
- var time = tFilter('ministries.groups.meeting_time.time_range', {
- start: formatTime(group.start_time),
- end: formatTime(group.end_time),
- });
+ // Return a human-readable string representing the group's meeting time
+ function formatMeetingTime(group) {
+ const time = tFilter('ministries.groups.meeting_time.time_range', {
+ start: formatTime(group.start_time),
+ end: formatTime(group.end_time),
+ });
- if (group.meets === 'weekly') {
- var dayOfWeek = tFilter('date.day_names.' + group.meeting_day);
- return tFilter('ministries.groups.meeting_time.weekly', {
- time: time,
- day: dayOfWeek,
- });
- } else if (group.meets === 'monthly') {
- var dayOfMonth = formatOrdinal(group.meeting_day);
- return tFilter('ministries.groups.meeting_time.monthly', {
- time: time,
- day: dayOfMonth,
- });
- } else if (group.meets === 'sporadically') {
- return tFilter('ministries.groups.meeting_time.sporadically');
- }
+ if (group.meets === 'weekly') {
+ const dayOfWeek = tFilter('date.day_names.' + group.meeting_day);
+ return tFilter('ministries.groups.meeting_time.weekly', {
+ time,
+ day: dayOfWeek,
+ });
+ } else if (group.meets === 'monthly') {
+ const dayOfMonth = formatOrdinal(group.meeting_day);
+ return tFilter('ministries.groups.meeting_time.monthly', {
+ time,
+ day: dayOfMonth,
+ });
+ } else if (group.meets === 'sporadically') {
+ return tFilter('ministries.groups.meeting_time.sporadically');
}
+ }
- return {
- formatTime: formatTime,
- formatOrdinal: formatOrdinal,
- formatMeetingTime: formatMeetingTime,
- };
+ return {
+ formatTime,
+ formatOrdinal,
+ formatMeetingTime,
+ };
}
diff --git a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.spec.js b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.spec.js
index 0c55494b58..a48c56c0b0 100644
--- a/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.spec.js
+++ b/app/assets/javascripts/angular/components/ministryViewGroup/ministryViewGroup.service.spec.js
@@ -1,75 +1,75 @@
import 'angular-mocks';
// Constants
-var ministryViewGroupService, tFilter;
+let ministryViewGroupService;
describe('ministryViewGroupService service', function () {
- beforeEach(inject(function (_ministryViewGroupService_) {
- ministryViewGroupService = _ministryViewGroupService_;
- }));
+ beforeEach(inject(function (_ministryViewGroupService_) {
+ ministryViewGroupService = _ministryViewGroupService_;
+ }));
- describe('ministryViewGroupService.formatOrdinal', function () {
- it('should convert numbers to ordinal strings', function () {
- expect(ministryViewGroupService.formatOrdinal(0)).toBe('0th');
- expect(ministryViewGroupService.formatOrdinal(1)).toBe('1st');
- expect(ministryViewGroupService.formatOrdinal(2)).toBe('2nd');
- expect(ministryViewGroupService.formatOrdinal(3)).toBe('3rd');
- expect(ministryViewGroupService.formatOrdinal(4)).toBe('4th');
- expect(ministryViewGroupService.formatOrdinal(10)).toBe('10th');
- expect(ministryViewGroupService.formatOrdinal(11)).toBe('11th');
- expect(ministryViewGroupService.formatOrdinal(12)).toBe('12th');
- expect(ministryViewGroupService.formatOrdinal(13)).toBe('13th');
- expect(ministryViewGroupService.formatOrdinal(14)).toBe('14th');
- expect(ministryViewGroupService.formatOrdinal(20)).toBe('20th');
- expect(ministryViewGroupService.formatOrdinal(21)).toBe('21st');
- expect(ministryViewGroupService.formatOrdinal(22)).toBe('22nd');
- expect(ministryViewGroupService.formatOrdinal(23)).toBe('23rd');
- expect(ministryViewGroupService.formatOrdinal(24)).toBe('24th');
- });
+ describe('ministryViewGroupService.formatOrdinal', function () {
+ it('should convert numbers to ordinal strings', function () {
+ expect(ministryViewGroupService.formatOrdinal(0)).toBe('0th');
+ expect(ministryViewGroupService.formatOrdinal(1)).toBe('1st');
+ expect(ministryViewGroupService.formatOrdinal(2)).toBe('2nd');
+ expect(ministryViewGroupService.formatOrdinal(3)).toBe('3rd');
+ expect(ministryViewGroupService.formatOrdinal(4)).toBe('4th');
+ expect(ministryViewGroupService.formatOrdinal(10)).toBe('10th');
+ expect(ministryViewGroupService.formatOrdinal(11)).toBe('11th');
+ expect(ministryViewGroupService.formatOrdinal(12)).toBe('12th');
+ expect(ministryViewGroupService.formatOrdinal(13)).toBe('13th');
+ expect(ministryViewGroupService.formatOrdinal(14)).toBe('14th');
+ expect(ministryViewGroupService.formatOrdinal(20)).toBe('20th');
+ expect(ministryViewGroupService.formatOrdinal(21)).toBe('21st');
+ expect(ministryViewGroupService.formatOrdinal(22)).toBe('22nd');
+ expect(ministryViewGroupService.formatOrdinal(23)).toBe('23rd');
+ expect(ministryViewGroupService.formatOrdinal(24)).toBe('24th');
});
+ });
- describe('ministryViewGroupService.formatTime', function () {
- it('should format a time in milliseconds since midnight as a time string', function () {
- expect(ministryViewGroupService.formatTime(0)).toBe('12:00 AM');
- });
+ describe('ministryViewGroupService.formatTime', function () {
+ it('should format a time in milliseconds since midnight as a time string', function () {
+ expect(ministryViewGroupService.formatTime(0)).toBe('12:00 AM');
+ });
- it('should return a blank string for null times', function () {
- expect(ministryViewGroupService.formatTime(null)).toBe('');
- });
+ it('should return a blank string for null times', function () {
+ expect(ministryViewGroupService.formatTime(null)).toBe('');
});
+ });
- describe('ministryViewGroupService.formatMeetingTime', function () {
- it('should format weekly meeting times', function () {
- expect(
- ministryViewGroupService.formatMeetingTime({
- meets: 'weekly',
- meeting_day: 4,
- start_time: 18 * 60 * 60 * 1000,
- end_time: 20 * 60 * 60 * 1000,
- }),
- ).toEqual('Weekly on Thursdays 6:00 PM - 8:00 PM');
- });
+ describe('ministryViewGroupService.formatMeetingTime', function () {
+ it('should format weekly meeting times', function () {
+ expect(
+ ministryViewGroupService.formatMeetingTime({
+ meets: 'weekly',
+ meeting_day: 4,
+ start_time: 18 * 60 * 60 * 1000,
+ end_time: 20 * 60 * 60 * 1000,
+ }),
+ ).toEqual('Weekly on Thursdays 6:00 PM - 8:00 PM');
+ });
- it('should format monthly meeting times', function () {
- expect(
- ministryViewGroupService.formatMeetingTime({
- meets: 'monthly',
- meeting_day: 11,
- start_time: 18 * 60 * 60 * 1000,
- end_time: 20 * 60 * 60 * 1000,
- }),
- ).toEqual('Monthly on the 11th from 6:00 PM - 8:00 PM');
- });
+ it('should format monthly meeting times', function () {
+ expect(
+ ministryViewGroupService.formatMeetingTime({
+ meets: 'monthly',
+ meeting_day: 11,
+ start_time: 18 * 60 * 60 * 1000,
+ end_time: 20 * 60 * 60 * 1000,
+ }),
+ ).toEqual('Monthly on the 11th from 6:00 PM - 8:00 PM');
+ });
- it('should format sporadic meeting times', function () {
- expect(
- ministryViewGroupService.formatMeetingTime({
- meets: 'sporadically',
- meeting_day: null,
- start_time: null,
- end_time: null,
- }),
- ).toEqual('Sporadically');
- });
+ it('should format sporadic meeting times', function () {
+ expect(
+ ministryViewGroupService.formatMeetingTime({
+ meets: 'sporadically',
+ meeting_day: null,
+ start_time: null,
+ end_time: null,
+ }),
+ ).toEqual('Sporadically');
});
+ });
});
diff --git a/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.js b/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.js
index 955b8b12df..0ba2adab86 100644
--- a/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.js
+++ b/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.js
@@ -2,50 +2,41 @@ import template from './ministryViewLabel.html';
import './ministryViewLabel.scss';
angular.module('campusContactsApp').component('ministryViewLabel', {
- controller: ministryViewLabelController,
- template: template,
- bindings: {
- label: '<',
- },
+ controller: ministryViewLabelController,
+ template,
+ bindings: {
+ label: '<',
+ },
});
-function ministryViewLabelController(
- $uibModal,
- confirmModalService,
- tFilter,
- labelsService,
- loggedInPerson,
- _,
-) {
- var vm = this;
+function ministryViewLabelController($uibModal, confirmModalService, tFilter, labelsService, loggedInPerson, _) {
+ const vm = this;
- vm.editLabel = editLabel;
- vm.deleteLabel = deleteLabel;
- vm.$onInit = activate;
+ vm.editLabel = editLabel;
+ vm.deleteLabel = deleteLabel;
+ vm.$onInit = activate;
- function activate() {
- vm.adminPrivileges = loggedInPerson.isAdminAt(vm.label.organization);
- }
+ function activate() {
+ vm.adminPrivileges = loggedInPerson.isAdminAt(vm.label.organization);
+ }
- function editLabel() {
- $uibModal.open({
- component: 'editLabel',
- resolve: {
- organizationId: _.constant(vm.label.organization.id),
- label: _.constant(vm.label),
- },
- windowClass: 'pivot_theme',
- size: 'sm',
- });
- }
+ function editLabel() {
+ $uibModal.open({
+ component: 'editLabel',
+ resolve: {
+ organizationId: _.constant(vm.label.organization.id),
+ label: _.constant(vm.label),
+ },
+ windowClass: 'pivot_theme',
+ size: 'sm',
+ });
+ }
- function deleteLabel() {
- return confirmModalService
- .create(
- tFilter('labels.delete.confirm', { label_name: vm.label.name }),
- )
- .then(function () {
- return labelsService.deleteLabel(vm.label);
- });
- }
+ function deleteLabel() {
+ return confirmModalService
+ .create(tFilter('labels.delete.confirm', { label_name: vm.label.name }))
+ .then(function () {
+ return labelsService.deleteLabel(vm.label);
+ });
+ }
}
diff --git a/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.spec.js b/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.spec.js
index c21fc0572e..d1493b7d5c 100644
--- a/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.spec.js
+++ b/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.component.spec.js
@@ -1,126 +1,105 @@
import 'angular-mocks';
// Constants
-var $ctrl, $q, $rootScope, JsonApiDataStore;
+let $ctrl, $q, $rootScope, JsonApiDataStore;
// Add better asynchronous support to a test function
// The test function must return a promise
// The promise will automatically be bound to "done" and the $rootScope will be automatically digested
function asynchronous(fn) {
- return function (done) {
- var returnValue = fn.call(this, done);
- returnValue
- .then(function () {
- done();
- })
- .catch(function (err) {
- done.fail(err);
- });
- $rootScope.$apply();
- return returnValue;
- };
+ return function (done) {
+ const returnValue = fn.call(this, done);
+ returnValue
+ .then(function () {
+ done();
+ })
+ .catch(function (err) {
+ done.fail(err);
+ });
+ $rootScope.$apply();
+ return returnValue;
+ };
}
describe('minstryViewLabel component', function () {
- beforeEach(inject(function (
- $componentController,
- _$q_,
- _$rootScope_,
- _JsonApiDataStore_,
- ) {
- $q = _$q_;
- $rootScope = _$rootScope_;
- JsonApiDataStore = _JsonApiDataStore_;
-
- this.loggedInPerson = jasmine.createSpyObj('loggedInPerson', [
- 'isAdminAt',
- ]);
- this.$uibModal = jasmine.createSpyObj('$uibModal', ['open']);
- this.confirmModalService = jasmine.createSpyObj('confirmModalService', [
- 'create',
- ]);
- this.labelsService = jasmine.createSpyObj('labelsService', [
- 'deleteLabel',
- ]);
-
- this.organization = new JsonApiDataStore.Model('organization', 1);
- this.organization.setRelationship('labels', []);
-
- this.label = new JsonApiDataStore.Model('label');
- this.label.setAttribute('id', 1);
- this.label.setAttribute('name', 'Test label');
- this.label.setRelationship('organization', this.organization);
-
- $ctrl = $componentController(
- 'ministryViewLabel',
- {
- loggedInPerson: this.loggedInPerson,
- $uibModal: this.$uibModal,
- confirmModalService: this.confirmModalService,
- labelsService: this.labelsService,
- },
- {
- label: this.label,
- },
- );
- }));
-
- describe('$onInit', function () {
- it('should initialize admin privileges', function () {
- this.loggedInPerson.isAdminAt.and.returnValue(true);
-
- $ctrl.$onInit();
-
- expect(this.loggedInPerson.isAdminAt).toHaveBeenCalledWith(
- this.organization,
- );
- expect($ctrl.adminPrivileges).toEqual(true);
- });
+ beforeEach(inject(function ($componentController, _$q_, _$rootScope_, _JsonApiDataStore_) {
+ $q = _$q_;
+ $rootScope = _$rootScope_;
+ JsonApiDataStore = _JsonApiDataStore_;
+
+ this.loggedInPerson = jasmine.createSpyObj('loggedInPerson', ['isAdminAt']);
+ this.$uibModal = jasmine.createSpyObj('$uibModal', ['open']);
+ this.confirmModalService = jasmine.createSpyObj('confirmModalService', ['create']);
+ this.labelsService = jasmine.createSpyObj('labelsService', ['deleteLabel']);
+
+ this.organization = new JsonApiDataStore.Model('organization', 1);
+ this.organization.setRelationship('labels', []);
+
+ this.label = new JsonApiDataStore.Model('label');
+ this.label.setAttribute('id', 1);
+ this.label.setAttribute('name', 'Test label');
+ this.label.setRelationship('organization', this.organization);
+
+ $ctrl = $componentController(
+ 'ministryViewLabel',
+ {
+ loggedInPerson: this.loggedInPerson,
+ $uibModal: this.$uibModal,
+ confirmModalService: this.confirmModalService,
+ labelsService: this.labelsService,
+ },
+ {
+ label: this.label,
+ },
+ );
+ }));
+
+ describe('$onInit', function () {
+ it('should initialize admin privileges', function () {
+ this.loggedInPerson.isAdminAt.and.returnValue(true);
+
+ $ctrl.$onInit();
+
+ expect(this.loggedInPerson.isAdminAt).toHaveBeenCalledWith(this.organization);
+ expect($ctrl.adminPrivileges).toEqual(true);
+ });
+ });
+
+ describe('editLabel', function () {
+ it('should open the edit label modal', function () {
+ $ctrl.editLabel();
+
+ expect(this.$uibModal.open).toHaveBeenCalledWith(
+ jasmine.objectContaining({
+ component: 'editLabel',
+ }),
+ );
+ expect(this.$uibModal.open.calls.argsFor(0)[0].resolve.organizationId()).toEqual(this.organization.id);
+ expect(this.$uibModal.open.calls.argsFor(0)[0].resolve.label()).toEqual(this.label);
});
+ });
- describe('editLabel', function () {
- it('should open the edit label modal', function () {
- $ctrl.editLabel();
-
- expect(this.$uibModal.open).toHaveBeenCalledWith(
- jasmine.objectContaining({
- component: 'editLabel',
- }),
- );
- expect(
- this.$uibModal.open.calls
- .argsFor(0)[0]
- .resolve.organizationId(),
- ).toEqual(this.organization.id);
- expect(
- this.$uibModal.open.calls.argsFor(0)[0].resolve.label(),
- ).toEqual(this.label);
- });
+ describe('deleteLabel', function () {
+ beforeEach(function () {
+ this.confirmModalService.create.and.returnValue($q.resolve());
+ this.labelsService.deleteLabel.and.returnValue($q.resolve());
});
- describe('deleteLabel', function () {
- beforeEach(function () {
- this.confirmModalService.create.and.returnValue($q.resolve());
- this.labelsService.deleteLabel.and.returnValue($q.resolve());
- });
+ it('should open the confirmation dialog', function () {
+ $ctrl.deleteLabel();
- it('should open the confirmation dialog', function () {
- $ctrl.deleteLabel();
+ expect(this.confirmModalService.create).toHaveBeenCalled();
+ });
- expect(this.confirmModalService.create).toHaveBeenCalled();
- });
+ it(
+ 'should delete the label',
+ asynchronous(function () {
+ const _this = this;
- it(
- 'should delete the label',
- asynchronous(function () {
- var _this = this;
-
- return $ctrl.deleteLabel().then(function () {
- expect(
- _this.labelsService.deleteLabel,
- ).toHaveBeenCalledWith(_this.label);
- });
- }),
- );
- });
+ return $ctrl.deleteLabel().then(function () {
+ expect(_this.labelsService.deleteLabel).toHaveBeenCalledWith(_this.label);
+ });
+ }),
+ );
+ });
});
diff --git a/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.html b/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.html
index f7e21eac30..c4d6612d08 100644
--- a/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.html
+++ b/app/assets/javascripts/angular/components/ministryViewLabel/ministryViewLabel.html
@@ -1,23 +1,8 @@
-
- {{ $ctrl.label.i18n ? ($ctrl.label.i18n | t) : $ctrl.label.name }}
-
+
{{ $ctrl.label.i18n ? ($ctrl.label.i18n | t) : $ctrl.label.name }}
-
-
-
-
+
+
+
+
diff --git a/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.component.js b/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.component.js
index 11924b51ca..d365528494 100644
--- a/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.component.js
+++ b/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.component.js
@@ -2,115 +2,90 @@ import template from './ministryViewPerson.html';
import './ministryViewPerson.scss';
angular.module('campusContactsApp').component('ministryViewPerson', {
- controller: ministryViewPersonController,
- template: template,
- bindings: {
- person: '<',
- organizationId: '<',
- selected: '=',
- questions: '<',
- odd: '<',
- showLastSurvey: '=',
- },
+ controller: ministryViewPersonController,
+ template,
+ bindings: {
+ person: '<',
+ organizationId: '<',
+ selected: '=',
+ questions: '<',
+ odd: '<',
+ showLastSurvey: '=',
+ },
});
-function ministryViewPersonController(
- $rootScope,
- $scope,
- personService,
- personProfileService,
-) {
- var vm = this;
-
- vm.loadingAssignmentOptions = false;
- vm.addAssignment = addAssignment;
- vm.removeAssignment = removeAssignment;
- vm.saveAttribute = saveAttribute;
- vm.toggleAssignmentVisibility = toggleAssignmentVisibility;
- vm.onAssignmentsKeydown = onAssignmentsKeydown;
- vm.isContact = isContact;
- vm.followupStatusOptions = personService.getFollowupStatusOptions();
-
- this.$onInit = () => {
- // Update UI when the users add or removes an assignment
- this.personModifiedUnsubscribe = $rootScope.$on(
- 'personModified',
- () => {
- this.updateAssigned();
- },
- );
- };
+function ministryViewPersonController($rootScope, $scope, personService, personProfileService) {
+ const vm = this;
- this.$onDestroy = () => {
- this.personModifiedUnsubscribe();
- };
+ vm.loadingAssignmentOptions = false;
+ vm.addAssignment = addAssignment;
+ vm.removeAssignment = removeAssignment;
+ vm.saveAttribute = saveAttribute;
+ vm.toggleAssignmentVisibility = toggleAssignmentVisibility;
+ vm.onAssignmentsKeydown = onAssignmentsKeydown;
+ vm.isContact = isContact;
+ vm.followupStatusOptions = personService.getFollowupStatusOptions();
- this.$onChanges = (changes) => {
- if (
- !changes.person ||
- changes.person.currentValue === changes.person.previousValue
- )
- return;
+ this.$onInit = () => {
+ // Update UI when the users add or removes an assignment
+ this.personModifiedUnsubscribe = $rootScope.$on('personModified', () => {
+ this.updateAssigned();
+ });
+ };
- const person = changes.person.currentValue;
+ this.$onDestroy = () => {
+ this.personModifiedUnsubscribe();
+ };
- if (this.showLastSurvey) {
- if (!person.answer_sheets) this.lastSurvey = null;
+ this.$onChanges = (changes) => {
+ if (!changes.person || changes.person.currentValue === changes.person.previousValue) return;
- this.lastSurvey = personService.getLastSurvey(person);
- }
+ const person = changes.person.currentValue;
- this.orgPermission = personService.getOrgPermission(
- person,
- this.organizationId,
- );
+ if (this.showLastSurvey) {
+ if (!person.answer_sheets) this.lastSurvey = null;
- this.assignedTo = personService.getAssignedTo(
- person,
- this.organizationId,
- );
+ this.lastSurvey = personService.getLastSurvey(person);
+ }
- this.updateAssigned = () => {
- this.assignedTo = personService.getAssignedTo(
- person,
- this.organizationId,
- );
- };
+ this.orgPermission = personService.getOrgPermission(person, this.organizationId);
- this.phoneNumber = personService.getPhoneNumber(person);
- this.emailAddress = personService.getEmailAddress(person);
+ this.assignedTo = personService.getAssignedTo(person, this.organizationId);
+
+ this.updateAssigned = () => {
+ this.assignedTo = personService.getAssignedTo(person, this.organizationId);
};
- function addAssignment(person) {
- return personProfileService
- .addAssignments(vm.person, vm.organizationId, [person])
- .then(() => $scope.$emit('personModified'));
- }
+ this.phoneNumber = personService.getPhoneNumber(person);
+ this.emailAddress = personService.getEmailAddress(person);
+ };
- function removeAssignment(person) {
- return personProfileService
- .removeAssignments(vm.person, [person])
- .then(() => $scope.$emit('personModified'));
- }
+ function addAssignment(person) {
+ return personProfileService
+ .addAssignments(vm.person, vm.organizationId, [person])
+ .then(() => $scope.$emit('personModified'));
+ }
- function saveAttribute(model, attribute) {
- personProfileService
- .saveAttribute(vm.person.id, model, attribute)
- .then(() => $scope.$emit('personModified'));
- }
+ function removeAssignment(person) {
+ return personProfileService.removeAssignments(vm.person, [person]).then(() => $scope.$emit('personModified'));
+ }
- function toggleAssignmentVisibility() {
- vm.assignmentsVisible = !vm.assignmentsVisible;
- }
+ function saveAttribute(model, attribute) {
+ personProfileService.saveAttribute(vm.person.id, model, attribute).then(() => $scope.$emit('personModified'));
+ }
- function onAssignmentsKeydown(event) {
- if (event.keyCode === 27) {
- // Escape key was pressed, so hide the assignments selector
- vm.assignmentsVisible = false;
- }
- }
+ function toggleAssignmentVisibility() {
+ vm.assignmentsVisible = !vm.assignmentsVisible;
+ }
- function isContact() {
- return personService.isContact(vm.person, vm.organizationId);
+ function onAssignmentsKeydown(event) {
+ if (event.keyCode === 27) {
+ // Escape key was pressed, so hide the assignments selector
+ vm.assignmentsVisible = false;
}
+ }
+
+ function isContact() {
+ return personService.isContact(vm.person, vm.organizationId);
+ }
}
diff --git a/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.html b/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.html
index 2e7843b7d1..5aeab57bfa 100644
--- a/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.html
+++ b/app/assets/javascripts/angular/components/ministryViewPerson/ministryViewPerson.html
@@ -1,99 +1,72 @@
-
-
-
-
-
+
+
+
-
-
{{ 'ministries.people.' + $ctrl.person.gender.toLowerCase() | t
- }}
+
-
-
-
- {{ $ctrl.assignedTo.length > 0 ? ($ctrl.assignedTo[0] |
- personName: 'firstAndLastInitial') : ('assignments.unassigned' |
- t) }}
- (+{{ $ctrl.assignedTo.length - 1 }})
-
-
-
-
+
+
+ {{ 'ministries.people.' + $ctrl.person.gender.toLowerCase() | t }}
+
+
+
+
+ {{ $ctrl.assignedTo.length > 0 ? ($ctrl.assignedTo[0] | personName: 'firstAndLastInitial') :
+ ('assignments.unassigned' | t) }}
+ (+{{ $ctrl.assignedTo.length - 1 }})
+
+
-
-
-
-
- {{ 'followup_status.archived' | t }}
-
-
-
-
-
- {{ $ctrl.lastSurvey | date }}
-
-
- {{ $ctrl.person.answers[question.id].join(', ') }}
+
+
+
+
+
+
+ {{ 'followup_status.archived' | t }}
+
+
+
+
+
+ {{ $ctrl.lastSurvey | date }}
+
+
+ {{ $ctrl.person.answers[question.id].join(', ') }}
+
diff --git a/app/assets/javascripts/angular/components/multiselectList/multiselectList.component.js b/app/assets/javascripts/angular/components/multiselectList/multiselectList.component.js
index a46f038cbb..86e7f0a0f2 100644
--- a/app/assets/javascripts/angular/components/multiselectList/multiselectList.component.js
+++ b/app/assets/javascripts/angular/components/multiselectList/multiselectList.component.js
@@ -2,58 +2,58 @@ import template from './multiselectList.html';
import './multiselectList.scss';
angular.module('campusContactsApp').component('multiselectList', {
- controller: multiselectListController,
- bindings: {
- // list of objects that have an id and name
- options: '=',
-
- // dictionary with id keys and value of true/false/null
- originalSelection: '=',
- addedOutput: '=',
- removedOutput: '=',
- },
- template: template,
+ controller: multiselectListController,
+ bindings: {
+ // list of objects that have an id and name
+ options: '=',
+
+ // dictionary with id keys and value of true/false/null
+ originalSelection: '=',
+ addedOutput: '=',
+ removedOutput: '=',
+ },
+ template,
});
function multiselectListController(multiselectListService, _) {
- var vm = this;
-
- vm.toggle = toggle;
- vm.isSelected = isSelected;
- vm.isUnselected = isUnselected;
- vm.isIndeterminate = isIndeterminate;
-
- vm.$onInit = activate;
-
- function activate() {
- fillSelected();
-
- vm.addedOutput = vm.addedOutput || [];
- vm.removedOutput = vm.removedOutput || [];
- }
-
- function fillSelected() {
- vm.selected = _.clone(vm.originalSelection);
- }
-
- function toggle(id) {
- multiselectListService.toggle(id, {
- originalSelection: vm.originalSelection,
- currentSelection: vm.selected,
- addedOutput: vm.addedOutput,
- removedOutput: vm.removedOutput,
- });
- }
-
- function isSelected(id) {
- return multiselectListService.isSelected(vm.selected, id);
- }
-
- function isUnselected(id) {
- return multiselectListService.isUnselected(vm.selected, id);
- }
-
- function isIndeterminate(id) {
- return multiselectListService.isIndeterminate(vm.selected, id);
- }
+ const vm = this;
+
+ vm.toggle = toggle;
+ vm.isSelected = isSelected;
+ vm.isUnselected = isUnselected;
+ vm.isIndeterminate = isIndeterminate;
+
+ vm.$onInit = activate;
+
+ function activate() {
+ fillSelected();
+
+ vm.addedOutput = vm.addedOutput || [];
+ vm.removedOutput = vm.removedOutput || [];
+ }
+
+ function fillSelected() {
+ vm.selected = _.clone(vm.originalSelection);
+ }
+
+ function toggle(id) {
+ multiselectListService.toggle(id, {
+ originalSelection: vm.originalSelection,
+ currentSelection: vm.selected,
+ addedOutput: vm.addedOutput,
+ removedOutput: vm.removedOutput,
+ });
+ }
+
+ function isSelected(id) {
+ return multiselectListService.isSelected(vm.selected, id);
+ }
+
+ function isUnselected(id) {
+ return multiselectListService.isUnselected(vm.selected, id);
+ }
+
+ function isIndeterminate(id) {
+ return multiselectListService.isIndeterminate(vm.selected, id);
+ }
}
diff --git a/app/assets/javascripts/angular/components/multiselectList/multiselectList.html b/app/assets/javascripts/angular/components/multiselectList/multiselectList.html
index 51ea048ae5..d48963cc86 100644
--- a/app/assets/javascripts/angular/components/multiselectList/multiselectList.html
+++ b/app/assets/javascripts/angular/components/multiselectList/multiselectList.html
@@ -1,22 +1,14 @@
- -
-
+
- {{ entry.i18n ? (entry.i18n | t) : entry.name }}
-
-
-
-
+ ng-click="$ctrl.toggle(entry.id)"
+ >
+ {{ entry.i18n ? (entry.i18n | t) : entry.name }}
+
+
+
+
diff --git a/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.js b/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.js
index 5efe7add82..b0c845548a 100644
--- a/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.js
+++ b/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.js
@@ -1,93 +1,76 @@
-angular
- .module('campusContactsApp')
- .factory('multiselectListService', multiselectListService);
+angular.module('campusContactsApp').factory('multiselectListService', multiselectListService);
function multiselectListService(_) {
- /*
- * Throughout the multiselectListService API, a "selection" is a hash where the key is the option id and the
- * value is the option's selection state. That state is an enumeration where true represents selected, false
- * represents unselected, and null represents indeterminate.
- */
- var multiselectListService = {
- // Return a boolean indicating whether or not an option is selected
- isSelected: function (selection, id) {
- return selection[id] === true;
- },
+ /*
+ * Throughout the multiselectListService API, a "selection" is a hash where the key is the option id and the
+ * value is the option's selection state. That state is an enumeration where true represents selected, false
+ * represents unselected, and null represents indeterminate.
+ */
+ var multiselectListService = {
+ // Return a boolean indicating whether or not an option is selected
+ isSelected: function (selection, id) {
+ return selection[id] === true;
+ },
- // Return a boolean indicating whether or not an option is unselected
- isUnselected: function (selection, id) {
- var state = selection[id];
- return typeof state === 'undefined' || state === false;
- },
+ // Return a boolean indicating whether or not an option is unselected
+ isUnselected: function (selection, id) {
+ const state = selection[id];
+ return typeof state === 'undefined' || state === false;
+ },
- // Return a boolean indicating whether or not an option is in the indeterminate state
- isIndeterminate: function (selection, id) {
- return selection[id] === null;
- },
+ // Return a boolean indicating whether or not an option is in the indeterminate state
+ isIndeterminate: function (selection, id) {
+ return selection[id] === null;
+ },
- // Toggle the state of an option, taking indeterminate states into account
- // The listState is an object with an "originalSelection" field that specifies the initial selection state,
- // a "currentSelection" field that specifies the current selection state, an "addedOutput" field that is an
- // array of all the option ids added to the current selection versus the original selection, and a
- // "removedOutput" field that is an array of all the option ids removed from the current selection versus
- // the original selection.
- // This method will update "currentSelection", "addedOutput", and "removedOutput" as necessary, while
- // leaving "originalSelection" untouched.
- toggle: function (id, listState) {
- var originalSelection = listState.originalSelection;
- var currentSelection = listState.currentSelection;
- var addedOutput = listState.addedOutput;
- var removedOutput = listState.removedOutput;
+ // Toggle the state of an option, taking indeterminate states into account
+ // The listState is an object with an "originalSelection" field that specifies the initial selection state,
+ // a "currentSelection" field that specifies the current selection state, an "addedOutput" field that is an
+ // array of all the option ids added to the current selection versus the original selection, and a
+ // "removedOutput" field that is an array of all the option ids removed from the current selection versus
+ // the original selection.
+ // This method will update "currentSelection", "addedOutput", and "removedOutput" as necessary, while
+ // leaving "originalSelection" untouched.
+ toggle: function (id, listState) {
+ const originalSelection = listState.originalSelection;
+ const currentSelection = listState.currentSelection;
+ const addedOutput = listState.addedOutput;
+ const removedOutput = listState.removedOutput;
- var newState;
- if (multiselectListService.isSelected(currentSelection, id)) {
- // Selected toggles to unselected
- newState = false;
- } else if (
- multiselectListService.isUnselected(currentSelection, id)
- ) {
- // Unselected toggles to indeterminate if available and selected otherwise
- newState = multiselectListService.isIndeterminate(
- originalSelection,
- id,
- )
- ? null
- : true;
- } else if (
- multiselectListService.isIndeterminate(currentSelection, id)
- ) {
- // Indeterminate toggles to selected
- newState = true;
- }
+ let newState;
+ if (multiselectListService.isSelected(currentSelection, id)) {
+ // Selected toggles to unselected
+ newState = false;
+ } else if (multiselectListService.isUnselected(currentSelection, id)) {
+ // Unselected toggles to indeterminate if available and selected otherwise
+ newState = multiselectListService.isIndeterminate(originalSelection, id) ? null : true;
+ } else if (multiselectListService.isIndeterminate(currentSelection, id)) {
+ // Indeterminate toggles to selected
+ newState = true;
+ }
- currentSelection[id] = newState;
+ currentSelection[id] = newState;
- if (multiselectListService.isSelected(currentSelection, id)) {
- _.pull(removedOutput, id);
+ if (multiselectListService.isSelected(currentSelection, id)) {
+ _.pull(removedOutput, id);
- // don't add to added diff if it was one of the original selections
- if (!multiselectListService.isSelected(originalSelection, id)) {
- addedOutput.push(id);
- }
- } else if (
- multiselectListService.isUnselected(currentSelection, id)
- ) {
- _.pull(addedOutput, id);
+ // don't add to added diff if it was one of the original selections
+ if (!multiselectListService.isSelected(originalSelection, id)) {
+ addedOutput.push(id);
+ }
+ } else if (multiselectListService.isUnselected(currentSelection, id)) {
+ _.pull(addedOutput, id);
- // don't flag something to be removed if it wasn't there in the first place
- if (
- !multiselectListService.isUnselected(originalSelection, id)
- ) {
- removedOutput.push(id);
- }
- } else if (
- multiselectListService.isIndeterminate(currentSelection, id)
- ) {
- _.pull(removedOutput, id);
- _.pull(addedOutput, id);
- }
- },
- };
+ // don't flag something to be removed if it wasn't there in the first place
+ if (!multiselectListService.isUnselected(originalSelection, id)) {
+ removedOutput.push(id);
+ }
+ } else if (multiselectListService.isIndeterminate(currentSelection, id)) {
+ _.pull(removedOutput, id);
+ _.pull(addedOutput, id);
+ }
+ },
+ };
- return multiselectListService;
+ return multiselectListService;
}
diff --git a/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.spec.js b/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.spec.js
index 5c3dd27c67..2fa3719ffb 100644
--- a/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.spec.js
+++ b/app/assets/javascripts/angular/components/multiselectList/multiselectList.service.spec.js
@@ -1,220 +1,169 @@
import 'angular-mocks';
// Constants
-var multiselectListService, _;
+let multiselectListService, _;
describe('multiselectListService', function () {
- beforeEach(inject(function (_multiselectListService_, ___) {
- multiselectListService = _multiselectListService_;
- _ = ___;
- }));
-
- describe('state checks', function () {
- beforeEach(function () {
- this.id = 1;
- this.selected = { 1: true };
- this.unselected = { 1: false };
- this.indeterminate = { 1: null };
- this.missing = {};
- });
+ beforeEach(inject(function (_multiselectListService_, ___) {
+ multiselectListService = _multiselectListService_;
+ _ = ___;
+ }));
+
+ describe('state checks', function () {
+ beforeEach(function () {
+ this.id = 1;
+ this.selected = { 1: true };
+ this.unselected = { 1: false };
+ this.indeterminate = { 1: null };
+ this.missing = {};
+ });
- describe('isSelected', function () {
- it('should return true for selected states', function () {
- expect(
- multiselectListService.isSelected(this.selected, this.id),
- ).toBe(true);
- });
-
- it('should return false for other states', function () {
- expect(
- multiselectListService.isSelected(this.unselected, this.id),
- ).toBe(false);
- expect(
- multiselectListService.isSelected(
- this.indeterminate,
- this.id,
- ),
- ).toBe(false);
- expect(
- multiselectListService.isSelected(this.missing, this.id),
- ).toBe(false);
- });
- });
+ describe('isSelected', function () {
+ it('should return true for selected states', function () {
+ expect(multiselectListService.isSelected(this.selected, this.id)).toBe(true);
+ });
- describe('isUnselected', function () {
- it('should return true for unselected and missing states', function () {
- expect(
- multiselectListService.isUnselected(
- this.unselected,
- this.id,
- ),
- ).toBe(true);
- expect(
- multiselectListService.isUnselected(this.missing, this.id),
- ).toBe(true);
- });
-
- it('should return false for other states', function () {
- expect(
- multiselectListService.isUnselected(this.selected, this.id),
- ).toBe(false);
- expect(
- multiselectListService.isUnselected(
- this.indeterminate,
- this.id,
- ),
- ).toBe(false);
- });
- });
+ it('should return false for other states', function () {
+ expect(multiselectListService.isSelected(this.unselected, this.id)).toBe(false);
+ expect(multiselectListService.isSelected(this.indeterminate, this.id)).toBe(false);
+ expect(multiselectListService.isSelected(this.missing, this.id)).toBe(false);
+ });
+ });
- describe('isIndeterminate', function () {
- it('should return true for indeterminate states', function () {
- expect(
- multiselectListService.isIndeterminate(
- this.indeterminate,
- this.id,
- ),
- ).toBe(true);
- });
-
- it('should return false for other states', function () {
- expect(
- multiselectListService.isIndeterminate(
- this.selected,
- this.id,
- ),
- ).toBe(false);
- expect(
- multiselectListService.isIndeterminate(
- this.unselected,
- this.id,
- ),
- ).toBe(false);
- expect(
- multiselectListService.isIndeterminate(
- this.missing,
- this.id,
- ),
- ).toBe(false);
- });
- });
+ describe('isUnselected', function () {
+ it('should return true for unselected and missing states', function () {
+ expect(multiselectListService.isUnselected(this.unselected, this.id)).toBe(true);
+ expect(multiselectListService.isUnselected(this.missing, this.id)).toBe(true);
+ });
+
+ it('should return false for other states', function () {
+ expect(multiselectListService.isUnselected(this.selected, this.id)).toBe(false);
+ expect(multiselectListService.isUnselected(this.indeterminate, this.id)).toBe(false);
+ });
});
- describe('toggle', function () {
- var id = 1;
- var states = {
- selected: { 1: true },
- unselected: { 1: false },
- indeterminate: { 1: null },
- missing: {},
- };
- var output = {
- present: [id],
- absent: [],
- };
-
- var tests = [
- {
- initialSelection: 'selected',
- currentSelection: 'selected',
- addedOutput: 'absent',
- removedOutput: 'absent',
- expectedSelected: 'unselected',
- expectedAddedOutput: 'absent',
- expectedRemovedOutput: 'present',
- },
- {
- initialSelection: 'unselected',
- currentSelection: 'selected',
- addedOutput: 'present',
- removedOutput: 'absent',
- expectedSelected: 'unselected',
- expectedAddedOutput: 'absent',
- expectedRemovedOutput: 'absent',
- },
- {
- initialSelection: 'indeterminate',
- currentSelection: 'selected',
- addedOutput: 'present',
- removedOutput: 'absent',
- expectedSelected: 'unselected',
- expectedAddedOutput: 'absent',
- expectedRemovedOutput: 'present',
- },
-
- {
- initialSelection: 'selected',
- currentSelection: 'unselected',
- addedOutput: 'absent',
- removedOutput: 'present',
- expectedSelected: 'selected',
- expectedAddedOutput: 'absent',
- expectedRemovedOutput: 'absent',
- },
- {
- initialSelection: 'unselected',
- currentSelection: 'unselected',
- addedOutput: 'absent',
- removedOutput: 'absent',
- expectedSelected: 'selected',
- expectedAddedOutput: 'present',
- expectedRemovedOutput: 'absent',
- },
- {
- initialSelection: 'indeterminate',
- currentSelection: 'unselected',
- addedOutput: 'present',
- removedOutput: 'absent',
- expectedSelected: 'indeterminate',
- expectedAddedOutput: 'absent',
- expectedRemovedOutput: 'absent',
- },
-
- {
- initialSelection: 'indeterminate',
- currentSelection: 'indeterminate',
- addedOutput: 'absent',
- removedOutput: 'absent',
- expectedSelected: 'selected',
- expectedAddedOutput: 'present',
- expectedRemovedOutput: 'absent',
- },
-
- {
- initialSelection: 'missing',
- currentSelection: 'missing',
- addedOutput: 'absent',
- removedOutput: 'absent',
- expectedSelected: 'selected',
- expectedAddedOutput: 'present',
- expectedRemovedOutput: 'absent',
- },
- ];
-
- tests.forEach(function (test) {
- var testText =
- 'should toggle ' +
- test.currentSelection +
- ' state that was initially ' +
- test.initialSelection;
- it(testText, function () {
- var currentSelection = _.clone(states[test.currentSelection]);
- var addedOutput = _.clone(output[test.addedOutput]);
- var removedOutput = _.clone(output[test.removedOutput]);
-
- multiselectListService.toggle(id, {
- originalSelection: states[test.initialSelection],
- currentSelection: currentSelection,
- addedOutput: addedOutput,
- removedOutput: removedOutput,
- });
-
- expect(currentSelection).toEqual(states[test.expectedSelected]);
- expect(addedOutput).toEqual(output[test.expectedAddedOutput]);
- expect(removedOutput).toEqual(
- output[test.expectedRemovedOutput],
- );
- });
+ describe('isIndeterminate', function () {
+ it('should return true for indeterminate states', function () {
+ expect(multiselectListService.isIndeterminate(this.indeterminate, this.id)).toBe(true);
+ });
+
+ it('should return false for other states', function () {
+ expect(multiselectListService.isIndeterminate(this.selected, this.id)).toBe(false);
+ expect(multiselectListService.isIndeterminate(this.unselected, this.id)).toBe(false);
+ expect(multiselectListService.isIndeterminate(this.missing, this.id)).toBe(false);
+ });
+ });
+ });
+
+ describe('toggle', function () {
+ const id = 1;
+ const states = {
+ selected: { 1: true },
+ unselected: { 1: false },
+ indeterminate: { 1: null },
+ missing: {},
+ };
+ const output = {
+ present: [id],
+ absent: [],
+ };
+
+ const tests = [
+ {
+ initialSelection: 'selected',
+ currentSelection: 'selected',
+ addedOutput: 'absent',
+ removedOutput: 'absent',
+ expectedSelected: 'unselected',
+ expectedAddedOutput: 'absent',
+ expectedRemovedOutput: 'present',
+ },
+ {
+ initialSelection: 'unselected',
+ currentSelection: 'selected',
+ addedOutput: 'present',
+ removedOutput: 'absent',
+ expectedSelected: 'unselected',
+ expectedAddedOutput: 'absent',
+ expectedRemovedOutput: 'absent',
+ },
+ {
+ initialSelection: 'indeterminate',
+ currentSelection: 'selected',
+ addedOutput: 'present',
+ removedOutput: 'absent',
+ expectedSelected: 'unselected',
+ expectedAddedOutput: 'absent',
+ expectedRemovedOutput: 'present',
+ },
+
+ {
+ initialSelection: 'selected',
+ currentSelection: 'unselected',
+ addedOutput: 'absent',
+ removedOutput: 'present',
+ expectedSelected: 'selected',
+ expectedAddedOutput: 'absent',
+ expectedRemovedOutput: 'absent',
+ },
+ {
+ initialSelection: 'unselected',
+ currentSelection: 'unselected',
+ addedOutput: 'absent',
+ removedOutput: 'absent',
+ expectedSelected: 'selected',
+ expectedAddedOutput: 'present',
+ expectedRemovedOutput: 'absent',
+ },
+ {
+ initialSelection: 'indeterminate',
+ currentSelection: 'unselected',
+ addedOutput: 'present',
+ removedOutput: 'absent',
+ expectedSelected: 'indeterminate',
+ expectedAddedOutput: 'absent',
+ expectedRemovedOutput: 'absent',
+ },
+
+ {
+ initialSelection: 'indeterminate',
+ currentSelection: 'indeterminate',
+ addedOutput: 'absent',
+ removedOutput: 'absent',
+ expectedSelected: 'selected',
+ expectedAddedOutput: 'present',
+ expectedRemovedOutput: 'absent',
+ },
+
+ {
+ initialSelection: 'missing',
+ currentSelection: 'missing',
+ addedOutput: 'absent',
+ removedOutput: 'absent',
+ expectedSelected: 'selected',
+ expectedAddedOutput: 'present',
+ expectedRemovedOutput: 'absent',
+ },
+ ];
+
+ tests.forEach(function (test) {
+ const testText = 'should toggle ' + test.currentSelection + ' state that was initially ' + test.initialSelection;
+ it(testText, function () {
+ const currentSelection = _.clone(states[test.currentSelection]);
+ const addedOutput = _.clone(output[test.addedOutput]);
+ const removedOutput = _.clone(output[test.removedOutput]);
+
+ multiselectListService.toggle(id, {
+ originalSelection: states[test.initialSelection],
+ currentSelection,
+ addedOutput,
+ removedOutput,
});
+
+ expect(currentSelection).toEqual(states[test.expectedSelected]);
+ expect(addedOutput).toEqual(output[test.expectedAddedOutput]);
+ expect(removedOutput).toEqual(output[test.expectedRemovedOutput]);
+ });
});
+ });
});
diff --git a/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.component.js b/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.component.js
index c1df883a03..c0ee1fc1df 100644
--- a/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.component.js
+++ b/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.component.js
@@ -2,8 +2,8 @@ import template from './myOrganizationsDashboard.html';
import './myOrganizationsDashboard.scss';
angular.module('campusContactsApp').component('myOrganizationsDashboard', {
- template: template,
- controller: function ($state) {
- this.$state = $state;
- },
+ template,
+ controller: function ($state) {
+ this.$state = $state;
+ },
});
diff --git a/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.html b/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.html
index 3a8e2deada..12c7692301 100644
--- a/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.html
+++ b/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.html
@@ -1,6 +1,3 @@
-
-
+
+
diff --git a/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.service.js b/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.service.js
index c5cf61d468..e83af7ee90 100644
--- a/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.service.js
+++ b/app/assets/javascripts/angular/components/myOrganizationsDashboard/myOrganizationsDashboard.service.js
@@ -1,28 +1,23 @@
-angular
- .module('campusContactsApp')
- .factory(
- 'myOrganizationsDashboardService',
- myOrganizationsDashboardService,
- );
+angular.module('campusContactsApp').factory('myOrganizationsDashboardService', myOrganizationsDashboardService);
function myOrganizationsDashboardService(httpProxy, modelsService, _) {
- return {
- // Return an array of all loaded root organizations
- getRootOrganizations: function () {
- return httpProxy
- .get(
- modelsService.getModelMetadata('organization').url.all,
- {
- 'filters[user_created]': false,
- 'filters[descendants]': false,
- include: 'organizational_permissions',
- 'page[limit]': 100,
- },
- {
- errorMessage: 'error.messages.organization.loadAll',
- },
- )
- .then(httpProxy.extractModels);
- },
- };
+ return {
+ // Return an array of all loaded root organizations
+ getRootOrganizations: function () {
+ return httpProxy
+ .get(
+ modelsService.getModelMetadata('organization').url.all,
+ {
+ 'filters[user_created]': false,
+ 'filters[descendants]': false,
+ include: 'organizational_permissions',
+ 'page[limit]': 100,
+ },
+ {
+ errorMessage: 'error.messages.organization.loadAll',
+ },
+ )
+ .then(httpProxy.extractModels);
+ },
+ };
}
diff --git a/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.component.js b/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.component.js
index 02c6a04817..583f676b97 100644
--- a/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.component.js
+++ b/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.component.js
@@ -1,33 +1,27 @@
import template from './myOrganizationsDashboardList.html';
angular.module('campusContactsApp').component('myOrganizationsDashboardList', {
- template: template,
- bindings: {
- rootOrgs: '<',
- },
- controller: /* @ngInject */ function (userPreferencesService, $scope) {
- let deregisterEditOrganizationsEvent;
+ template,
+ bindings: {
+ rootOrgs: '<',
+ },
+ controller: /* @ngInject */ function (userPreferencesService, $scope) {
+ const deregisterEditOrganizationsEvent = $scope.$on('editOrganizations', (_, value) => {
+ this.editOrganizations = value;
+ });
- deregisterEditOrganizationsEvent = $scope.$on(
- 'editOrganizations',
- (event, value) => {
- this.editOrganizations = value;
- },
- );
+ const vm = this;
- var vm = this;
+ this.$onDestroy = () => {
+ deregisterEditOrganizationsEvent();
+ };
- this.$onDestroy = () => {
- deregisterEditOrganizationsEvent();
- };
-
- vm.sortableOptions = {
- handle: '.sort-orgs-handle',
- ghostClass: 'o-40',
- forceFallback: true, // Needed to make sticky header and scrollSensitivity work
- scrollSensitivity: 100,
- onEnd: () =>
- userPreferencesService.organizationOrderChange(vm.rootOrgs),
- };
- },
+ vm.sortableOptions = {
+ handle: '.sort-orgs-handle',
+ ghostClass: 'o-40',
+ forceFallback: true, // Needed to make sticky header and scrollSensitivity work
+ scrollSensitivity: 100,
+ onEnd: () => userPreferencesService.organizationOrderChange(vm.rootOrgs),
+ };
+ },
});
diff --git a/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.html b/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.html
index b1f5dba9d7..19de805635 100644
--- a/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.html
+++ b/app/assets/javascripts/angular/components/myOrganizationsDashboardList/myOrganizationsDashboardList.html
@@ -1,13 +1,13 @@
-
-
-
- {{ 'ministries.noMinistriesFound' | t }}
-
+
+
+
+ {{ 'ministries.noMinistriesFound' | t }}
+
diff --git a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.js b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.js
index f6a3a008e2..cdb3402cf9 100644
--- a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.js
+++ b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.js
@@ -8,202 +8,166 @@ import template from './myPeopleDashboard.html';
import './myPeopleDashboard.scss';
angular.module('campusContactsApp').component('myPeopleDashboard', {
- controller: myPeopleDashboardController,
- bindings: {
- editMode: '<',
- },
- template: template,
+ controller: myPeopleDashboardController,
+ bindings: {
+ editMode: '<',
+ },
+ template,
});
function myPeopleDashboardController(
- $scope,
- $log,
- $document,
- $window,
- JsonApiDataStore,
- _,
- myPeopleDashboardService,
- periodService,
- loggedInPerson,
- personService,
- reportsService,
- userPreferencesService,
+ $scope,
+ $log,
+ $document,
+ $window,
+ JsonApiDataStore,
+ _,
+ myPeopleDashboardService,
+ periodService,
+ loggedInPerson,
+ personService,
+ reportsService,
+ userPreferencesService,
) {
- var vm = this;
- vm.people = [];
- vm.organizations = [];
- vm.loading = true;
- vm.noPeople = false;
+ const vm = this;
+ vm.people = [];
+ vm.organizations = [];
+ vm.loading = true;
+ vm.noPeople = false;
- vm.favicon = favicon;
- vm.iosShare = iosShare;
+ vm.favicon = favicon;
+ vm.iosShare = iosShare;
- vm.sortableOptions = {
- handle: '.sort-orgs-handle',
- ghostClass: 'o-40',
- forceFallback: true, // Needed to make sticky header and scrollSensitivity work
- scrollSensitivity: 100,
- onEnd: () =>
- userPreferencesService.organizationOrderChange(vm.organizations),
- };
+ vm.sortableOptions = {
+ handle: '.sort-orgs-handle',
+ ghostClass: 'o-40',
+ forceFallback: true, // Needed to make sticky header and scrollSensitivity work
+ scrollSensitivity: 100,
+ onEnd: () => userPreferencesService.organizationOrderChange(vm.organizations),
+ };
- vm.$onInit = async () => {
- await loggedInPerson.loadOnce();
- await loadAndSyncData();
+ vm.$onInit = async () => {
+ await loggedInPerson.loadOnce();
+ await loadAndSyncData();
- angular.element($document).on('people::personAdded', loadAndSyncData);
- vm.toggleOrgVisibility =
- userPreferencesService.toggleOrganizationVisibility;
+ angular.element($document).on('people::personAdded', loadAndSyncData);
+ vm.toggleOrgVisibility = userPreferencesService.toggleOrganizationVisibility;
- vm.noPeopleWelcome = i18next.t('dashboard.no_contacts.welcome', {
- name: loggedInPerson.person.first_name.toUpperCase(),
- });
+ vm.noPeopleWelcome = i18next.t('dashboard.no_contacts.welcome', {
+ name: loggedInPerson.person.first_name.toUpperCase(),
+ });
- periodService.subscribe($scope, loadReports);
+ periodService.subscribe($scope, loadReports);
- $scope.$apply();
- };
+ $scope.$apply();
+ };
- vm.$onDestroy = cleanUp;
+ vm.$onDestroy = cleanUp;
- vm.noPeopleWelcome = '';
+ vm.noPeopleWelcome = '';
- vm.showSuggestLandscape = () =>
- // Yes this code can be removed after this date https://jira.cru.org/browse/MHP-3002?focusedCommentId=84408&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-84408
- moment().isBefore('2020-12-01') &&
- myPeopleDashboardService.isMobile() &&
- $window.matchMedia('(orientation: portrait)').matches &&
- !localStorage.getItem('hideSuggestLandscape');
+ vm.showSuggestLandscape = () =>
+ // Yes this code can be removed after this date https://jira.cru.org/browse/MHP-3002?focusedCommentId=84408&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-84408
+ moment().isBefore('2020-12-01') &&
+ myPeopleDashboardService.isMobile() &&
+ $window.matchMedia('(orientation: portrait)').matches &&
+ !localStorage.getItem('hideSuggestLandscape');
- vm.dismissSuggestLandscape = () =>
- localStorage.setItem('hideSuggestLandscape', true);
+ vm.dismissSuggestLandscape = () => localStorage.setItem('hideSuggestLandscape', true);
- vm.showIosAddToHomeScreen = () =>
- myPeopleDashboardService.isIos() &&
- !window.navigator.standalone &&
- !localStorage.getItem('hideIosAddToHomeScreen');
+ vm.showIosAddToHomeScreen = () =>
+ myPeopleDashboardService.isIos() && !window.navigator.standalone && !localStorage.getItem('hideIosAddToHomeScreen');
- vm.dismissIosAddToHomeScreen = () =>
- localStorage.setItem('hideIosAddToHomeScreen', true);
+ vm.dismissIosAddToHomeScreen = () => localStorage.setItem('hideIosAddToHomeScreen', true);
- function cleanUp() {
- angular.element($document).off('people::personAdded', loadAndSyncData);
- }
+ function cleanUp() {
+ angular.element($document).off('people::personAdded', loadAndSyncData);
+ }
- function loadAndSyncData() {
- var includes = [
- 'organizational_permissions',
- 'phone_numbers',
- 'email_addresses',
- ];
+ function loadAndSyncData() {
+ const includes = ['organizational_permissions', 'phone_numbers', 'email_addresses'];
- personService
- .getContactAssignments(loggedInPerson.person, null, includes)
- .then(dataLoaded);
- }
+ personService.getContactAssignments(loggedInPerson.person, null, includes).then(dataLoaded);
+ }
- function loadReports() {
- const people = vm.organizations.flatMap(({ people }) => people);
- const organizations = vm.organizations;
-
- const limitLength = 100;
- const warnLength = 25;
-
- if (people.length > limitLength || organizations.length > limitLength) {
- $log.error(
- `People dashboard tried to load more than ${limitLength} reports. Report ids truncated.`,
- {
- numPeopleIds: people.length,
- numCommunityIds: organizations.length,
- },
- );
- } else if (
- people.length > warnLength ||
- organizations.length > warnLength
- ) {
- $log.warn(
- `People dashboard loaded more than ${warnLength} reports.`,
- {
- numPeopleIds: people.length,
- numCommunityIds: organizations.length,
- },
- );
- }
-
- const limitedOrganizations = organizations.slice(0, limitLength);
- const limitedPeople = people.slice(0, limitLength);
-
- reportsService
- .loadOrganizationReports(limitedOrganizations)
- .catch(function (error) {
- $log.error('Error loading organization reports', error);
- });
-
- reportsService
- .loadMultiplePeopleReports(limitedOrganizations, limitedPeople)
- .catch(function (error) {
- $log.error('Error loading people reports', error);
- });
- }
+ function loadReports() {
+ const people = vm.organizations.flatMap(({ people }) => people);
+ const organizations = vm.organizations;
- function loadOrganizations() {
- myPeopleDashboardService
- .loadOrganizations({ 'page[limit]': 100 })
- .then(function (organizations) {
- vm.organizations = organizations;
-
- vm.organizations = userPreferencesService.applyUserOrgDisplayPreferences(
- vm.organizations,
- );
- loadReports();
- })
- .catch(function (error) {
- $log.error('Error loading organizations', error);
- });
- }
+ const limitLength = 100;
+ const warnLength = 25;
- function dataLoaded(assignmentsToMe) {
- var people = JsonApiDataStore.store.findAll('person');
- people.forEach(function (person) {
- if (_.isNil(person.last_name)) {
- person.last_name = '';
- }
- });
-
- // Get the array of all the organizations that have at least one person assigned to me
- vm.organizations = _.chain(assignmentsToMe)
- .map('organization')
- .uniq()
- .value();
-
- vm.organizations.forEach(function (organization) {
- // Get an array of the people assigned to me on this organization
- organization.people = _.filter(assignmentsToMe, {
- organization: organization,
- }).map(function (assignment) {
- return JsonApiDataStore.store.find(
- 'person',
- assignment.person_id,
- );
- });
- });
-
- vm.organizations = userPreferencesService.applyUserOrgDisplayPreferences(
- vm.organizations,
- );
-
- vm.collapsible =
- people.length > 10 || _.keys(vm.organizations).length > 1;
-
- if (_.keys(vm.organizations).length === 0) {
- noPeople();
- }
- loadReports();
- vm.loading = false;
+ if (people.length > limitLength || organizations.length > limitLength) {
+ $log.error(`People dashboard tried to load more than ${limitLength} reports. Report ids truncated.`, {
+ numPeopleIds: people.length,
+ numCommunityIds: organizations.length,
+ });
+ } else if (people.length > warnLength || organizations.length > warnLength) {
+ $log.warn(`People dashboard loaded more than ${warnLength} reports.`, {
+ numPeopleIds: people.length,
+ numCommunityIds: organizations.length,
+ });
}
- function noPeople() {
- vm.noPeople = true;
- loadOrganizations();
+ const limitedOrganizations = organizations.slice(0, limitLength);
+ const limitedPeople = people.slice(0, limitLength);
+
+ reportsService.loadOrganizationReports(limitedOrganizations).catch(function (error) {
+ $log.error('Error loading organization reports', error);
+ });
+
+ reportsService.loadMultiplePeopleReports(limitedOrganizations, limitedPeople).catch(function (error) {
+ $log.error('Error loading people reports', error);
+ });
+ }
+
+ function loadOrganizations() {
+ myPeopleDashboardService
+ .loadOrganizations({ 'page[limit]': 100 })
+ .then(function (organizations) {
+ vm.organizations = organizations;
+
+ vm.organizations = userPreferencesService.applyUserOrgDisplayPreferences(vm.organizations);
+ loadReports();
+ })
+ .catch(function (error) {
+ $log.error('Error loading organizations', error);
+ });
+ }
+
+ function dataLoaded(assignmentsToMe) {
+ const people = JsonApiDataStore.store.findAll('person');
+ people.forEach(function (person) {
+ if (_.isNil(person.last_name)) {
+ person.last_name = '';
+ }
+ });
+
+ // Get the array of all the organizations that have at least one person assigned to me
+ vm.organizations = _.chain(assignmentsToMe).map('organization').uniq().value();
+
+ vm.organizations.forEach(function (organization) {
+ // Get an array of the people assigned to me on this organization
+ organization.people = _.filter(assignmentsToMe, {
+ organization,
+ }).map(function (assignment) {
+ return JsonApiDataStore.store.find('person', assignment.person_id);
+ });
+ });
+
+ vm.organizations = userPreferencesService.applyUserOrgDisplayPreferences(vm.organizations);
+
+ vm.collapsible = people.length > 10 || _.keys(vm.organizations).length > 1;
+
+ if (_.keys(vm.organizations).length === 0) {
+ noPeople();
}
+ loadReports();
+ vm.loading = false;
+ }
+
+ function noPeople() {
+ vm.noPeople = true;
+ loadOrganizations();
+ }
}
diff --git a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.spec.js b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.spec.js
index 8a715144c0..263af40feb 100644
--- a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.spec.js
+++ b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.component.spec.js
@@ -1,40 +1,36 @@
import 'angular-mocks';
// Constants
-var $controller, myPeopleDashboardService, $scope, loggedInPerson;
+let $controller, myPeopleDashboardService, $scope, loggedInPerson;
describe('myPeopleDashboard Components Tests', function () {
- beforeEach(inject(function (
- $rootScope,
- $componentController,
- _loggedInPerson_,
- ) {
- $scope = $rootScope.$new();
- loggedInPerson = _loggedInPerson_;
+ beforeEach(inject(function ($rootScope, $componentController, _loggedInPerson_) {
+ $scope = $rootScope.$new();
+ loggedInPerson = _loggedInPerson_;
- spyOn(loggedInPerson, 'person').andReturn({ first_name: 'John' });
+ $controller = $componentController(
+ 'myPeopleDashboard',
+ { $scope },
+ { myBinding: { period: '<', editMode: '<' } },
+ loggedInPerson,
+ );
+ myPeopleDashboardService = jasmine.createSpyObj('myPeopleDashboardService', ['loadPeople', 'loadReports']);
+ }));
- $controller = $componentController(
- 'myPeopleDashboard',
- { $scope: $scope },
- { myBinding: { period: '<', editMode: '<' } },
- loggedInPerson,
- );
+ describe('Components.Controller', function () {
+ it('should exist', function () {
+ expect($controller).toBeDefined();
+ });
- myPeopleDashboardService = jasmine.createSpyObj(
- 'myPeopleDashboardService',
- ['loadPeople', 'loadReports', 'loadOrganizations'],
- );
+ it('loadPeople should have been called', function () {
+ myPeopleDashboardService.loadPeople();
+ expect(myPeopleDashboardService.loadPeople).toHaveBeenCalled();
+ });
+ });
- describe('Components.Controller', function () {
- it('should exist', function () {
- expect($controller).toBeDefined();
- });
-
- it('loadPeople should have been called', function () {
- myPeopleDashboardService.loadPeople();
- expect(myPeopleDashboardService.loadPeople).toHaveBeenCalled();
- });
- });
- }));
+ describe('People Tests', function () {
+ it('should contain loadPeople', function () {
+ expect(myPeopleDashboardService.loadPeople).toBeDefined();
+ });
+ });
});
diff --git a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.html b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.html
index 243dff3700..04f1781c83 100644
--- a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.html
+++ b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.html
@@ -1,101 +1,74 @@
-
- {{ 'dashboard.loading_contacts' | t }}
-
-
-
{{ $ctrl.noPeopleWelcome }}
-
- {{ 'dashboard.no_contacts_help.part1' | t }}
-
- {{ 'dashboard.no_contacts_help.part2' | t }}
-
-
+
{{ 'dashboard.loading_contacts' | t }}
+
+
{{ $ctrl.noPeopleWelcome }}
+
+ {{ 'dashboard.no_contacts_help.part1' | t }}
+
+ {{ 'dashboard.no_contacts_help.part2' | t }}
+
+
-
-
-
-
-
-
- {{ 'dashboard.suggestLandscape.suggestion' | t }}
-
-
- {{ 'dashboard.suggestLandscape.dismiss' | t }}
-
+
+
+
+
+
+
+
+ {{ 'dashboard.suggestLandscape.suggestion' | t }}
+ {{ 'dashboard.suggestLandscape.dismiss' | t }}
-
-
-
-
-
- {{ 'dashboard.suggestIosAddToHomeScreen.title' | t }}
-
-
- {{ 'dashboard.suggestIosAddToHomeScreen.description' | t }}
-
-
-
- {{ 'dashboard.suggestIosAddToHomeScreen.actionPart1' | t }}
-
-
-
- {{ 'dashboard.suggestIosAddToHomeScreen.actionPart2' | t }}
-
-
-
+
+
+
+
+
+
{{ 'dashboard.suggestIosAddToHomeScreen.title' | t }}
+
{{ 'dashboard.suggestIosAddToHomeScreen.description' | t }}
+
+
{{ 'dashboard.suggestIosAddToHomeScreen.actionPart1' | t }}
+
+
{{ 'dashboard.suggestIosAddToHomeScreen.actionPart2' | t }}
+
+
diff --git a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.js b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.js
index 1185f3afe7..e91fada62d 100644
--- a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.js
+++ b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.js
@@ -1,48 +1,37 @@
-angular
- .module('campusContactsApp')
- .factory('myPeopleDashboardService', myPeopleDashboardService);
+angular.module('campusContactsApp').factory('myPeopleDashboardService', myPeopleDashboardService);
function myPeopleDashboardService(httpProxy, modelsService, _) {
- var myPeopleDashboardService = {
- isMobile: () =>
- window.navigator.userAgent.match(
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i,
- ),
+ const myPeopleDashboardService = {
+ isMobile: () => window.navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),
- isIos: () => window.navigator.userAgent.match(/iPhone|iPad|iPod/i),
+ isIos: () => window.navigator.userAgent.match(/iPhone|iPad|iPod/i),
- loadPeople: function (params) {
- return httpProxy.get(
- modelsService.getModelMetadata('person').url.all,
- params || {},
- {
- errorMessage:
- 'error.messages.my_people_dashboard.load_people',
- },
- );
- },
+ loadPeople: function (params) {
+ return httpProxy.get(modelsService.getModelMetadata('person').url.all, params || {}, {
+ errorMessage: 'error.messages.my_people_dashboard.load_people',
+ });
+ },
- loadOrganizations: function (params) {
- return httpProxy
- .get(
- modelsService.getModelMetadata('organization').url.all,
- _.extend(
- {
- sort: '-active_people_count',
- include: '',
- 'page[limit]': 100,
- 'filters[user_created]': false,
- },
- params,
- ),
- {
- errorMessage:
- 'error.messages.my_people_dashboard.load_orgs',
- },
- )
- .then(httpProxy.extractModels);
- },
- };
+ loadOrganizations: function (params) {
+ return httpProxy
+ .get(
+ modelsService.getModelMetadata('organization').url.all,
+ _.extend(
+ {
+ sort: '-active_people_count',
+ include: '',
+ 'page[limit]': 100,
+ 'filters[user_created]': false,
+ },
+ params,
+ ),
+ {
+ errorMessage: 'error.messages.my_people_dashboard.load_orgs',
+ },
+ )
+ .then(httpProxy.extractModels);
+ },
+ };
- return myPeopleDashboardService;
+ return myPeopleDashboardService;
}
diff --git a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.spec.js b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.spec.js
index 4007670532..d6a683ee7e 100644
--- a/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.spec.js
+++ b/app/assets/javascripts/angular/components/myPeopleDashboard/myPeopleDashboard.service.spec.js
@@ -1,149 +1,130 @@
import 'angular-mocks';
// Constants
-var myPeopleDashboardService, httpProxy, $rootScope, $q, JsonApiDataStore;
+let myPeopleDashboardService, httpProxy, $rootScope, $q, JsonApiDataStore;
function async(fn) {
- return function (done) {
- var returnValue = fn.call(this, done);
- returnValue
- .then(function () {
- done();
- })
- .catch(function (err) {
- done.fail(err);
- });
- $rootScope.$apply();
- return returnValue;
- };
+ return function (done) {
+ const returnValue = fn.call(this, done);
+ returnValue
+ .then(function () {
+ done();
+ })
+ .catch(function (err) {
+ done.fail(err);
+ });
+ $rootScope.$apply();
+ return returnValue;
+ };
}
describe('myPeopleDashboardService Tests', function () {
- beforeEach(inject(function (
- _$q_,
- _$rootScope_,
- _myPeopleDashboardService_,
- _httpProxy_,
- _JsonApiDataStore_,
- ) {
- var _this = this;
-
- $q = _$q_;
- $rootScope = _$rootScope_;
- httpProxy = _httpProxy_;
- JsonApiDataStore = _JsonApiDataStore_;
- myPeopleDashboardService = _myPeopleDashboardService_;
-
- this.person = {
- personId: 123,
- };
-
- this.people = [
- {
- personId: 123,
- },
- {
- personId: 234,
- },
- ];
-
- this.organizationReports = {
- reportId: 123,
- };
-
- this.loadOrganizationParams = {
- 'page[limit]': 100,
- sort: '-active_people_count',
- include: '',
- 'filters[user_created]': false,
- };
-
- spyOn(httpProxy, 'callHttp').and.callFake(function () {
- return $q.resolve(_this.httpResponse);
- });
+ beforeEach(inject(function (_$q_, _$rootScope_, _myPeopleDashboardService_, _httpProxy_, _JsonApiDataStore_) {
+ const _this = this;
- spyOn(JsonApiDataStore.store, 'sync').and.returnValue(
- _this.httpResponse,
- );
- }));
- describe('People Tests', function () {
- it('should contain loadPeople', function () {
- expect(myPeopleDashboardService.loadPeople).toBeDefined();
- });
+ $q = _$q_;
+ $rootScope = _$rootScope_;
+ httpProxy = _httpProxy_;
+ JsonApiDataStore = _JsonApiDataStore_;
+ myPeopleDashboardService = _myPeopleDashboardService_;
- it('should call GET loadPeople URL', function () {
- var params = {
- 'page[limit]': 250,
- include:
- 'phone_numbers,email_addresses,reverse_contact_assignments.organization,' +
- 'organizational_permissions',
- 'filters[assigned_tos]': 'me',
- };
- myPeopleDashboardService.loadPeople(params);
- expect(httpProxy.callHttp).toHaveBeenCalledWith(
- 'GET',
- jasmine.any(String),
- params,
- null,
- jasmine.objectContaining({ errorMessage: jasmine.any(String) }),
- );
- });
+ this.person = {
+ personId: 123,
+ };
- it('should load people', async(function () {
- this.httpResponse = this.people;
-
- var _this = this;
- return myPeopleDashboardService
- .loadPeople()
- .then(function (loadedPeople) {
- expect(loadedPeople).toEqual(_this.people);
- });
- }));
-
- it('should sync people JsonApiDataStore', async(function () {
- this.httpResponse = this.people;
-
- var _this = this;
- JsonApiDataStore.store.sync('people', this.people);
- return myPeopleDashboardService.loadPeople().then(function () {
- expect(JsonApiDataStore.store.sync).toHaveBeenCalledWith(
- 'people',
- _this.people,
- );
- });
- }));
+ this.people = [
+ {
+ personId: 123,
+ },
+ {
+ personId: 234,
+ },
+ ];
+
+ this.organizationReports = {
+ reportId: 123,
+ };
+
+ this.loadOrganizationParams = {
+ 'page[limit]': 100,
+ sort: '-active_people_count',
+ include: '',
+ 'filters[user_created]': false,
+ };
+
+ spyOn(httpProxy, 'callHttp').and.callFake(function () {
+ return $q.resolve(_this.httpResponse);
});
- describe('Organization loading', function () {
- it('should contain load Organization', function () {
- expect(myPeopleDashboardService.loadOrganizations).toBeDefined();
- });
+ spyOn(JsonApiDataStore.store, 'sync').and.returnValue(_this.httpResponse);
+ }));
+ describe('People Tests', function () {
+ it('should contain loadPeople', function () {
+ expect(myPeopleDashboardService.loadPeople).toBeDefined();
+ });
- it('should call GET loadOrganization URL', function () {
- myPeopleDashboardService.loadOrganizations(
- this.loadOrganizationParams,
- );
- expect(httpProxy.callHttp).toHaveBeenCalledWith(
- 'GET',
- jasmine.any(String),
- this.loadOrganizationParams,
- null,
- jasmine.objectContaining({ errorMessage: jasmine.any(String) }),
- );
- });
+ it('should call GET loadPeople URL', function () {
+ const params = {
+ 'page[limit]': 250,
+ include: 'phone_numbers,email_addresses,reverse_contact_assignments.organization,organizational_permissions',
+ 'filters[assigned_tos]': 'me',
+ };
+ myPeopleDashboardService.loadPeople(params);
+ expect(httpProxy.callHttp).toHaveBeenCalledWith(
+ 'GET',
+ jasmine.any(String),
+ params,
+ null,
+ jasmine.objectContaining({ errorMessage: jasmine.any(String) }),
+ );
+ });
+
+ it('should load people', async(function () {
+ this.httpResponse = this.people;
+
+ const _this = this;
+ return myPeopleDashboardService.loadPeople().then(function (loadedPeople) {
+ expect(loadedPeople).toEqual(_this.people);
+ });
+ }));
+
+ it('should sync people JsonApiDataStore', async(function () {
+ this.httpResponse = this.people;
- it('should load Organization', async(function () {
- this.httpResponse = { data: this.organizationReports };
+ const _this = this;
+ JsonApiDataStore.store.sync('people', this.people);
+ return myPeopleDashboardService.loadPeople().then(function () {
+ expect(JsonApiDataStore.store.sync).toHaveBeenCalledWith('people', _this.people);
+ });
+ }));
+ });
- var _this = this;
+ describe('Organization loading', function () {
+ it('should contain load Organization', function () {
+ expect(myPeopleDashboardService.loadOrganizations).toBeDefined();
+ });
- return myPeopleDashboardService
- .loadOrganizations(this.loadOrganizationParams)
- .then(function (loadedOrganization) {
- expect(loadedOrganization).toEqual(
- _this.organizationReports,
- );
- });
- }));
+ it('should call GET loadOrganization URL', function () {
+ myPeopleDashboardService.loadOrganizations(this.loadOrganizationParams);
+ expect(httpProxy.callHttp).toHaveBeenCalledWith(
+ 'GET',
+ jasmine.any(String),
+ this.loadOrganizationParams,
+ null,
+ jasmine.objectContaining({ errorMessage: jasmine.any(String) }),
+ );
});
+
+ it('should load Organization', async(function () {
+ this.httpResponse = { data: this.organizationReports };
+
+ const _this = this;
+
+ return myPeopleDashboardService
+ .loadOrganizations(this.loadOrganizationParams)
+ .then(function (loadedOrganization) {
+ expect(loadedOrganization).toEqual(_this.organizationReports);
+ });
+ }));
+ });
});
diff --git a/app/assets/javascripts/angular/components/orgManagement/orgManagement.component.js b/app/assets/javascripts/angular/components/orgManagement/orgManagement.component.js
index 9f1fe7f17e..b7c3155484 100644
--- a/app/assets/javascripts/angular/components/orgManagement/orgManagement.component.js
+++ b/app/assets/javascripts/angular/components/orgManagement/orgManagement.component.js
@@ -2,11 +2,11 @@ import template from './orgManagement.html';
import './orgManagement.scss';
angular.module('campusContactsApp').component('orgManagement', {
- controller: orgManagementController,
- require: {
- organizationOverview: '^',
- },
- template: template,
+ controller: orgManagementController,
+ require: {
+ organizationOverview: '^',
+ },
+ template,
});
function orgManagementController() {}
diff --git a/app/assets/javascripts/angular/components/orgManagement/orgManagement.html b/app/assets/javascripts/angular/components/orgManagement/orgManagement.html
index 3706771983..6db590323a 100644
--- a/app/assets/javascripts/angular/components/orgManagement/orgManagement.html
+++ b/app/assets/javascripts/angular/components/orgManagement/orgManagement.html
@@ -1,12 +1,7 @@
-
- {{ 'common:organizations.manage' | t }}
-
+
{{ 'common:organizations.manage' | t }}
-
-
-
+
+
+
diff --git a/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.component.js b/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.component.js
index 6715b41d2a..527ce568ff 100644
--- a/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.component.js
+++ b/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.component.js
@@ -3,39 +3,39 @@ import helpIcon from '../../../../images/icons/icon-help.svg';
import template from './orgManagementEdit.html';
angular.module('campusContactsApp').component('orgManagementEdit', {
- controller: orgManagementEditController,
- bindings: {
- org: '<',
- new: '<',
- editComplete: '&',
- },
- template: template,
+ controller: orgManagementEditController,
+ bindings: {
+ org: '<',
+ new: '<',
+ editComplete: '&',
+ },
+ template,
});
function orgManagementEditController(organizationService) {
- this.helpIcon = helpIcon;
+ this.helpIcon = helpIcon;
- this.$onInit = () => {
- if (this.new) {
- this.orgEdit = {
- show_sub_orgs: true,
- };
- } else {
- this.orgEdit = { ...this.org };
- }
- };
+ this.$onInit = () => {
+ if (this.new) {
+ this.orgEdit = {
+ show_sub_orgs: true,
+ };
+ } else {
+ this.orgEdit = { ...this.org };
+ }
+ };
- this.save = () => {
- if (this.new) {
- organizationService.createOrg(this.orgEdit, this.org).then(() => {
- this.editComplete({ orgId: this.org.id, refresh: true });
- });
- } else {
- organizationService.saveOrg(this.orgEdit).then(() => {
- this.org = this.orgEdit;
+ this.save = () => {
+ if (this.new) {
+ organizationService.createOrg(this.orgEdit, this.org).then(() => {
+ this.editComplete({ orgId: this.org.id, refresh: true });
+ });
+ } else {
+ organizationService.saveOrg(this.orgEdit).then(() => {
+ this.org = this.orgEdit;
- this.editComplete({ orgId: this.org.id });
- });
- }
- };
+ this.editComplete({ orgId: this.org.id });
+ });
+ }
+ };
}
diff --git a/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.html b/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.html
index 6a27b2d9bf..958bc3a524 100644
--- a/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.html
+++ b/app/assets/javascripts/angular/components/orgManagement/orgManagementEdit.html
@@ -1,96 +1,78 @@
-
- {{ $ctrl.new ? ('common:organizations.new' | t) :
- ('common:organizations.edit' | t) }}
-
-