Merge Request #55

Merged
noosfero-themes/angular-theme!55
Created by Victor Costa

Add components that display tasks and allow accept/reject

Assignee: None
Milestone: 2016.07

Merged by Michel Felipe

Source branch has been removed
Commits (11)
2 participants
Showing 41 changed files   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({
3
  • Me
    Michel Felipe @mfdeveloper

    Ok, entendi :+1:

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    A forma que o ComponentTestHelper foi feito não funciona nesse caso por causa do this.$element.replaceWith que o componente executa na inicialização.

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Algum motivo especial em não ter utilizado a classe ComponentTestHelper neste teste?

    Choose File ...   File name...
    Cancel
  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";
1
  • Me
    Michel Felipe @mfdeveloper

    Essa substituição e concatenação não seria + indicada em um método separado, contendo um teste unitário correspondente?

    Choose File ...   File name...
    Cancel
  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) {
1
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Os métodos accept() e reject() me parecem que fazem a mesma coisa. Não seria interessante um método central, passando como parâmetro o que muda entre eles ?

    Choose File ...   File name...
    Cancel
  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() {
1
  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}}">
1
  • Me
    Michel Felipe @mfdeveloper

    Não seria melhor substituir as chamadas a ng-click="" por (click)="method()" no formato "Angular 2" de ser?

    Choose File ...   File name...
    Cancel
  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}}
1
  • Me
    Michel Felipe @mfdeveloper

    Essa mensagem não deveria ser controlada pela tradução? Dessa forma ficará apenas em inglês

    Choose File ...   File name...
    Cancel
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">
1
  • Me
    Michel Felipe @mfdeveloper

    Você tentou utilizar a classe .checkbox-nice e sua estrutura HTML? Talvez a apresentação deste fique melhor, uma vez que já está sendo utilizado no formulário de login :)

    Choose File ...   File name...
    Cancel
  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 = [];
