Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: #87 c1-save-customers
  • Loading branch information
daros10 committed Apr 17, 2020
commit f2f62e419b3094d5bb4bbbfd33318f3b7a247f16
2 changes: 1 addition & 1 deletion src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { TimeEntriesComponent } from './modules/time-entries/pages/time-entries.
import { ActivitiesManagementComponent } from './modules/activities-management/pages/activities-management.component';
import { HomeComponent } from './modules/home/home.component';
import { LoginComponent } from './modules/login/login.component';
import { CustomerComponent } from './modules/customer-managment/pages/customer.component';
import { CustomerComponent } from './modules/customer-management/pages/customer.component';

const routes: Routes = [
{
Expand Down
21 changes: 11 additions & 10 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,21 @@ import { ProjectEffects } from './modules/project-management/store/project.effec
import { TechnologyEffects } from './modules/shared/store/technology.effects';
import { reducers, metaReducers } from './reducers';
import { environment } from '../environments/environment';
import { CustomerComponent } from './modules/customer-managment/pages/customer.component';
import { CustomerComponent } from './modules/customer-management/pages/customer.component';
// tslint:disable-next-line: max-line-length
import { CustomerListComponent } from './modules/customer-managment/components/customer-info/components/customer-list/customer-list.component';
import { SearchComponent } from './modules/customer-managment/components/search/search.component';
import { CustomerListComponent } from './modules/customer-management/components/customer-info/components/customer-list/customer-list.component';
import { SearchComponent } from './modules/customer-management/components/search/search.component';
// tslint:disable-next-line: max-line-length
import { ManagementCustomerProjectsComponent } from './modules/customer-managment/components/management-customer-projects/management-customer-projects.component';
import { CreateCustomerComponent } from './modules/customer-managment/components/customer-info/components/create-customer/create-customer';
import { ManagementCustomerProjectsComponent } from './modules/customer-management/components/management-customer-projects/management-customer-projects.component';
import { CreateCustomerComponent } from './modules/customer-management/components/customer-info/components/create-customer/create-customer';
// tslint:disable-next-line: max-line-length
import { CreateProjectComponent } from './modules/customer-managment/components/projects/components/create-project/create-project.component';
import { ProjectListComponent } from './modules/customer-managment/components/projects/components/project-list/project-list.component';
import { CreateProjectComponent } from './modules/customer-management/components/projects/components/create-project/create-project.component';
import { ProjectListComponent } from './modules/customer-management/components/projects/components/project-list/project-list.component';
// tslint:disable-next-line: max-line-length
import { ProjectTypeListComponent } from './modules/customer-managment/components/projects-type/components/project-type-list/project-type-list.component';
import { ProjectTypeListComponent } from './modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component';
// tslint:disable-next-line: max-line-length
import { CreateProjectTypeComponent } from './modules/customer-managment/components/projects-type/components/create-project-type/create-project-type.component';
import { CreateProjectTypeComponent } from './modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component';
import { CustomerEffects } from './modules/customer-management/store/customer-management.effects';

@NgModule({
declarations: [
Expand Down Expand Up @@ -102,7 +103,7 @@ import { CreateProjectTypeComponent } from './modules/customer-managment/compone
maxAge: 15, // Retains last 15 states
})
: [],
EffectsModule.forRoot([ProjectEffects, ActivityEffects, TechnologyEffects]),
EffectsModule.forRoot([ProjectEffects, ActivityEffects, CustomerEffects, TechnologyEffects]),
],
providers: [],
bootstrap: [AppComponent],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class="container">
<form style="width: 600px;" [formGroup]="customerForm" (ngSubmit)="onSubmit(customerForm.value)">
<div class="form-group">
<div
*ngIf="showAlert && messageToShow !== ''"
[ngClass]="{'bg-secondary': messageToShow == 'Customer create successfully!', 'bg-primary': messageToShow != 'Customer create successfully!'}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:
Customer created successfully!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

class="alert alert-dismissible fade fade-in show text-white"
role="alert"
>
<strong>{{ messageToShow }}</strong>
</div>

<input
class="form-control form-control-sm"
id="name"
type="text"
formControlName="name"
placeholder="Customer name"
[class.is-invalid]="customerForm.invalid && customerForm.touched"
required
/>
<span
class="badge badge-pill badge-light text-danger"
*ngIf="(customerForm.dirty || customerForm.touched) && customerForm.invalid"
>Activity name is required</span
>
<textarea
class="form-control form-control-sm mt-2"
id="description"
rows="3"
formControlName="description"
placeholder="Customer description"
></textarea>
<button type="submit" class="btn btn-sm btn-primary" [disabled]="!customerForm.valid">Save</button>
<button (click)="resetCustomerForm()" id="cancel" type="button" class="btn btn-sm btn-secondary mb-2 ml-2 mt-2">
Cancel
</button>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { CreateCustomerComponent } from './create-customer';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { CustomerState, CreateCustomer } from 'src/app/modules/customer-management/store';
import { FormBuilder } from '@angular/forms';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sort imports by module

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Customer } from 'src/app/modules/shared/models/customer.model';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The customer.model must be added in the index file in 'src/app/modules/shared/models/index.ts'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


describe('CreateCustomerComponent', () => {
let component: CreateCustomerComponent;
let fixture: ComponentFixture<CreateCustomerComponent>;
let store: MockStore<CustomerState>;

const state = {
data: [],
isLoading: false,
message: '',
};

const customerData: Customer = {
name: 'aa',
description: 'bb',
tenant_id: 'cc',
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CreateCustomerComponent],
providers: [FormBuilder, provideMockStore({ initialState: state })],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(CreateCustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();

store = TestBed.inject(MockStore);
store.setState(state);
});

afterEach(() => {
fixture.destroy();
});

it('component should be created', () => {
expect(component).toBeTruthy();
});

it('should call resetCustomerForm', () => {
spyOn(component.customerForm, 'reset');

component.resetCustomerForm();

expect(component.customerForm.reset).toHaveBeenCalled();
});

it('onSubmit and dispatch CreateCustomer action', () => {
spyOn(store, 'dispatch');

component.onSubmit(customerData);

expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith(new CreateCustomer(customerData));
});

it('should call resetCustomerForm', () => {
spyOn(component.customerForm, 'reset');

component.resetCustomerForm();

expect(component.customerForm.reset).toHaveBeenCalled();
});

it('should be enable tabs and show message Customer create successfully! ', () => {
component.isActiveItemTabs = false;
component.messageToShow = '';

spyOn(store, 'dispatch');

component.ngOnInit();

component.onSubmit(customerData);

component.setStatusOnScreen('Customer create successfully!');

expect(component.messageToShow).toEqual('Customer create successfully!');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Customer created successfully

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

expect(component.isActiveItemTabs).toBeTrue();
});

it('should be disabled tabs and show message Something went wrong creating customer! ', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it('should disable tabs and show a message "An error occurred, try again later." '

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

component.isActiveItemTabs = false;
component.messageToShow = '';

spyOn(store, 'dispatch');

component.ngOnInit();

component.onSubmit(customerData);

component.setStatusOnScreen('Something went wrong creating customer!');

expect(component.messageToShow).toEqual('Something went wrong creating customer!');
expect(component.isActiveItemTabs).toBeFalse();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Component, Input, Output, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { CustomerState, CreateCustomer } from 'src/app/modules/customer-management/store';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sort imports by module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Subscription } from 'rxjs';
import { getStatusMessage } from 'src/app/modules/customer-management/store/customer-management.selectors';

@Component({
selector: 'app-create-customer',
templateUrl: './create-customer.html',
styleUrls: ['./create-customer.scss'],
})
export class CreateCustomerComponent implements OnInit, OnDestroy {
customerForm: FormGroup;
@Input() isActiveItemTabs: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better name would be:
areTabsActive

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@Output() changeValueIsActiveItemTabs = new EventEmitter<boolean>();
showAlert = false;
messageToShow = '';
saveSubscription: Subscription;

constructor(private formBuilder: FormBuilder, private store: Store<CustomerState>) {
this.customerForm = this.formBuilder.group({
name: ['', Validators.required],
description: [''],
});
}

ngOnInit() {
const messages$ = this.store.pipe(select(getStatusMessage));
this.saveSubscription = messages$.subscribe((valueMessage) => {
this.setStatusOnScreen(valueMessage);
console.log(valueMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, delete this console.log

Copy link
Contributor Author

@daros10 daros10 Apr 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

});
}

ngOnDestroy() {
this.isActiveItemTabs = false;
this.saveSubscription.unsubscribe();
}

onSubmit(customerData) {
this.store.dispatch(new CreateCustomer(customerData));
this.showAlert = true;
}

setStatusOnScreen(message: string) {
if (message === 'Customer create successfully!') {
this.messageToShow = message;
this.isActiveItemTabs = true;
this.changeValueIsActiveItemTabs.emit(this.isActiveItemTabs);
} else if (message === 'Something went wrong creating customer!') {
this.messageToShow = message;
this.isActiveItemTabs = false;
this.changeValueIsActiveItemTabs.emit(this.isActiveItemTabs);
}
}

resetCustomerForm() {
this.showAlert = false;
this.customerForm.reset();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
>
</li>
<li class="nav-item">
<!--nav-active-->
<a
class="nav-link nav-active text-black font-weight-bold"
class="nav-link text-black font-weight-bold"
[ngClass]="{ 'nav-active': isActiveNavsItemTabs, disabled: !isActiveNavsItemTabs }"
id="projects-type-tab"
data-toggle="tab"
href="#projectsType"
Expand All @@ -24,7 +26,8 @@
</li>
<li class="nav-item">
<a
class="nav-link nav-active text-black font-weight-bold"
class="nav-link text-black font-weight-bold"
[ngClass]="{ 'nav-active': isActiveNavsItemTabs, disabled: !isActiveNavsItemTabs }"
id="projects-tab"
data-toggle="tab"
href="#projects"
Expand All @@ -41,7 +44,10 @@
role="tabpanel"
aria-labelledby="customer-information-tab"
>
<app-create-customer></app-create-customer>
<app-create-customer
[isActiveItemTabs]="isActiveNavsItemTabs"
(changeValueIsActiveItemTabs)="isActiveNavsItemTabs = $event"
></app-create-customer>
</div>
<div class="tab-pane fade mt-3" id="projects" role="tabpanel" aria-labelledby="projects-tab">
<app-create-project></app-create-project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ import { Component } from '@angular/core';
styleUrls: ['./management-customer-projects.component.scss'],
})
export class ManagementCustomerProjectsComponent {
isActiveNavsItemTabs = false;

constructor() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { TestBed, inject } from '@angular/core/testing';

import { CustomerService } from './customer.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sort imports by module

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Customer } from '../../shared/models/customer.model';

describe('CustomerService', () => {
let service: CustomerService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({ imports: [HttpClientTestingModule] });
service = TestBed.inject(CustomerService);
httpMock = TestBed.inject(HttpTestingController);
});

afterEach(() => {
httpMock.verify();
});

it('services are ready to be used', inject(
[HttpClientTestingModule, CustomerService],
(httpClient: HttpClientTestingModule, customerService: CustomerService) => {
expect(customerService).toBeTruthy();
expect(httpClient).toBeTruthy();
}
));

it('create customer using POST from baseUrl', () => {
const customer: Customer[] = [{ name: 'aa', description: 'bb', tenant_id: 'cc' }];

service.baseUrl = 'customers';

service.createCustomer(customer).subscribe((response) => {
expect(response.length).toBe(1);
});

const createCustomerRequest = httpMock.expectOne(service.baseUrl);
expect(createCustomerRequest.request.method).toBe('POST');
createCustomerRequest.flush(customer);
});
});
21 changes: 21 additions & 0 deletions src/app/modules/customer-management/services/customer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from './../../../../environments/environment';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class CustomerService {
baseUrl = `${environment.timeTrackerApiUrl}/customers`;

constructor(private http: HttpClient) {}

createCustomer(customerData): Observable<any> {
const body = {
...customerData,
tenant_id: '4225ab1e-1033-4a5f-8650-0dd4950f38c8',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enriquezrene Do we still need to set a tenant_id fake?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @daros10 needs to talk to @macrisguncay to understand how to get the tenant_id from the session_storage. Good catch @jorgecod, ty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes was applied except this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daros10 you can get the real tenant id and bearer token from session storage, I implemented these methods in AzureAdB2CService, please check it and use them please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you @macrisguncay , @enriquezrene all review requesting was applied.

};
return this.http.post(this.baseUrl, body);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as actions from './customer-management.actions';

describe('CustomerManagmentActions', () => {
it('CreateCustomer type is CustomerManagementActionTypes.CREATE_CUSTOMER', () => {
const createActivity = new actions.CreateCustomer({
name: 'aa',
description: 'bb',
tenant_id: 'cc',
});
expect(createActivity.type).toEqual(actions.CustomerManagementActionTypes.CREATE_CUSTOMER);
});

it('CreateCustomerSuccess type is CustomerManagementActionTypes.CREATE_CUSTOMER_SUCCES', () => {
const createActivitySuccess = new actions.CreateCustomerSuccess({
name: 'aa',
description: 'bb',
tenant_id: 'cc',
});
expect(createActivitySuccess.type).toEqual(actions.CustomerManagementActionTypes.CREATE_CUSTOMER_SUCCESS);
});

it('CreateCustomerFail type is CustomerManagementActionTypes.CREATE_CUSTOMER_FAIL', () => {
const createActivityFail = new actions.CreateCustomerFail('error');
expect(createActivityFail.type).toEqual(actions.CustomerManagementActionTypes.CREATE_CUSTOMER_FAIL);
});
});
Loading