Commit a1375e043a9e6338cb200416ace8e6ce04fe3c5c

Authored by Michel Felipe
2 parents f3cb9503 d6d5bae0

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(&quot;MainComponent&quot;, function () { @@ -40,7 +40,8 @@ describe(&quot;MainComponent&quot;, 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 &quot;../shared/components/html-editor/html-edito @@ -49,9 +49,11 @@ import { HtmlEditorComponent } from &quot;../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 &quot;./config-bar.component&quot;; @@ -92,6 +94,18 @@ import {ConfigBarComponent} from &quot;./config-bar.component&quot;;
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 +}
src/app/task/task-list/accept.html 0 → 100644
@@ -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>
src/app/task/task-list/reject.html 0 → 100644
@@ -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>
src/app/task/task-list/task-accept.component.spec.ts 0 → 100644
@@ -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 +});
src/app/task/task-list/task-accept.component.ts 0 → 100644
@@ -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 +}
src/app/task/task-list/task-list.component.spec.ts 0 → 100644
@@ -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 +});
src/app/task/task-list/task-list.component.ts 0 → 100644
@@ -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 +}
src/app/task/task-list/task-list.html 0 → 100644
@@ -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>
src/app/task/task-list/task-list.scss 0 → 100644
@@ -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 +}
src/app/task/tasks-menu/tasks-menu.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 { 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 +});
src/app/task/tasks-menu/tasks-menu.component.ts 0 → 100644
@@ -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 +}
src/app/task/tasks-menu/tasks-menu.html 0 → 100644
@@ -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>
src/app/task/tasks-menu/tasks-menu.scss 0 → 100644
@@ -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 +}
src/app/task/tasks/tasks.component.spec.ts 0 → 100644
@@ -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 +});
src/app/task/tasks/tasks.component.ts 0 → 100644
@@ -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 +}
src/app/task/tasks/tasks.html 0 → 100644
@@ -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>
src/app/task/types/abuse-complaint/abuse-complaint.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-user-times"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.abuse_complaint.message" | translate}}
src/app/task/types/add-friend/add-friend.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-user-plus"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.add_friend.message" | translate}} <span class="target">{{task.target.name}}</span>
src/app/task/types/add-member/add-member-accept.html 0 → 100644
@@ -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/add-member/add-member.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-user-plus"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.add_member.message" | translate}} <span class="target">{{task.target.name}}</span>
src/app/task/types/add-member/add-member.scss 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +.add-member-details {
  2 + .roles {
  3 + margin-left: 40px;
  4 + }
  5 +}
src/app/task/types/create-community/create-community.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-users"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.create_community.message" | translate}} <span class="target">{{task.data.name}}</span>
src/app/task/types/default.html 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<!-- not implemented yet -->
src/app/task/types/suggest-article/suggest-article.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-file-text"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.suggest_article.message" | translate}} <span class="target">{{task.data.article.name}}</span>
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 }
src/lib/ng-noosfero-api/http/role.service.spec.ts 0 → 100644
@@ -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 +});
src/lib/ng-noosfero-api/http/role.service.ts 0 → 100644
@@ -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 +}
src/lib/ng-noosfero-api/http/task.service.spec.ts 0 → 100644
@@ -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 +});
src/lib/ng-noosfero-api/http/task.service.ts 0 → 100644
@@ -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 +}
src/lib/ng-noosfero-api/interfaces/add_member_task.ts 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +namespace noosfero {
  2 + /**
  3 + * @ngdoc interface
  4 + * @name noosfero.AddMemberTask
  5 + * @description
  6 + * A representation of an AddMemberTask in Noosfero.
  7 + */
  8 + export interface AddMemberTask extends Task {
  9 + roles: number[];
  10 + }
  11 +}
src/lib/ng-noosfero-api/interfaces/role.ts 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +namespace noosfero {
  2 + /**
  3 + * @ngdoc interface
  4 + * @name noosfero.Role
  5 + * @description
  6 + * A representation of a Role in Noosfero.
  7 + */
  8 + export interface Role extends RestModel {
  9 + name: string;
  10 + key: string;
  11 + }
  12 +}
src/lib/ng-noosfero-api/interfaces/task.ts 0 → 100644
@@ -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: {