Skip to content

Commit 3971c13

Browse files
authored
Merge pull request #208 from ioet/201/feature-clock-in-out-message
201/feature clock in out message
2 parents 0dc8687 + 044cf2a commit 3971c13

File tree

12 files changed

+105
-52
lines changed

12 files changed

+105
-52
lines changed

README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,24 @@ Install the following extensions:
5252
- `Prettier - Code formatter`.
5353
- Go to user settings (`settings.json`) and enable formatting on save: `"editor.formatOnSave": true`.
5454

55-
### Commit message format
56-
A commit message needs to start with one of the following words to bump the application version
57-
properly (This application is following a semver strategy for versioning https://semver.org/)
58-
### Sumary
59-
- **fix** is equal to Patch Release example: 1.0.1
60-
- **feat** is equal to Feature Release example: 1.1.0
61-
- **perf** is equal to Breaking Release example: 2.0.0
55+
### Commit messages format
56+
Commit messages' format follows the [Conventional Commits guidelines](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification,
57+
and specifically we are relying on the [Angular commit specifications](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines) to bump the [semantic version](https://semver.org/) and generate app change log.
58+
59+
Below there are some common examples you can use for your commit messages:
60+
61+
- **feat**: A new feature.
62+
- **fix**: A bug fix.
63+
- **perf**: A code change that improves performance.
64+
- **build**: Changes that affect the build system or external dependencies (example scopes: npm, ts configuration).
65+
- **ci**: Changes to our CI or CD configuration files and scripts (example scopes: Azure devops, github actions).
66+
- **docs**: Documentation only changes.
67+
- **refactor**: A code change that neither fixes a bug nor adds a feature.
68+
- **style**: Changes that do not affect the meaning of the code (typos, white-space, formatting, missing semi-colons, etc).
69+
It is important to mention that this key is not related to css styles.
70+
- **test**: Adding missing tests or correcting existing tests.
6271
### Example
63-
fix: #48 implement semantic version.
72+
fix: #48 implement semantic versioning.
6473

6574
## Code scaffolding
6675
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
},
7676
"config": {
7777
"commit-message-validator": {
78-
"pattern": "^(fix: #|feat: #|perf: #)[0-9].*",
78+
"pattern": "^(fix: #|feat: #|perf: #|build: #|ci: #|docs: #|refactor: #|style: #|test: #)[0-9].*",
7979
"errorMessage": "Your commit message needs to start with fix:, feat:, or perf: followed by issue number, e.g. fix: #43 any commit message"
8080
}
8181
},

src/app/modules/time-clock/pages/time-clock.component.html

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1+
<app-notification [notificationMsg]="message | async" [isError]="isError" *ngIf="showNotification"></app-notification>
12
<div class="text-center mt-3">
2-
<div class="alert alert-danger" role="alert">
3-
{{message}}
4-
</div>
5-
63
<div class="card">
74
<div class="card-body">
85
<h6 class="text-left"><strong>Summary</strong></h6>

src/app/modules/time-clock/pages/time-clock.component.spec.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
import { StopTimeEntryRunning } from './../store/entry.actions';
1+
import {EntryActionTypes, StopTimeEntryRunning } from './../store/entry.actions';
22
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
33
import { HttpClientTestingModule } from '@angular/common/http/testing';
44
import { provideMockStore, MockStore } from '@ngrx/store/testing';
55

66
import { TimeClockComponent } from './time-clock.component';
77
import { ProjectState } from '../../customer-management/components/projects/components/store/project.reducer';
88
import { ProjectListHoverComponent } from '../components';
9-
import { ProjectService } from '../../customer-management/components/projects/components/services/project.service';
109
import { FilterProjectPipe } from '../../shared/pipes';
1110
import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service';
11+
import {ActionsSubject} from '@ngrx/store';
1212

1313
describe('TimeClockComponent', () => {
1414
let component: TimeClockComponent;
1515
let fixture: ComponentFixture<TimeClockComponent>;
1616
let store: MockStore<ProjectState>;
17-
let projectService: ProjectService;
1817
let azureAdB2CService: AzureAdB2CService;
18+
const actionSub: ActionsSubject = new ActionsSubject();
1919
const state = {
2020
projects: {
2121
projects: [{ id: 'id', name: 'name', project_type_id: '' }],
@@ -44,7 +44,10 @@ describe('TimeClockComponent', () => {
4444
TestBed.configureTestingModule({
4545
imports: [HttpClientTestingModule],
4646
declarations: [TimeClockComponent, ProjectListHoverComponent, FilterProjectPipe],
47-
providers: [ProjectService, AzureAdB2CService, provideMockStore({ initialState: state })],
47+
providers: [
48+
AzureAdB2CService,
49+
{ provide: ActionsSubject, useValue: actionSub },
50+
provideMockStore({ initialState: state })],
4851
}).compileComponents();
4952
store = TestBed.inject(MockStore);
5053
}));
@@ -53,7 +56,6 @@ describe('TimeClockComponent', () => {
5356
fixture = TestBed.createComponent(TimeClockComponent);
5457
component = fixture.componentInstance;
5558
fixture.detectChanges();
56-
projectService = TestBed.inject(ProjectService);
5759
azureAdB2CService = TestBed.inject(AzureAdB2CService);
5860
});
5961

@@ -77,13 +79,6 @@ describe('TimeClockComponent', () => {
7779
expect(azureAdB2CService.getName).toHaveBeenCalledTimes(0);
7880
});
7981

80-
it('Service injected via inject(...) and TestBed.get(...) should be the same instance', inject(
81-
[ProjectService],
82-
(injectService: ProjectService) => {
83-
expect(injectService).toBe(projectService);
84-
}
85-
));
86-
8782
it('clockOut dispatch a StopTimeEntryRunning action', () => {
8883
spyOn(store, 'dispatch');
8984

@@ -92,4 +87,22 @@ describe('TimeClockComponent', () => {
9287
expect(store.dispatch).toHaveBeenCalledWith(new StopTimeEntryRunning('id'));
9388
});
9489

90+
it('on success create entry, the notification is shown', () => {
91+
const actionSubject = TestBed.get(ActionsSubject) as ActionsSubject;
92+
const action = {
93+
type: EntryActionTypes.CREATE_ENTRY_SUCCESS
94+
};
95+
actionSubject.next(action);
96+
expect(component.showNotification).toEqual(true);
97+
});
98+
99+
it('on success stop entry, the notification is shown', () => {
100+
const actionSubject = TestBed.get(ActionsSubject) as ActionsSubject;
101+
const action = {
102+
type: EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS
103+
};
104+
actionSubject.next(action);
105+
expect(component.showNotification).toEqual(true);
106+
});
107+
95108
});
Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
1-
import { getStatusMessage } from './../../customer-management/store/customer-management.selectors';
2-
import { getActiveTimeEntry } from './../store/entry.selectors';
3-
import { StopTimeEntryRunning } from './../store/entry.actions';
4-
import { Entry } from './../../shared/models/entry.model';
5-
import { Store, select } from '@ngrx/store';
6-
import { Component, OnInit } from '@angular/core';
7-
import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service';
1+
import {getStatusMessage} from './../../time-clock/store/entry.selectors';
2+
import {getActiveTimeEntry} from './../store/entry.selectors';
3+
import {EntryActionTypes, StopTimeEntryRunning} from './../store/entry.actions';
4+
import {Entry} from './../../shared/models/entry.model';
5+
import {Store, select, ActionsSubject} from '@ngrx/store';
6+
import {Component, OnInit} from '@angular/core';
7+
import {AzureAdB2CService} from '../../login/services/azure.ad.b2c.service';
8+
import {Observable, Subscription} from 'rxjs';
9+
import {filter} from 'rxjs/operators';
810

911
@Component({
1012
selector: 'app-time-clock',
1113
templateUrl: './time-clock.component.html',
1214
styleUrls: ['./time-clock.component.scss'],
1315
})
1416
export class TimeClockComponent implements OnInit {
15-
1617
username: string;
1718
areFieldsVisible = false;
1819
activeTimeEntry: Entry;
19-
message: string;
20+
message: Observable<string>;
21+
showNotification = false;
22+
isError = false;
23+
actionsSubscription: Subscription;
2024

21-
constructor(private azureAdB2CService: AzureAdB2CService, private store: Store<Entry>) {
25+
constructor(private azureAdB2CService: AzureAdB2CService, private store: Store<Entry>, private actionsSubject$: ActionsSubject) {
2226
}
2327

2428
ngOnInit() {
29+
this.message = this.store.pipe(select(getStatusMessage));
2530
this.username = this.azureAdB2CService.isLogin() ? this.azureAdB2CService.getName() : '';
2631
this.store.pipe(select(getActiveTimeEntry)).subscribe((activeTimeEntry) => {
2732
this.activeTimeEntry = activeTimeEntry;
@@ -31,8 +36,12 @@ export class TimeClockComponent implements OnInit {
3136
this.areFieldsVisible = false;
3237
}
3338
});
34-
this.store.pipe(select(getStatusMessage)).subscribe((valueMessage) => {
35-
this.message = valueMessage;
39+
40+
this.actionsSubscription = this.actionsSubject$.pipe(
41+
filter((action: any) => (action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS) ||
42+
action.type === EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS)
43+
).subscribe((action) => {
44+
this.displayNotification();
3645
});
3746
}
3847

@@ -41,4 +50,8 @@ export class TimeClockComponent implements OnInit {
4150
this.areFieldsVisible = false;
4251
}
4352

53+
displayNotification() {
54+
this.showNotification = true;
55+
setTimeout(() => ((this.showNotification = false), (this.isError = false)), 3000);
56+
}
4457
}

src/app/modules/time-clock/store/entry.actions.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ describe('Actions for Entries', () => {
2929
project_id: '1',
3030
description: 'It is good for learning',
3131
});
32-
expect(updateActiveEntrySuccess.type).toEqual(actions.EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS);
32+
expect(updateActiveEntrySuccess.type).toEqual(actions.EntryActionTypes.UPDATE_ACTIVE_ENTRY_SUCCESS);
3333
});
3434

3535
it('UpdateActiveEntryFail type is EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL', () => {
3636
const updateActiveEntryFail = new actions.UpdateActiveEntryFail('error');
37-
expect(updateActiveEntryFail.type).toEqual(actions.EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL);
37+
expect(updateActiveEntryFail.type).toEqual(actions.EntryActionTypes.UPDATE_ACTIVE_ENTRY_FAIL);
3838
});
3939
});

src/app/modules/time-clock/store/entry.actions.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ export enum EntryActionTypes {
88
CREATE_ENTRY = '[Entry] CREATE_ENTRY',
99
CREATE_ENTRY_SUCCESS = '[Entry] CREATE_ENTRY_SUCCESS',
1010
CREATE_ENTRY_FAIL = '[Entry] CREATE_ENTRY_FAIL',
11-
UDPATE_ACTIVE_ENTRY = '[Entry] UDPATE_ACTIVE_ENTRY',
12-
UDPATE_ACTIVE_ENTRY_SUCCESS = '[Entry] UDPATE_ACTIVE_ENTRY_SUCCESS',
13-
UDPATE_ACTIVE_ENTRY_FAIL = '[Entry] UDPATE_ACTIVE_ENTRY_FAIL',
11+
UPDATE_ACTIVE_ENTRY = '[Entry] UPDATE_ACTIVE_ENTRY',
12+
UPDATE_ACTIVE_ENTRY_SUCCESS = '[Entry] UPDATE_ACTIVE_ENTRY_SUCCESS',
13+
UPDATE_ACTIVE_ENTRY_FAIL = '[Entry] UPDATE_ACTIVE_ENTRY_FAIL',
1414
STOP_TIME_ENTRY_RUNNING = '[Entry] STOP_TIME_ENTRIES_RUNNING',
1515
STOP_TIME_ENTRY_RUNNING_SUCCESS = '[Entry] STOP_TIME_ENTRIES_RUNNING_SUCCESS',
1616
STOP_TIME_ENTRY_RUNNING_FAILED = '[Entry] STOP_TIME_ENTRIES_RUNNING_FAILED',
@@ -50,19 +50,19 @@ export class CreateEntryFail implements Action {
5050
}
5151

5252
export class UpdateActiveEntry implements Action {
53-
public readonly type = EntryActionTypes.UDPATE_ACTIVE_ENTRY;
53+
public readonly type = EntryActionTypes.UPDATE_ACTIVE_ENTRY;
5454

5555
constructor(public payload: NewEntry) {}
5656
}
5757

5858
export class UpdateActiveEntrySuccess implements Action {
59-
public readonly type = EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS;
59+
public readonly type = EntryActionTypes.UPDATE_ACTIVE_ENTRY_SUCCESS;
6060

6161
constructor(public payload: NewEntry) {}
6262
}
6363

6464
export class UpdateActiveEntryFail implements Action {
65-
public readonly type = EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL;
65+
public readonly type = EntryActionTypes.UPDATE_ACTIVE_ENTRY_FAIL;
6666

6767
constructor(public error: string) {}
6868
}

src/app/modules/time-clock/store/entry.effects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class EntryEffects {
3939

4040
@Effect()
4141
updateActiveEntry$: Observable<Action> = this.actions$.pipe(
42-
ofType(actions.EntryActionTypes.UDPATE_ACTIVE_ENTRY),
42+
ofType(actions.EntryActionTypes.UPDATE_ACTIVE_ENTRY),
4343
map((action: actions.UpdateActiveEntry) => action.payload),
4444
mergeMap((project) =>
4545
this.entryService.updateActiveEntry(project).pipe(

src/app/modules/time-clock/store/entry.reducer.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ describe('entryReducer', () => {
3737
expect(state.isLoading).toEqual(true);
3838
});
3939

40+
it('on CreateEntrySuccess, message is updated', () => {
41+
const entryToCreate: NewEntry = { project_id: '1', start_date: '2020-04-21T19:51:36.559000+00:00' };
42+
const action = new actions.CreateEntrySuccess(entryToCreate);
43+
const state = entryReducer(initialState, action);
44+
45+
expect(state.message).toEqual('You clocked-in successfully');
46+
});
47+
4048
it('on CreateEntryFail, entryList equal []', () => {
4149
const action = new actions.CreateEntryFail('error');
4250
const state = entryReducer(initialState, action);
@@ -81,12 +89,13 @@ describe('entryReducer', () => {
8189
expect(state.isLoading).toEqual(true);
8290
});
8391

84-
it('on StopTimeEntryRunningSuccess, active to be null', () => {
92+
it('on StopTimeEntryRunningSuccess, active is null and message is updated', () => {
8593
const action = new actions.StopTimeEntryRunningSuccess('id');
8694

8795
const state = entryReducer(initialState, action);
8896

8997
expect(state.active).toEqual(null);
98+
expect(state.message).toEqual('You clocked-out successfully');
9099
});
91100

92101
it('on UpdateActiveEntryFail, isLoading is false', () => {

src/app/modules/time-clock/store/entry.reducer.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
5252
active: action.payload,
5353
entryList: [...state.entryList, action.payload],
5454
isLoading: false,
55-
message: 'Entry Created',
55+
message: 'You clocked-in successfully',
5656
};
5757
}
5858

@@ -65,14 +65,14 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
6565
};
6666
}
6767

68-
case EntryActionTypes.UDPATE_ACTIVE_ENTRY: {
68+
case EntryActionTypes.UPDATE_ACTIVE_ENTRY: {
6969
return {
7070
...state,
7171
isLoading: true,
7272
};
7373
}
7474

75-
case EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS: {
75+
case EntryActionTypes.UPDATE_ACTIVE_ENTRY_SUCCESS: {
7676
const activeEntry = { ...state.active, ...action.payload };
7777

7878
return {
@@ -82,7 +82,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
8282
};
8383
}
8484

85-
case EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL: {
85+
case EntryActionTypes.UPDATE_ACTIVE_ENTRY_FAIL: {
8686
return {
8787
...state,
8888
active: null,
@@ -102,7 +102,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
102102
...state,
103103
active: null,
104104
isLoading: false,
105-
message: 'You just clocked-out successfully',
105+
message: 'You clocked-out successfully',
106106
};
107107
}
108108

0 commit comments

Comments
 (0)