3
  • Me
    Michel Felipe @mfdeveloper

    Então, seguindo o modelo de interfaces typescript espelhando os models do backend, deveria ter um noosfero.MyTask que herda de noosfero.Task nao?

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    A questão é que roles não é um atributo genérico, é específico de um tipo de Task.

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper (Edited )

    O atributo 'roles' não poderia ser definido na interface noosfero.Task? Isso evitaria o cast para anyde forma tão recorrente ao longo desse componente.

    Choose File ...   File name...
    Cancel
  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: {
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    mentioned in issue #120

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 0af91d99 - List roles when accept add member tasks
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 00341879 - Accept parameters when accept/reject tasks
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 12 new commits:

    • 8784fe3c - Ticket #116: Better display of recent activities
    • 1e4736a4 - added events-hub service
    • b3953cbd - improving adding a base interface EventsHubKnownEventNames
    • 332c24ec - added changes to allow pass string[] or EventsHubKnownEventNames to EventsHubService constructor
    • 328a7a82 - some small refactoring on class naming and changed to allow EVENTS_HUB_KNOW_EVEN…
    • fb1e923a - Merge branch 'events-hub' into 'master'
    • f0967aa0 - Add component to display tasks in menu
    • 434c23df - Add component to list tasks
    • ef871ad6 - Add accept/reject buttons to task list
    • 51eac1ba - Add tests to task components
    • 83f027df - List roles when accept add member tasks
    • f25f5c3c - Accept parameters when accept/reject tasks
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 22792707 - Emit and subscribe to events for task accept/reject
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Reassigned to @mfdeveloper

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/task/types/abuse-complaint/abuse-complaint.html 0 → 100644
      1 +<i class="task-icon fa fa-fw fa-user-times"></i>
      2 +<span class="requestor">{{task.requestor.name}}</span> was reported due to inappropriate behavior
    1
    • Me
      Michel Felipe @mfdeveloper

      Essa mensagem não deveria ser controlada pela tradução? Dessa forma ficará apenas em inglês

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/task/types/add-member/add-member-accept.html 0 → 100644
      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" ng-repeat="role in ctrl.roles">
    1
    • Me
      Michel Felipe @mfdeveloper

      Você tentou utilizar a classe .checkbox-nice e sua estrutura HTML? Talvez a apresentação deste fique melhor, uma vez que já está sendo utilizado no formulário de login :)

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/task/types/add-member/add-member-task-accept.component.ts 0 → 100644
      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.Task;
      13 + roles: noosfero.Role[];
      14 +
      15 + constructor(private roleService: RoleService) { }
      16 +
      17 + ngOnInit() {
      18 + if (!this.task.target) return;
      19 + (<any>this.confirmationTask)['roles'] = [];
    3
    • Me
      Michel Felipe @mfdeveloper (Edited )

      O atributo 'roles' não poderia ser definido na interface noosfero.Task? Isso evitaria o cast para anyde forma tão recorrente ao longo desse componente.

      Choose File ...   File name...
      Cancel
    • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
      Victor Costa @vfcosta

      A questão é que roles não é um atributo genérico, é específico de um tipo de Task.

      Choose File ...   File name...
      Cancel
    • Me
      Michel Felipe @mfdeveloper

      Então, seguindo o modelo de interfaces typescript espelhando os models do backend, deveria ter um noosfero.MyTask que herda de noosfero.Task nao?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/task/task-list/task-accept.component.spec.ts 0 → 100644
      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({
    3
    • Me
      Michel Felipe @mfdeveloper

      Algum motivo especial em não ter utilizado a classe ComponentTestHelper neste teste?

      Choose File ...   File name...
      Cancel
    • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
      Victor Costa @vfcosta

      A forma que o ComponentTestHelper foi feito não funciona nesse caso por causa do this.$element.replaceWith que o componente executa na inicialização.

      Choose File ...   File name...
      Cancel
    • Me
      Michel Felipe @mfdeveloper

      Ok, entendi :+1:

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/task/task-list/task-accept.component.ts 0 → 100644
      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";
    1
    • Me
      Michel Felipe @mfdeveloper

      Essa substituição e concatenação não seria + indicada em um método separado, contendo um teste unitário correspondente?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/task/task-list/task-list.component.ts 0 → 100644
      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) {
    1
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Os métodos accept() e reject() me parecem que fazem a mesma coisa. Não seria interessante um método central, passando como parâmetro o que muda entre eles ?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/task/task-list/task-list.component.ts 0 → 100644
      79 + });
      80 + } else {
      81 + this.callReject();
      82 + }
      83 + }
      84 +
      85 + callAccept() {
      86 + this.taskService.finishTask(this.confirmationTask).then(() => {
      87 + this.eventsHubService.emitEvent(this.eventsNames.TASK_CLOSED, this.currentTask);
      88 + this.notificationService.success({ title: "tasks.actions.accept.title", message: "tasks.actions.accept.message" });
      89 + }).finally(() => {
      90 + this.cancel();
      91 + });
      92 + }
      93 +
      94 + callReject() {
    1
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/task/task-list/task-list.html 0 → 100644
      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}}">
    1
    • Me
      Michel Felipe @mfdeveloper

      Não seria melhor substituir as chamadas a ng-click="" por (click)="method()" no formato "Angular 2" de ser?

      Choose File ...   File name...
      Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 3 new commits:

    • 879c272e - Translate tasks messages
    • 7c38f4b5 - Use .checkbox-nice in accept member task
    • ffc634cb - Refactor methods to close task
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • dbdaa1d0 - Create interface for AddMemberTask
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    @mfdeveloper feito!

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Added 32 new commits:

    • 3d2172f3 - Adds account register module
    • bc91225e - Refactor of the account register component to uses service and restangular instead.
    • 42443554 - Fixing registration - it is working now
    • 503976b5 - add redirect to home in case of succeded signup
    • e10ddb2a - Added notification when user is created
    • 716c2a26 - Added welcome message for translation on signup
    • 74b67520 - Refactory html template + new environment service property
    • 64b0a116 - Added environment fields and ngMessages form validation
    • 4c25e839 - Added the ng.ui.bootstrap typescript definitions
    • 015b68e2 - Added the modal to show environment terms of use text
    • 224b593d - Added module 'angular-password' to check password confir match
    • a4dac9ec - Added unit tests and minor refactories
    • b4a0a739 - Fix rebase with master adjusts
    • 64000ab5 - Merge branch 'register_page' into 'master'
    • 26ff5335 - Change txt navbar menuitem from 'Novo Artigo' to 'Novo post' on pt.json
    • d3bba43c - Ticket #118: Profile images block
    • 6596b7af - adding scrap activity in profile timeline
    • e80a93b4 - change scrap timeline visualization
    • de08c0f6 - Merge branch 'add_scrap_in_timeline' into 'master'
    • 7309dec7 - Add button to join in community
    • f3cb9503 - Merge branch 'join-community' into 'master'
    • 4d167676 - Add component to display tasks in menu
    • 917544be - Add component to list tasks
    • 07f15849 - Add accept/reject buttons to task list
    • 45183e9d - Add tests to task components
    • 118eeb9d - List roles when accept add member tasks
    • 0354c769 - Accept parameters when accept/reject tasks
    • 7205a81d - Emit and subscribe to events for task accept/reject
    • 917a672d - Translate tasks messages
    • 1adb5479 - Use .checkbox-nice in accept member task
    • 6fca41b5 - Refactor methods to close task
    • d6d5bae0 - Create interface for AddMemberTask
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Perfeito @vfcosta ! Merge realizado :)

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Assignee removed

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    mentioned in issue #120

    Choose File ...   File name...
    Cancel