Commit a1375e043a9e6338cb200416ace8e6ce04fe3c5c
Exists in
master
and in
6 other branches
Merge branch 'tasks' into 'master'
Add components that display tasks and allow accept/reject See merge request !55
Showing
41 changed files
with
1001 additions
and
30 deletions
Show diff stats
src/app/known-events.ts
1 | import { EventsHubKnownEventNames } from './shared/services/events-hub.service'; | 1 | import { EventsHubKnownEventNames } from './shared/services/events-hub.service'; |
2 | 2 | ||
3 | -export class NoosferoKnownEvents implements EventsHubKnownEventNames { | 3 | +export class NoosferoKnownEvents implements EventsHubKnownEventNames { |
4 | IMAGE_PROFILE_UPDATED: string = 'IMAGE_PROFILE_UPDATED'; | 4 | IMAGE_PROFILE_UPDATED: string = 'IMAGE_PROFILE_UPDATED'; |
5 | PROFILE_INFO_UPDATED: string = 'PROFILE_INFO_UPDATED'; | 5 | PROFILE_INFO_UPDATED: string = 'PROFILE_INFO_UPDATED'; |
6 | ARTICLE_UPDATED: string = 'ARTICLE_UPDATED'; | 6 | ARTICLE_UPDATED: string = 'ARTICLE_UPDATED'; |
7 | + TASK_CLOSED: string = 'TASK_CLOSED'; | ||
7 | 8 | ||
8 | constructor() { | 9 | constructor() { |
9 | } | 10 | } |
@@ -11,4 +12,4 @@ export class NoosferoKnownEvents implements EventsHubKnownEventNames { | @@ -11,4 +12,4 @@ export class NoosferoKnownEvents implements EventsHubKnownEventNames { | ||
11 | getNames() { | 12 | getNames() { |
12 | return Object.getOwnPropertyNames(this); | 13 | return Object.getOwnPropertyNames(this); |
13 | } | 14 | } |
14 | -} | ||
15 | \ No newline at end of file | 15 | \ No newline at end of file |
16 | +} |
src/app/layout/navbar/navbar.html
@@ -45,10 +45,12 @@ | @@ -45,10 +45,12 @@ | ||
45 | </ul> | 45 | </ul> |
46 | </li> | 46 | </li> |
47 | </ul> | 47 | </ul> |
48 | - | ||
49 | <ul class="nav navbar-nav navbar-right"> | 48 | <ul class="nav navbar-nav navbar-right"> |
50 | <language-selector class="nav navbar-nav navbar-right"></language-selector> | 49 | <language-selector class="nav navbar-nav navbar-right"></language-selector> |
51 | </ul> | 50 | </ul> |
51 | + <ul class="nav navbar-nav navbar-right"> | ||
52 | + <tasks-menu class="nav navbar-nav navbar-right"></tasks-menu> | ||
53 | + </ul> | ||
52 | <div ui-view="actions"></div> | 54 | <div ui-view="actions"></div> |
53 | <div class="nav navbar-nav search navbar-right"> | 55 | <div class="nav navbar-nav search navbar-right"> |
54 | <search-form></search-form> | 56 | <search-form></search-form> |
src/app/main/main.component.spec.ts
@@ -40,7 +40,8 @@ describe("MainComponent", function () { | @@ -40,7 +40,8 @@ describe("MainComponent", function () { | ||
40 | useValue: [ | 40 | useValue: [ |
41 | 'IMAGE_PROFILE_UPDATED', | 41 | 'IMAGE_PROFILE_UPDATED', |
42 | 'PROFILE_INFO_UPDATED', | 42 | 'PROFILE_INFO_UPDATED', |
43 | - 'ARTICLE_UPDATED' | 43 | + 'ARTICLE_UPDATED', |
44 | + 'TASK_CLOSED' | ||
44 | ] | 45 | ] |
45 | }), | 46 | }), |
46 | ] | 47 | ] |
src/app/main/main.component.ts
@@ -49,9 +49,11 @@ import { HtmlEditorComponent } from "../shared/components/html-editor/html-edito | @@ -49,9 +49,11 @@ import { HtmlEditorComponent } from "../shared/components/html-editor/html-edito | ||
49 | import { PermissionDirective } from "../shared/components/permission/permission.directive"; | 49 | import { PermissionDirective } from "../shared/components/permission/permission.directive"; |
50 | import { SearchComponent } from "../search/search.component"; | 50 | import { SearchComponent } from "../search/search.component"; |
51 | import { SearchFormComponent } from "../search/search-form/search-form.component"; | 51 | import { SearchFormComponent } from "../search/search-form/search-form.component"; |
52 | - | ||
53 | import { EVENTS_HUB_KNOW_EVENT_NAMES, EventsHubService } from "../shared/services/events-hub.service"; | 52 | import { EVENTS_HUB_KNOW_EVENT_NAMES, EventsHubService } from "../shared/services/events-hub.service"; |
54 | import { NoosferoKnownEvents } from "../known-events"; | 53 | import { NoosferoKnownEvents } from "../known-events"; |
54 | +import { TasksMenuComponent } from "../task/tasks-menu/tasks-menu.component"; | ||
55 | +import { TaskListComponent } from "../task/task-list/task-list.component"; | ||
56 | + | ||
55 | /** | 57 | /** |
56 | * @ngdoc controller | 58 | * @ngdoc controller |
57 | * @name main.MainContentComponent | 59 | * @name main.MainContentComponent |
@@ -118,16 +120,16 @@ export class EnvironmentContent { | @@ -118,16 +120,16 @@ export class EnvironmentContent { | ||
118 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, | 120 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, |
119 | LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, | 121 | LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, |
120 | PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, | 122 | PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, |
121 | - ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent | 123 | + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent |
122 | ].concat(plugins.mainComponents).concat(plugins.hotspots), | 124 | ].concat(plugins.mainComponents).concat(plugins.hotspots), |
123 | - providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, RegisterService, | 125 | + providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, |
124 | "ngAnimate", "ngCookies", "ngStorage", "ngTouch", | 126 | "ngAnimate", "ngCookies", "ngStorage", "ngTouch", |
125 | "ngSanitize", "ngMessages", "ngAria", "restangular", | 127 | "ngSanitize", "ngMessages", "ngAria", "restangular", |
126 | "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", | 128 | "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", |
127 | "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", | 129 | "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", |
128 | "angular-timeline", "duScroll", "oitozero.ngSweetAlert", | 130 | "angular-timeline", "duScroll", "oitozero.ngSweetAlert", |
129 | "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", | 131 | "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", |
130 | - "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch", "ngPassword"] | 132 | + "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch"] |
131 | }) | 133 | }) |
132 | @StateConfig([ | 134 | @StateConfig([ |
133 | { | 135 | { |
@@ -148,6 +150,7 @@ export class EnvironmentContent { | @@ -148,6 +150,7 @@ export class EnvironmentContent { | ||
148 | url: '/', | 150 | url: '/', |
149 | component: EnvironmentComponent, | 151 | component: EnvironmentComponent, |
150 | name: 'main.environment', | 152 | name: 'main.environment', |
153 | + abstract: true, | ||
151 | views: { | 154 | views: { |
152 | "content": { | 155 | "content": { |
153 | templateUrl: "app/environment/environment.html", | 156 | templateUrl: "app/environment/environment.html", |
src/app/profile/profile.component.ts
1 | -import {StateConfig, Component, Inject, provide} from 'ng-forward'; | ||
2 | -import {ProfileInfoComponent} from './info/profile-info.component'; | ||
3 | -import {ProfileHomeComponent} from './profile-home.component'; | ||
4 | -import {BasicEditorComponent} from '../article/cms/basic-editor/basic-editor.component'; | ||
5 | -import {CmsComponent} from '../article/cms/cms.component'; | ||
6 | -import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component"; | ||
7 | -import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component"; | ||
8 | -import {ActivitiesComponent} from "./activities/activities.component"; | ||
9 | -import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; | ||
10 | -import {NotificationService} from "../shared/services/notification.service"; | ||
11 | -import {MyProfileComponent} from "./myprofile.component"; | ||
12 | -import {ProfileActionsComponent} from "./profile-actions.component"; | ||
13 | -import {ConfigBarComponent} from "./config-bar.component"; | 1 | +import { StateConfig, Component, Inject, provide } from 'ng-forward'; |
2 | +import { ProfileInfoComponent } from './info/profile-info.component'; | ||
3 | +import { ProfileHomeComponent } from './profile-home.component'; | ||
4 | +import { BasicEditorComponent } from '../article/cms/basic-editor/basic-editor.component'; | ||
5 | +import { CmsComponent } from '../article/cms/cms.component'; | ||
6 | +import { ContentViewerComponent } from "../article/content-viewer/content-viewer.component"; | ||
7 | +import { ContentViewerActionsComponent } from "../article/content-viewer/content-viewer-actions.component"; | ||
8 | +import { ActivitiesComponent } from "./activities/activities.component"; | ||
9 | +import { ProfileService } from "../../lib/ng-noosfero-api/http/profile.service"; | ||
10 | +import { NotificationService } from "../shared/services/notification.service"; | ||
11 | +import { MyProfileComponent } from "./myprofile.component"; | ||
12 | +import { ProfileActionsComponent } from "./profile-actions.component"; | ||
13 | +import { ConfigBarComponent } from "./config-bar.component"; | ||
14 | +import { TasksComponent } from "../task/tasks/tasks.component"; | ||
15 | + | ||
14 | /** | 16 | /** |
15 | * @ngdoc controller | 17 | * @ngdoc controller |
16 | * @name profile.Profile | 18 | * @name profile.Profile |
@@ -92,6 +94,18 @@ import {ConfigBarComponent} from "./config-bar.component"; | @@ -92,6 +94,18 @@ import {ConfigBarComponent} from "./config-bar.component"; | ||
92 | } | 94 | } |
93 | }, | 95 | }, |
94 | { | 96 | { |
97 | + name: 'main.profile.tasks', | ||
98 | + url: "^/myprofile/:profile/tasks", | ||
99 | + component: TasksComponent, | ||
100 | + views: { | ||
101 | + "mainBlockContent": { | ||
102 | + templateUrl: "app/task/tasks/tasks.html", | ||
103 | + controller: TasksComponent, | ||
104 | + controllerAs: "vm" | ||
105 | + } | ||
106 | + } | ||
107 | + }, | ||
108 | + { | ||
95 | name: 'main.profile.home', | 109 | name: 'main.profile.home', |
96 | url: "", | 110 | url: "", |
97 | component: ProfileHomeComponent, | 111 | component: ProfileHomeComponent, |
src/app/shared/services/events-hub.service.ts
@@ -31,13 +31,13 @@ export class EventsHubService { | @@ -31,13 +31,13 @@ export class EventsHubService { | ||
31 | emitEvent(eventType: string, payload?: any) { | 31 | emitEvent(eventType: string, payload?: any) { |
32 | this.checkKnownEvent(eventType); | 32 | this.checkKnownEvent(eventType); |
33 | let event = this.emitters.get(eventType); | 33 | let event = this.emitters.get(eventType); |
34 | - if ( event ) this.emitters.get(eventType).next(payload); | 34 | + if (event) this.emitters.get(eventType).next(payload); |
35 | } | 35 | } |
36 | 36 | ||
37 | subscribeToEvent<T>(eventType: string, generatorOrNext?: ((p?: T) => void), error?: any, complete?: any) { | 37 | subscribeToEvent<T>(eventType: string, generatorOrNext?: ((p?: T) => void), error?: any, complete?: any) { |
38 | this.checkKnownEvent(eventType); | 38 | this.checkKnownEvent(eventType); |
39 | let event = this.emitters.get(eventType); | 39 | let event = this.emitters.get(eventType); |
40 | - if (event) event.subscribe(generatorOrNext, error, complete); | 40 | + if (event) event.subscribe(generatorOrNext, error, complete); |
41 | } | 41 | } |
42 | 42 | ||
43 | private setupEmitters() { | 43 | private setupEmitters() { |
@@ -53,4 +53,4 @@ export class EventsHubService { | @@ -53,4 +53,4 @@ export class EventsHubService { | ||
53 | } | 53 | } |
54 | 54 | ||
55 | 55 | ||
56 | -} | ||
57 | \ No newline at end of file | 56 | \ No newline at end of file |
57 | +} |
@@ -0,0 +1,10 @@ | @@ -0,0 +1,10 @@ | ||
1 | +<div class="task-accept task-confirmation"> | ||
2 | + <div class="accept-title confirmation-title">{{"tasks.actions.accept.confirmation.title" | translate}}</div> | ||
3 | + <div class="accept-fields confirmation-details"> | ||
4 | + <task-accept ng-if="ctrl.currentTask.accept_details" [task]="ctrl.currentTask" [confirmation-task]="ctrl.confirmationTask"></task-accept> | ||
5 | + </div> | ||
6 | + <div class="actions"> | ||
7 | + <button type="submit" class="btn btn-default" ng-click="ctrl.callAccept()">{{"tasks.actions.confirmation.yes" | translate}}</button> | ||
8 | + <button type="button" class="btn btn-warning" ng-click="ctrl.cancel()">{{"tasks.actions.confirmation.cancel" | translate}}</button> | ||
9 | + </div> | ||
10 | +</div> |
@@ -0,0 +1,10 @@ | @@ -0,0 +1,10 @@ | ||
1 | +<div class="task-reject task-confirmation"> | ||
2 | + <div class="reject-title confirmation-title">{{"tasks.actions.reject.confirmation.title" | translate}}</div> | ||
3 | + <div class="reject-fields confirmation-details"> | ||
4 | + <input ng-model="ctrl.confirmationTask.reject_explanation" id="rejectionExplanationInput" type="text" placeholder="{{'tasks.actions.reject.explanation.label' | translate}}" class="rejection-explanation form-control"> | ||
5 | + </div> | ||
6 | + <div class="actions"> | ||
7 | + <button type="submit" class="btn btn-default" ng-click="ctrl.callReject()">{{"tasks.actions.confirmation.yes" | translate}}</button> | ||
8 | + <button type="button" class="btn btn-warning" ng-click="ctrl.cancel()">{{"tasks.actions.confirmation.cancel" | translate}}</button> | ||
9 | + </div> | ||
10 | +</div> |
@@ -0,0 +1,33 @@ | @@ -0,0 +1,33 @@ | ||
1 | +import { Provider, provide, Component } from 'ng-forward'; | ||
2 | +import * as helpers from "../../../spec/helpers"; | ||
3 | +import { TaskAcceptComponent } from './task-accept.component'; | ||
4 | + | ||
5 | +const htmlTemplate: string = '<task-accept [task]="ctrl.task"></task-accept>'; | ||
6 | + | ||
7 | +describe("Components", () => { | ||
8 | + describe("Task Accept Component", () => { | ||
9 | + | ||
10 | + let task = { id: 1, type: "AddMember" }; | ||
11 | + let roleService = jasmine.createSpyObj("roleService", ["getByProfile"]); | ||
12 | + | ||
13 | + beforeEach(angular.mock.module("templates")); | ||
14 | + | ||
15 | + function createComponent() { | ||
16 | + return helpers.quickCreateComponent({ | ||
17 | + template: htmlTemplate, | ||
18 | + directives: [TaskAcceptComponent], | ||
19 | + properties: { task: task }, | ||
20 | + providers: [ | ||
21 | + helpers.createProviderToValue("RoleService", roleService) | ||
22 | + ].concat(helpers.provideFilters("translateFilter")) | ||
23 | + }); | ||
24 | + } | ||
25 | + | ||
26 | + it("replace element with the specific task accept component", (done: Function) => { | ||
27 | + createComponent().then(fixture => { | ||
28 | + expect(fixture.debugElement.queryAll("add-member-task-accept").length).toBe(1); | ||
29 | + done(); | ||
30 | + }); | ||
31 | + }); | ||
32 | + }); | ||
33 | +}); |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +import { Input, Inject, Component } from 'ng-forward'; | ||
2 | +import { AddMemberTaskAcceptComponent } from "../types/add-member/add-member-task-accept.component"; | ||
3 | + | ||
4 | +@Component({ | ||
5 | + selector: 'task-accept', | ||
6 | + template: '<div></div>', | ||
7 | + directives: [AddMemberTaskAcceptComponent] | ||
8 | +}) | ||
9 | +@Inject("$element", "$scope", "$injector", "$compile") | ||
10 | +export class TaskAcceptComponent { | ||
11 | + | ||
12 | + @Input() task: noosfero.Task; | ||
13 | + @Input() confirmationTask: noosfero.Task; | ||
14 | + | ||
15 | + ngOnInit() { | ||
16 | + let componentName = this.task.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); | ||
17 | + componentName += "-task-accept"; | ||
18 | + this.$element.replaceWith(this.$compile(`<${componentName} [task]="ctrl.task" [confirmation-task]="ctrl.confirmationTask"></${componentName}>`)(this.$scope)); | ||
19 | + } | ||
20 | + | ||
21 | + constructor(private $element: any, private $scope: ng.IScope, private $injector: ng.auto.IInjectorService, private $compile: ng.ICompileService) { } | ||
22 | +} |
@@ -0,0 +1,106 @@ | @@ -0,0 +1,106 @@ | ||
1 | +import { Provider, provide, Component } from 'ng-forward'; | ||
2 | +import * as helpers from "../../../spec/helpers"; | ||
3 | +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper'; | ||
4 | +import { TaskListComponent } from './task-list.component'; | ||
5 | + | ||
6 | +const htmlTemplate: string = '<task-list [task]="ctrl.task"></task-list>'; | ||
7 | + | ||
8 | +describe("Components", () => { | ||
9 | + describe("Task List Component", () => { | ||
10 | + | ||
11 | + let helper: ComponentTestHelper<TaskListComponent>; | ||
12 | + let taskService = jasmine.createSpyObj("taskService", ["getAllPending"]); | ||
13 | + let tasks = [{ id: 1 }, { id: 2 }]; | ||
14 | + let modal = helpers.mocks.$modal; | ||
15 | + let eventsHubService = jasmine.createSpyObj("eventsHubService", ["subscribeToEvent", "emitEvent"]); | ||
16 | + taskService.getAllPending = jasmine.createSpy("getAllPending").and.returnValue(Promise.resolve({ headers: () => { }, data: tasks })); | ||
17 | + | ||
18 | + beforeEach(angular.mock.module("templates")); | ||
19 | + | ||
20 | + beforeEach((done) => { | ||
21 | + let cls = createClass({ | ||
22 | + template: htmlTemplate, | ||
23 | + directives: [TaskListComponent], | ||
24 | + providers: [ | ||
25 | + helpers.createProviderToValue("TaskService", taskService), | ||
26 | + helpers.createProviderToValue("EventsHubService", eventsHubService), | ||
27 | + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService), | ||
28 | + helpers.createProviderToValue('$uibModal', modal), | ||
29 | + ].concat(helpers.provideFilters("groupByFilter")), | ||
30 | + properties: { tasks: tasks } | ||
31 | + }); | ||
32 | + helper = new ComponentTestHelper<TaskListComponent>(cls, done); | ||
33 | + }); | ||
34 | + | ||
35 | + it("return specific template for a task", () => { | ||
36 | + let task = { type: "AddMember" }; | ||
37 | + expect(helper.component.getTaskTemplate(<any>task)).toEqual("app/task/types/add-member/add-member.html"); | ||
38 | + }); | ||
39 | + | ||
40 | + it("return the default template for a task", () => { | ||
41 | + let task = { type: "" }; | ||
42 | + expect(helper.component.getTaskTemplate(<any>task)).toEqual("app/task/types/default.html"); | ||
43 | + }); | ||
44 | + | ||
45 | + it("open confirmation modal when it has details to accept a task", () => { | ||
46 | + let task = { accept_details: true }; | ||
47 | + helper.component.accept(<any>task); | ||
48 | + expect(modal.open).toHaveBeenCalled(); | ||
49 | + }); | ||
50 | + | ||
51 | + it("open confirmation modal when it has details to reject a task", () => { | ||
52 | + let task = { reject_details: true }; | ||
53 | + helper.component.reject(<any>task); | ||
54 | + expect(modal.open).toHaveBeenCalled(); | ||
55 | + }); | ||
56 | + | ||
57 | + it("call api directly when it has no details to accept a task", () => { | ||
58 | + let task = { accept_details: false }; | ||
59 | + helper.component.callAccept = jasmine.createSpy("callAccept"); | ||
60 | + helper.component.accept(<any>task); | ||
61 | + expect(helper.component.callAccept).toHaveBeenCalled(); | ||
62 | + }); | ||
63 | + | ||
64 | + it("call api directly when it has no details to reject a task", () => { | ||
65 | + let task = { accept_details: false }; | ||
66 | + helper.component.callReject = jasmine.createSpy("callReject"); | ||
67 | + helper.component.reject(<any>task); | ||
68 | + expect(helper.component.callReject).toHaveBeenCalled(); | ||
69 | + }); | ||
70 | + | ||
71 | + it("call cancel and emit event when accept was called successfully", () => { | ||
72 | + helper.component.currentTask = <any>{ id: 1 }; | ||
73 | + let result = helpers.mocks.promiseResultTemplate({ data: { id: 1 } }); | ||
74 | + taskService.closeTask = jasmine.createSpy("closeTask").and.returnValue(result); | ||
75 | + helper.component.cancel = jasmine.createSpy("cancel"); | ||
76 | + helper.component.callAccept(); | ||
77 | + expect(helper.component.cancel).toHaveBeenCalled(); | ||
78 | + expect((<any>helper.component)['eventsHubService'].emitEvent).toHaveBeenCalled(); | ||
79 | + }); | ||
80 | + | ||
81 | + it("call cancel and emit event when reject was called successfully", () => { | ||
82 | + helper.component.currentTask = <any>{ id: 1 }; | ||
83 | + let result = helpers.mocks.promiseResultTemplate({ data: { id: 1 } }); | ||
84 | + taskService.closeTask = jasmine.createSpy("closeTask").and.returnValue(result); | ||
85 | + helper.component.cancel = jasmine.createSpy("cancel"); | ||
86 | + helper.component.callReject(); | ||
87 | + expect(helper.component.cancel).toHaveBeenCalled(); | ||
88 | + expect((<any>helper.component)['eventsHubService'].emitEvent).toHaveBeenCalled(); | ||
89 | + }); | ||
90 | + | ||
91 | + it("reset currentTask and close modal when call cancel", () => { | ||
92 | + let modalInstance = jasmine.createSpyObj("modalInstance", ["close"]); | ||
93 | + helper.component["modalInstance"] = modalInstance; | ||
94 | + helper.component.currentTask = <any>{ id: 1 }; | ||
95 | + helper.component.cancel(); | ||
96 | + expect(modalInstance.close).toHaveBeenCalled(); | ||
97 | + expect(helper.component.currentTask).toBeNull(); | ||
98 | + }); | ||
99 | + | ||
100 | + it("not fail when call cancel with no modalInstance", () => { | ||
101 | + helper.component["modalInstance"] = null; | ||
102 | + helper.component.currentTask = null; | ||
103 | + helper.component.cancel(); | ||
104 | + }); | ||
105 | + }); | ||
106 | +}); |
@@ -0,0 +1,107 @@ | @@ -0,0 +1,107 @@ | ||
1 | +import { Component, Input, Inject, provide } from "ng-forward"; | ||
2 | +import { NotificationService } from "../../shared/services/notification.service"; | ||
3 | +import { TaskService } from "../../../lib/ng-noosfero-api/http/task.service"; | ||
4 | +import { TaskAcceptComponent } from "./task-accept.component"; | ||
5 | +import { Arrays } from "../../../lib/util/arrays"; | ||
6 | +import { EventsHubService } from "../../shared/services/events-hub.service"; | ||
7 | +import { NoosferoKnownEvents } from "../../known-events"; | ||
8 | + | ||
9 | +@Component({ | ||
10 | + selector: "task-list", | ||
11 | + templateUrl: "app/task/task-list/task-list.html", | ||
12 | + directives: [TaskAcceptComponent], | ||
13 | + providers: [ | ||
14 | + provide('eventsHubService', { useClass: EventsHubService }) | ||
15 | + ] | ||
16 | +}) | ||
17 | +@Inject(NotificationService, "$scope", "$uibModal", TaskService, EventsHubService) | ||
18 | +export class TaskListComponent { | ||
19 | + | ||
20 | + @Input() tasks: noosfero.Task[]; | ||
21 | + | ||
22 | + private taskTemplates = ["AddFriend", "AddMember", "CreateCommunity", "SuggestArticle", "AbuseComplaint"]; | ||
23 | + | ||
24 | + currentTask: noosfero.Task; | ||
25 | + confirmationTask: noosfero.Task; | ||
26 | + eventsNames: NoosferoKnownEvents; | ||
27 | + private modalInstance: any = null; | ||
28 | + | ||
29 | + constructor(private notificationService: NotificationService, | ||
30 | + private $scope: ng.IScope, | ||
31 | + private $uibModal: any, | ||
32 | + private taskService: TaskService, | ||
33 | + private eventsHubService: EventsHubService) { | ||
34 | + | ||
35 | + this.eventsNames = new NoosferoKnownEvents(); | ||
36 | + } | ||
37 | + | ||
38 | + ngOnInit() { | ||
39 | + this.eventsHubService.subscribeToEvent(this.eventsNames.TASK_CLOSED, (task: noosfero.Task) => { | ||
40 | + Arrays.remove(this.tasks, task); | ||
41 | + }); | ||
42 | + } | ||
43 | + | ||
44 | + getTaskTemplate(task: noosfero.Task) { | ||
45 | + if (this.taskTemplates.indexOf(task.type) >= 0) { | ||
46 | + let templateName = this.getTemplateName(task); | ||
47 | + return `app/task/types/${templateName}/${templateName}.html`; | ||
48 | + } else { | ||
49 | + return 'app/task/types/default.html'; | ||
50 | + } | ||
51 | + } | ||
52 | + | ||
53 | + accept(task: noosfero.Task) { | ||
54 | + this.closeTask(task, task.accept_details, "app/task/task-list/accept.html", () => { this.callAccept(); }); | ||
55 | + } | ||
56 | + | ||
57 | + reject(task: noosfero.Task) { | ||
58 | + this.closeTask(task, task.reject_details, "app/task/task-list/reject.html", () => { this.callReject(); }); | ||
59 | + } | ||
60 | + | ||
61 | + private closeTask(task: noosfero.Task, hasDetails: boolean, templateUrl: string, confirmationFunction: Function) { | ||
62 | + this.currentTask = task; | ||
63 | + this.confirmationTask = <any>{ id: task.id }; | ||
64 | + if (hasDetails) { | ||
65 | + this.modalInstance = this.$uibModal.open({ | ||
66 | + templateUrl: templateUrl, | ||
67 | + controller: TaskListComponent, | ||
68 | + controllerAs: 'modal', | ||
69 | + bindToController: true, | ||
70 | + scope: this.$scope | ||
71 | + }); | ||
72 | + } else { | ||
73 | + confirmationFunction(); | ||
74 | + } | ||
75 | + } | ||
76 | + | ||
77 | + callAccept() { | ||
78 | + this.callCloseTask("finish", "tasks.actions.accept.title", "tasks.actions.accept.message"); | ||
79 | + } | ||
80 | + | ||
81 | + callReject() { | ||
82 | + this.callCloseTask("cancel", "tasks.actions.reject.title", "tasks.actions.reject.message"); | ||
83 | + } | ||
84 | + | ||
85 | + private callCloseTask(action: string, title: string, message: string) { | ||
86 | + this.taskService.closeTask(this.confirmationTask, action).then(() => { | ||
87 | + this.eventsHubService.emitEvent(this.eventsNames.TASK_CLOSED, this.currentTask); | ||
88 | + this.notificationService.success({ title: title, message: message }); | ||
89 | + }).finally(() => { | ||
90 | + this.cancel(); | ||
91 | + }); | ||
92 | + } | ||
93 | + | ||
94 | + cancel() { | ||
95 | + if (this.modalInstance) { | ||
96 | + this.modalInstance.close(); | ||
97 | + this.modalInstance = null; | ||
98 | + } | ||
99 | + this.currentTask = null; | ||
100 | + this.confirmationTask = null; | ||
101 | + } | ||
102 | + | ||
103 | + private getTemplateName(task: noosfero.Task) { | ||
104 | + return task.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); | ||
105 | + } | ||
106 | + | ||
107 | +} |
@@ -0,0 +1,24 @@ | @@ -0,0 +1,24 @@ | ||
1 | +<ul class="task-list"> | ||
2 | + <li class="task-group" ng-repeat="(target, tasks) in ctrl.tasks | groupBy: 'target.name'"> | ||
3 | + <div class="task-target"> | ||
4 | + <noosfero-profile-image ng-if="tasks[0].target.type" [profile]="tasks[0].target"></noosfero-profile-image> | ||
5 | + <div class="target-name">{{target}}</div> | ||
6 | + </div> | ||
7 | + <div class="task-body" ng-repeat="task in tasks | orderBy: 'created_at':true"> | ||
8 | + <div class="task"> | ||
9 | + <ng-include src="ctrl.getTaskTemplate(task)"></ng-include> | ||
10 | + </div> | ||
11 | + <div class="actions"> | ||
12 | + <a href="#" ng-if="!task.accept_disabled" ng-click="ctrl.accept(task)" class="accept" uib-tooltip="{{'tasks.actions.accept' | translate}}"> | ||
13 | + <i class="fa fa-check"></i> | ||
14 | + </a> | ||
15 | + <a href="#" ng-if="!task.reject_disabled" ng-click="ctrl.reject(task)" class="reject" uib-tooltip="{{'tasks.actions.reject' | translate}}"> | ||
16 | + <i class="fa fa-close"></i> | ||
17 | + </a> | ||
18 | + </div> | ||
19 | + <span class="time"> | ||
20 | + <span class="bullet-separator">•</span> <span am-time-ago="task.created_at | dateFormat"></span> | ||
21 | + </span> | ||
22 | + </div> | ||
23 | + </li> | ||
24 | +</ul> |
@@ -0,0 +1,86 @@ | @@ -0,0 +1,86 @@ | ||
1 | +$task-action-accept-color: #77c123; | ||
2 | +$task-action-reject-color: #d64e18; | ||
3 | + | ||
4 | +.task-list { | ||
5 | + width: 100%; | ||
6 | + padding: 0; | ||
7 | + list-style-type: none; | ||
8 | + .task-group { | ||
9 | + border-top: 1px solid #f3f3f3; | ||
10 | + padding: 4px 16px 8px 16px; | ||
11 | + } | ||
12 | + .task-target { | ||
13 | + margin-top: 12px; | ||
14 | + .profile-image { | ||
15 | + color: #2c3e50; | ||
16 | + font-size: 25px; | ||
17 | + width: 25px; | ||
18 | + display: inline-block; | ||
19 | + @extend .img-rounded; | ||
20 | + } | ||
21 | + .target-name { | ||
22 | + display: inline-block; | ||
23 | + margin-left: 10px; | ||
24 | + font-size: 18px; | ||
25 | + font-weight: bold; | ||
26 | + } | ||
27 | + } | ||
28 | + .task-body { | ||
29 | + margin-left: 35px; | ||
30 | + padding: 2px; | ||
31 | + .task { | ||
32 | + display: inline-block; | ||
33 | + color: #949494; | ||
34 | + .task-icon { | ||
35 | + font-size: 18px; | ||
36 | + color: #e84e40; | ||
37 | + } | ||
38 | + .requestor, .target { | ||
39 | + font-style: italic; | ||
40 | + color: #676767; | ||
41 | + } | ||
42 | + } | ||
43 | + .actions { | ||
44 | + display: inline-block; | ||
45 | + font-size: 19px; | ||
46 | + a { | ||
47 | + text-decoration: none; | ||
48 | + } | ||
49 | + .accept { | ||
50 | + color: $task-action-accept-color; | ||
51 | + &:hover { | ||
52 | + color: darken($task-action-accept-color, 10%); | ||
53 | + } | ||
54 | + } | ||
55 | + .reject { | ||
56 | + color: $task-action-reject-color; | ||
57 | + &:hover { | ||
58 | + color: darken($task-action-reject-color, 10%); | ||
59 | + } | ||
60 | + } | ||
61 | + } | ||
62 | + .time { | ||
63 | + color: #c1c1c1; | ||
64 | + font-size: 12px; | ||
65 | + .bullet-separator { | ||
66 | + font-size: 10px; | ||
67 | + color: #d1d1d1; | ||
68 | + } | ||
69 | + } | ||
70 | + } | ||
71 | +} | ||
72 | +.task-confirmation { | ||
73 | + @extend .form-group; | ||
74 | + .confirmation-details { | ||
75 | + margin-bottom: 20px; | ||
76 | + } | ||
77 | + .confirmation-title { | ||
78 | + text-align: center; | ||
79 | + font-size: 30px; | ||
80 | + font-weight: bold; | ||
81 | + margin-bottom: 20px; | ||
82 | + } | ||
83 | + .actions { | ||
84 | + text-align: center; | ||
85 | + } | ||
86 | +} |
@@ -0,0 +1,44 @@ | @@ -0,0 +1,44 @@ | ||
1 | +import { Provider, provide, Component } from 'ng-forward'; | ||
2 | +import * as helpers from "../../../spec/helpers"; | ||
3 | +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper'; | ||
4 | +import { TasksMenuComponent } from './tasks-menu.component'; | ||
5 | +import { AuthEvents } from "./../../login"; | ||
6 | + | ||
7 | +const htmlTemplate: string = '<tasks-menu></tasks-menu>'; | ||
8 | + | ||
9 | +describe("Components", () => { | ||
10 | + describe("Task Menu Component", () => { | ||
11 | + | ||
12 | + let helper: ComponentTestHelper<TasksMenuComponent>; | ||
13 | + let taskService = jasmine.createSpyObj("taskService", ["getAllPending"]); | ||
14 | + let tasks = [{ id: 1 }, { id: 2 }]; | ||
15 | + let eventsHubService = jasmine.createSpyObj("eventsHubService", ["subscribeToEvent", "emitEvent"]); | ||
16 | + taskService.getAllPending = jasmine.createSpy("getAllPending").and.returnValue(Promise.resolve({ headers: () => { }, data: tasks })); | ||
17 | + | ||
18 | + beforeEach(angular.mock.module("templates")); | ||
19 | + | ||
20 | + beforeEach((done) => { | ||
21 | + let cls = createClass({ | ||
22 | + template: htmlTemplate, | ||
23 | + directives: [TasksMenuComponent], | ||
24 | + providers: [ | ||
25 | + helpers.createProviderToValue("TaskService", taskService), | ||
26 | + helpers.createProviderToValue("EventsHubService", eventsHubService), | ||
27 | + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})) | ||
28 | + ] | ||
29 | + }); | ||
30 | + helper = new ComponentTestHelper<TasksMenuComponent>(cls, done); | ||
31 | + }); | ||
32 | + | ||
33 | + it("load person tasks", () => { | ||
34 | + expect(taskService.getAllPending).toHaveBeenCalled(); | ||
35 | + }); | ||
36 | + | ||
37 | + it("load person tasks when receive a login event", () => { | ||
38 | + helper.component.loadTasks = jasmine.createSpy("loadTasks"); | ||
39 | + helper.component.ngOnInit(); | ||
40 | + (<any>helper.component['authService'])[AuthEvents[AuthEvents.loginSuccess]].next({}); | ||
41 | + expect(helper.component.loadTasks).toHaveBeenCalled(); | ||
42 | + }); | ||
43 | + }); | ||
44 | +}); |
@@ -0,0 +1,46 @@ | @@ -0,0 +1,46 @@ | ||
1 | +import { Component, Inject } from "ng-forward"; | ||
2 | +import { TaskService } from "../../../lib/ng-noosfero-api/http/task.service"; | ||
3 | +import { AuthService, SessionService, AuthEvents } from "./../../login"; | ||
4 | +import { EventsHubService } from "../../shared/services/events-hub.service"; | ||
5 | +import { NoosferoKnownEvents } from "../../known-events"; | ||
6 | + | ||
7 | +@Component({ | ||
8 | + selector: "tasks-menu", | ||
9 | + templateUrl: "app/task/tasks-menu/tasks-menu.html" | ||
10 | +}) | ||
11 | +@Inject(TaskService, SessionService, AuthService, EventsHubService) | ||
12 | +export class TasksMenuComponent { | ||
13 | + | ||
14 | + tasks: noosfero.Task[]; | ||
15 | + total: number; | ||
16 | + perPage = 5; | ||
17 | + person: noosfero.Person; | ||
18 | + eventsNames: NoosferoKnownEvents; | ||
19 | + | ||
20 | + constructor(private taskService: TaskService, | ||
21 | + private session: SessionService, | ||
22 | + private authService: AuthService, | ||
23 | + private eventsHubService: EventsHubService) { | ||
24 | + | ||
25 | + this.eventsNames = new NoosferoKnownEvents(); | ||
26 | + } | ||
27 | + | ||
28 | + ngOnInit() { | ||
29 | + this.eventsHubService.subscribeToEvent(this.eventsNames.TASK_CLOSED, (task: noosfero.Task) => { | ||
30 | + this.total--; | ||
31 | + }); | ||
32 | + this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => { | ||
33 | + this.loadTasks(); | ||
34 | + }); | ||
35 | + this.loadTasks(); | ||
36 | + } | ||
37 | + | ||
38 | + loadTasks() { | ||
39 | + if (!this.session.currentUser()) return; | ||
40 | + this.person = this.session.currentUser().person; | ||
41 | + this.taskService.getAllPending({ per_page: this.perPage }).then((result: noosfero.RestResult<noosfero.Task[]>) => { | ||
42 | + this.total = result.headers('total'); | ||
43 | + this.tasks = result.data; | ||
44 | + }); | ||
45 | + } | ||
46 | +} |
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | +<li class="btn-nav tasks-menu" uib-dropdown ng-show="ctrl.total > 0" ng-if="ctrl.total"> | ||
2 | + <a href="#" uib-dropdown-toggle> | ||
3 | + <i class="fa fa-bell-o fa-fw fa-2x"></i> | ||
4 | + <span class="badge">{{ctrl.total}}</span> | ||
5 | + </a> | ||
6 | + <div class="dropdown-menu task-panel" uib-dropdown-menu> | ||
7 | + <div class="task-menu-header">{{"tasks.menu.header" | translate:{tasks: ctrl.total}:"messageformat"}}</div> | ||
8 | + <task-list [tasks]="ctrl.tasks"></task-list> | ||
9 | + <a ui-sref="main.profile.tasks({profile: ctrl.person.identifier})" class="all-tasks btn btn-default btn-xs">{{"tasks.menu.all" | translate}}</a> | ||
10 | + </div> | ||
11 | +</li> |
@@ -0,0 +1,29 @@ | @@ -0,0 +1,29 @@ | ||
1 | +tasks-menu { | ||
2 | + .tasks-menu { | ||
3 | + position: relative; | ||
4 | + margin-right: 0; | ||
5 | + .badge { | ||
6 | + position: absolute; | ||
7 | + top: 1px; | ||
8 | + right: 8px; | ||
9 | + border-radius: 6px; | ||
10 | + background-color: #e84e40; | ||
11 | + color: #fff; | ||
12 | + } | ||
13 | + .all-tasks { | ||
14 | + text-align: center; | ||
15 | + width: 100%; | ||
16 | + display: block; | ||
17 | + line-height: 25px; | ||
18 | + } | ||
19 | + .task-panel { | ||
20 | + width: 550px; | ||
21 | + padding: 0; | ||
22 | + } | ||
23 | + .task-menu-header { | ||
24 | + text-align: center; | ||
25 | + padding: 7px; | ||
26 | + font-weight: bold; | ||
27 | + } | ||
28 | + } | ||
29 | +} |
@@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
1 | +import { Provider, provide, Component } from 'ng-forward'; | ||
2 | +import * as helpers from "../../../spec/helpers"; | ||
3 | +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper'; | ||
4 | +import { TasksComponent } from './tasks.component'; | ||
5 | +import { AuthEvents } from "./../../login"; | ||
6 | + | ||
7 | +const htmlTemplate: string = '<tasks></tasks>'; | ||
8 | + | ||
9 | +describe("Components", () => { | ||
10 | + describe("Task Menu Component", () => { | ||
11 | + | ||
12 | + let helper: ComponentTestHelper<TasksComponent>; | ||
13 | + let taskService = jasmine.createSpyObj("taskService", ["getAllPending"]); | ||
14 | + let tasks = [{ id: 1 }, { id: 2 }]; | ||
15 | + taskService.getAllPending = jasmine.createSpy("getAllPending").and.returnValue(Promise.resolve({ headers: () => { }, data: tasks })); | ||
16 | + | ||
17 | + beforeEach(angular.mock.module("templates")); | ||
18 | + | ||
19 | + beforeEach((done) => { | ||
20 | + let cls = createClass({ | ||
21 | + template: htmlTemplate, | ||
22 | + directives: [TasksComponent], | ||
23 | + providers: [ | ||
24 | + helpers.createProviderToValue("TaskService", taskService) | ||
25 | + ] | ||
26 | + }); | ||
27 | + helper = new ComponentTestHelper<TasksComponent>(cls, done); | ||
28 | + }); | ||
29 | + | ||
30 | + it("load person tasks", () => { | ||
31 | + expect(taskService.getAllPending).toHaveBeenCalled(); | ||
32 | + }); | ||
33 | + }); | ||
34 | +}); |
@@ -0,0 +1,30 @@ | @@ -0,0 +1,30 @@ | ||
1 | +import { Component, Inject, provide } from "ng-forward"; | ||
2 | +import { TaskService } from "../../../lib/ng-noosfero-api/http/task.service"; | ||
3 | + | ||
4 | +@Component({ | ||
5 | + selector: "tasks", | ||
6 | + templateUrl: "app/task/tasks/tasks.html", | ||
7 | + providers: [ | ||
8 | + provide('taskService', { useClass: TaskService }) | ||
9 | + ] | ||
10 | +}) | ||
11 | +@Inject(TaskService) | ||
12 | +export class TasksComponent { | ||
13 | + | ||
14 | + tasks: noosfero.Task[]; | ||
15 | + total: number; | ||
16 | + currentPage: number; | ||
17 | + perPage = 5; | ||
18 | + | ||
19 | + constructor(private taskService: TaskService) { | ||
20 | + this.loadPage(); | ||
21 | + } | ||
22 | + | ||
23 | + loadPage() { | ||
24 | + this.taskService.getAllPending({ page: this.currentPage, per_page: this.perPage }).then((result: noosfero.RestResult<noosfero.Task[]>) => { | ||
25 | + this.total = result.headers('total'); | ||
26 | + this.tasks = result.data; | ||
27 | + }); | ||
28 | + } | ||
29 | + | ||
30 | +} |
@@ -0,0 +1,8 @@ | @@ -0,0 +1,8 @@ | ||
1 | +<h3>{{"tasks.header" | translate}}</h3> | ||
2 | + | ||
3 | +<task-list [tasks]="vm.tasks"></task-list> | ||
4 | + | ||
5 | +<uib-pagination ng-model="vm.currentPage" total-items="vm.total" class="pagination-sm center-block" | ||
6 | + boundary-links="true" items-per-page="vm.perPage" ng-change="vm.loadPage()" | ||
7 | + first-text="«" last-text="»" previous-text="‹" next-text="›"> | ||
8 | +</uib-pagination> |
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | +<div class="add-member-details"> | ||
2 | + <label>{{"tasks.add_member.accept.select_role" | translate}}</label> | ||
3 | + <div class="form-group roles"> | ||
4 | + <div class="checkbox-nice" ng-repeat="role in ctrl.roles"> | ||
5 | + <input type="checkbox" id="role_{{role.name}}" (click)="ctrl.toggleSelection(role)"> | ||
6 | + <label for="role_{{role.name}}"> | ||
7 | + {{role.name}} | ||
8 | + </label> | ||
9 | + </div> | ||
10 | + </div> | ||
11 | +</div> |
src/app/task/types/add-member/add-member-task-accept-component.spec.ts
0 → 100644
@@ -0,0 +1,44 @@ | @@ -0,0 +1,44 @@ | ||
1 | +import { Provider, provide, Component } from 'ng-forward'; | ||
2 | +import * as helpers from "../../../../spec/helpers"; | ||
3 | +import { ComponentTestHelper, createClass } from '../../../../spec/component-test-helper'; | ||
4 | +import { AddMemberTaskAcceptComponent } from './add-member-task-accept.component'; | ||
5 | + | ||
6 | +const htmlTemplate: string = '<add-member-task-accept [task]="ctrl.task" [confirmation-task]="ctrl.confirmationTask"></add-member-task-accept>'; | ||
7 | + | ||
8 | +describe("Components", () => { | ||
9 | + describe("Add Member Task Accept Component", () => { | ||
10 | + | ||
11 | + let helper: ComponentTestHelper<AddMemberTaskAcceptComponent>; | ||
12 | + let roleService = jasmine.createSpyObj("roleService", ["getByProfile"]); | ||
13 | + let roles = [{ id: 1 }, { id: 2 }]; | ||
14 | + let task = <any>{ target: { id: 5 } }; | ||
15 | + roleService.getByProfile = jasmine.createSpy("getByProfile").and.returnValue(Promise.resolve({ headers: () => { }, data: roles })); | ||
16 | + | ||
17 | + beforeEach(angular.mock.module("templates")); | ||
18 | + | ||
19 | + beforeEach((done) => { | ||
20 | + let cls = createClass({ | ||
21 | + template: htmlTemplate, | ||
22 | + directives: [AddMemberTaskAcceptComponent], | ||
23 | + providers: [ | ||
24 | + helpers.createProviderToValue("RoleService", roleService) | ||
25 | + ].concat(helpers.provideFilters("translateFilter")), | ||
26 | + properties: { task: task, confirmationTask: task } | ||
27 | + }); | ||
28 | + helper = new ComponentTestHelper<AddMemberTaskAcceptComponent>(cls, done); | ||
29 | + }); | ||
30 | + | ||
31 | + it("insert role id in roles list when toggle selection", () => { | ||
32 | + let role = { id: 1 }; | ||
33 | + helper.component.toggleSelection(<any>role); | ||
34 | + expect(helper.component.confirmationTask.roles).toEqual([role.id]); | ||
35 | + }); | ||
36 | + | ||
37 | + it("remove role id from roles list when toggle selection", () => { | ||
38 | + let role = { id: 1 }; | ||
39 | + helper.component.confirmationTask.roles = [role.id]; | ||
40 | + helper.component.toggleSelection(<any>role); | ||
41 | + expect(helper.component.confirmationTask.roles).toEqual([]); | ||
42 | + }); | ||
43 | + }); | ||
44 | +}); |
src/app/task/types/add-member/add-member-task-accept.component.ts
0 → 100644
@@ -0,0 +1,33 @@ | @@ -0,0 +1,33 @@ | ||
1 | +import { Component, Input, Inject } from "ng-forward"; | ||
2 | +import { RoleService } from "../../../../lib/ng-noosfero-api/http/role.service"; | ||
3 | + | ||
4 | +@Component({ | ||
5 | + selector: "add-member-task-accept", | ||
6 | + templateUrl: "app/task/types/add-member/add-member-accept.html", | ||
7 | +}) | ||
8 | +@Inject(RoleService) | ||
9 | +export class AddMemberTaskAcceptComponent { | ||
10 | + | ||
11 | + @Input() task: noosfero.Task; | ||
12 | + @Input() confirmationTask: noosfero.AddMemberTask; | ||
13 | + roles: noosfero.Role[]; | ||
14 | + | ||
15 | + constructor(private roleService: RoleService) { } | ||
16 | + | ||
17 | + ngOnInit() { | ||
18 | + if (!this.task.target) return; | ||
19 | + this.confirmationTask.roles = []; | ||
20 | + this.roleService.getByProfile(this.task.target.id).then((result: noosfero.RestResult<noosfero.Role[]>) => { | ||
21 | + this.roles = result.data; | ||
22 | + }); | ||
23 | + } | ||
24 | + | ||
25 | + toggleSelection(role: noosfero.Role) { | ||
26 | + let index = this.confirmationTask.roles.indexOf(role.id); | ||
27 | + if (index >= 0) { | ||
28 | + this.confirmationTask.roles.splice(index, 1); | ||
29 | + } else { | ||
30 | + this.confirmationTask.roles.push(role.id); | ||
31 | + } | ||
32 | + } | ||
33 | +} |
src/app/task/types/create-community/create-community.html
0 → 100644
@@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
1 | +<!-- not implemented yet --> |
src/languages/en.json
@@ -124,5 +124,26 @@ | @@ -124,5 +124,26 @@ | ||
124 | "messages.invalid.maxlength": "This field is too long", | 124 | "messages.invalid.maxlength": "This field is too long", |
125 | "messages.invalid.minlength": "This field is too short", | 125 | "messages.invalid.minlength": "This field is too short", |
126 | "messages.invalid.email": "This needs to be a valid email", | 126 | "messages.invalid.email": "This needs to be a valid email", |
127 | - "messages.invalid.passwordMatch": "Your passwords did not match" | 127 | + "messages.invalid.passwordMatch": "Your passwords did not match", |
128 | + "tasks.menu.all": "All tasks", | ||
129 | + "tasks.menu.all": "See all tasks", | ||
130 | + "tasks.menu.header": "You have {tasks, plural, one{one pending task} other{# pending tasks}}", | ||
131 | + "tasks.actions.accept": "Accept", | ||
132 | + "tasks.actions.reject": "Reject", | ||
133 | + "tasks.header": "Tasks", | ||
134 | + "tasks.actions.accept.confirmation.title": "Confirm task acceptance?", | ||
135 | + "tasks.actions.reject.confirmation.title": "Confirm task rejection?", | ||
136 | + "tasks.actions.confirmation.yes": "Yes", | ||
137 | + "tasks.actions.confirmation.cancel": "Cancel", | ||
138 | + "tasks.actions.accept.title": "Good job!", | ||
139 | + "tasks.actions.reject.title": "Good job!", | ||
140 | + "tasks.actions.accept.message": "Task Accepted", | ||
141 | + "tasks.actions.reject.message": "Task Rejected", | ||
142 | + "tasks.actions.reject.explanation.label": "Rejection explanation", | ||
143 | + "tasks.add_member.accept.select_role": "Select Roles:", | ||
144 | + "tasks.abuse_complaint.message": "was reported due to inappropriate behavior", | ||
145 | + "tasks.add_friend.message": "wants to be friend of", | ||
146 | + "tasks.add_member.message": "wants to join", | ||
147 | + "tasks.create_community.message": "wants to create a new community:", | ||
148 | + "tasks.suggest_article.message": "suggested a new article:" | ||
128 | } | 149 | } |
src/languages/pt.json
@@ -127,5 +127,26 @@ | @@ -127,5 +127,26 @@ | ||
127 | "messages.invalid.maxlength": "O valor é muito longo", | 127 | "messages.invalid.maxlength": "O valor é muito longo", |
128 | "messages.invalid.minlength": "O valor é muito curto", | 128 | "messages.invalid.minlength": "O valor é muito curto", |
129 | "messages.invalid.email": "Informe um email válido", | 129 | "messages.invalid.email": "Informe um email válido", |
130 | - "messages.invalid.passwordMatch": "As senhas não coincidem" | 130 | + "messages.invalid.passwordMatch": "As senhas não coincidem", |
131 | + "tasks.menu.all": "Todas as tarefas", | ||
132 | + "tasks.menu.all": "Veja todas as tarefas", | ||
133 | + "tasks.menu.header": "Você tem {tasks, plural, one{uma tarefa pendente} other{# tarefas pendentes}}", | ||
134 | + "tasks.actions.accept": "Aceitar", | ||
135 | + "tasks.actions.reject": "Rejeitar", | ||
136 | + "tasks.header": "Tarefas", | ||
137 | + "tasks.actions.accept.confirmation.title": "Confirmar aprovação da tarefa?", | ||
138 | + "tasks.actions.reject.confirmation.title": "Confirmar reprovação da tarefa?", | ||
139 | + "tasks.actions.confirmation.yes": "Sim", | ||
140 | + "tasks.actions.confirmation.cancel": "Cancelar", | ||
141 | + "tasks.actions.accept.title": "Bom trabalho!", | ||
142 | + "tasks.actions.reject.title": "Bom trabalho!", | ||
143 | + "tasks.actions.accept.message": "Tarefa Aceita", | ||
144 | + "tasks.actions.reject.message": "Tarefa Rejeitada", | ||
145 | + "tasks.actions.reject.explanation.label": "Motivo da rejeição", | ||
146 | + "tasks.add_member.accept.select_role": "Selecionar papéis:", | ||
147 | + "tasks.abuse_complaint.message": "foi notificado por comportamento inadequado", | ||
148 | + "tasks.add_friend.message": "quer ser amigo de", | ||
149 | + "tasks.add_member.message": "quer entrar em", | ||
150 | + "tasks.create_community.message": "quer criar uma nova comunidade:", | ||
151 | + "tasks.suggest_article.message": "sugeriu um novo artigo:" | ||
131 | } | 152 | } |
@@ -0,0 +1,33 @@ | @@ -0,0 +1,33 @@ | ||
1 | +import { RoleService } from "./role.service"; | ||
2 | + | ||
3 | +describe("Services", () => { | ||
4 | + | ||
5 | + describe("Role Service", () => { | ||
6 | + | ||
7 | + let $httpBackend: ng.IHttpBackendService; | ||
8 | + let roleService: RoleService; | ||
9 | + | ||
10 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | ||
11 | + $translateProvider.translations('en', {}); | ||
12 | + })); | ||
13 | + | ||
14 | + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _RoleService_: RoleService) => { | ||
15 | + $httpBackend = _$httpBackend_; | ||
16 | + roleService = _RoleService_; | ||
17 | + })); | ||
18 | + | ||
19 | + | ||
20 | + describe("Succesfull requests", () => { | ||
21 | + | ||
22 | + it("list organization roles", (done) => { | ||
23 | + $httpBackend.expectGET(`/api/v1/profiles/1/roles`).respond(200, { roles: [{ id: 1 }] }); | ||
24 | + roleService.getByProfile(1).then((result: noosfero.RestResult<noosfero.Role[]>) => { | ||
25 | + expect(result.data).toEqual([{ id: 1 }]); | ||
26 | + done(); | ||
27 | + }); | ||
28 | + $httpBackend.flush(); | ||
29 | + }); | ||
30 | + }); | ||
31 | + | ||
32 | + }); | ||
33 | +}); |
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +import { Injectable, Inject } from "ng-forward"; | ||
2 | +import { RestangularService } from "./restangular_service"; | ||
3 | + | ||
4 | +@Injectable() | ||
5 | +@Inject("Restangular", "$q", "$log") | ||
6 | +export class RoleService extends RestangularService<noosfero.Role> { | ||
7 | + | ||
8 | + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) { | ||
9 | + super(Restangular, $q, $log); | ||
10 | + } | ||
11 | + | ||
12 | + getResourcePath() { | ||
13 | + return "roles"; | ||
14 | + } | ||
15 | + | ||
16 | + getDataKeys() { | ||
17 | + return { | ||
18 | + singular: 'role', | ||
19 | + plural: 'roles' | ||
20 | + }; | ||
21 | + } | ||
22 | + | ||
23 | + getByProfile(profileId: number, params: any = {}) { | ||
24 | + return this.list(this.restangularService.one("profiles", profileId), params); | ||
25 | + } | ||
26 | + | ||
27 | +} |
@@ -0,0 +1,56 @@ | @@ -0,0 +1,56 @@ | ||
1 | +import { TaskService } from "./task.service"; | ||
2 | + | ||
3 | + | ||
4 | +describe("Services", () => { | ||
5 | + | ||
6 | + describe("Task Service", () => { | ||
7 | + | ||
8 | + let $httpBackend: ng.IHttpBackendService; | ||
9 | + let taskService: TaskService; | ||
10 | + | ||
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | ||
12 | + $translateProvider.translations('en', {}); | ||
13 | + })); | ||
14 | + | ||
15 | + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _TaskService_: TaskService) => { | ||
16 | + $httpBackend = _$httpBackend_; | ||
17 | + taskService = _TaskService_; | ||
18 | + })); | ||
19 | + | ||
20 | + | ||
21 | + describe("Succesfull requests", () => { | ||
22 | + | ||
23 | + it("list pending tasks", (done) => { | ||
24 | + $httpBackend.expectGET(`/api/v1/tasks?all_pending=true&status=1`).respond(200, { tasks: [{ id: 1 }] }); | ||
25 | + taskService.getAllPending().then((result: noosfero.RestResult<noosfero.Task[]>) => { | ||
26 | + expect(result.data).toEqual([{ id: 1 }]); | ||
27 | + done(); | ||
28 | + }); | ||
29 | + $httpBackend.flush(); | ||
30 | + }); | ||
31 | + | ||
32 | + it("finish a task", (done) => { | ||
33 | + let taskId = 1; | ||
34 | + let task: noosfero.Task = <any>{ id: taskId }; | ||
35 | + $httpBackend.expectPUT(`/api/v1/tasks/${taskId}/finish`).respond(200, { task: { id: taskId } }); | ||
36 | + taskService.finishTask(task).then((result: noosfero.RestResult<noosfero.Task>) => { | ||
37 | + expect(result.data).toEqual({ id: 1 }); | ||
38 | + done(); | ||
39 | + }); | ||
40 | + $httpBackend.flush(); | ||
41 | + }); | ||
42 | + | ||
43 | + it("cancel a task", (done) => { | ||
44 | + let taskId = 1; | ||
45 | + let task: noosfero.Task = <any>{ id: taskId }; | ||
46 | + $httpBackend.expectPUT(`/api/v1/tasks/${taskId}/cancel`).respond(200, { task: { id: taskId } }); | ||
47 | + taskService.cancelTask(task).then((result: noosfero.RestResult<noosfero.Task>) => { | ||
48 | + expect(result.data).toEqual({ id: 1 }); | ||
49 | + done(); | ||
50 | + }); | ||
51 | + $httpBackend.flush(); | ||
52 | + }); | ||
53 | + }); | ||
54 | + | ||
55 | + }); | ||
56 | +}); |
@@ -0,0 +1,46 @@ | @@ -0,0 +1,46 @@ | ||
1 | +import { Injectable, Inject } from "ng-forward"; | ||
2 | +import { RestangularService } from "./restangular_service"; | ||
3 | + | ||
4 | +@Injectable() | ||
5 | +@Inject("Restangular", "$q", "$log") | ||
6 | +export class TaskService extends RestangularService<noosfero.Task> { | ||
7 | + | ||
8 | + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) { | ||
9 | + super(Restangular, $q, $log); | ||
10 | + } | ||
11 | + | ||
12 | + getResourcePath() { | ||
13 | + return "tasks"; | ||
14 | + } | ||
15 | + | ||
16 | + getDataKeys() { | ||
17 | + return { | ||
18 | + singular: 'task', | ||
19 | + plural: 'tasks' | ||
20 | + }; | ||
21 | + } | ||
22 | + | ||
23 | + getAllPending(params: any = {}) { | ||
24 | + params['all_pending'] = true; | ||
25 | + params['status'] = 1; | ||
26 | + return this.list(null, params); | ||
27 | + } | ||
28 | + | ||
29 | + finishTask(task: noosfero.Task) { | ||
30 | + return this.closeTask(task, "finish"); | ||
31 | + } | ||
32 | + | ||
33 | + cancelTask(task: noosfero.Task) { | ||
34 | + return this.closeTask(task, "cancel"); | ||
35 | + } | ||
36 | + | ||
37 | + closeTask(task: noosfero.Task, action: string) { | ||
38 | + let element = this.getElement(task.id); | ||
39 | + delete task.id; | ||
40 | + let put = element.customPUT({ task: task }, action); | ||
41 | + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Task>>(); | ||
42 | + put.then(this.getHandleSuccessFunction<noosfero.RestResult<noosfero.Task>>(deferred)); | ||
43 | + put.catch(this.getHandleErrorFunction<noosfero.RestResult<noosfero.Task>>(deferred)); | ||
44 | + return deferred.promise; | ||
45 | + } | ||
46 | +} |
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +namespace noosfero { | ||
2 | + /** | ||
3 | + * @ngdoc interface | ||
4 | + * @name noosfero.Task | ||
5 | + * @description | ||
6 | + * A representation of a Task in Noosfero. | ||
7 | + */ | ||
8 | + export interface Task extends RestModel { | ||
9 | + type: string; | ||
10 | + accept_details: boolean; | ||
11 | + reject_details: boolean; | ||
12 | + target: noosfero.Profile | noosfero.Environment; | ||
13 | + } | ||
14 | +} |
src/spec/mocks.ts
@@ -237,11 +237,16 @@ export var mocks: any = { | @@ -237,11 +237,16 @@ export var mocks: any = { | ||
237 | } | 237 | } |
238 | }, | 238 | }, |
239 | promiseResultTemplate: (response?: {}) => { | 239 | promiseResultTemplate: (response?: {}) => { |
240 | - | ||
241 | - return { | ||
242 | - then: (func?: (response: any) => void) => { | ||
243 | - if (func) { return func(response); } | 240 | + let callback = (func?: (response: any) => any) => { |
241 | + if (func) { | ||
242 | + let ret = func(response); | ||
243 | + if (ret && typeof ret.then === "function") return ret; | ||
244 | } | 244 | } |
245 | + return mocks.promiseResultTemplate(response); | ||
246 | + }; | ||
247 | + return { | ||
248 | + then: callback, | ||
249 | + finally: callback | ||
245 | }; | 250 | }; |
246 | }, | 251 | }, |
247 | $log: { | 252 | $log: { |