Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 1 deletion .github/workflows/CI-time-tracker-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ jobs:
STACK_EXCHANGE_ID: ${{ secrets.STACK_EXCHANGE_ID }}
STACK_EXCHANGE_ACCESS_TOKEN: ${{ secrets.STACK_EXCHANGE_ACCESS_TOKEN }}
AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${{ secrets.AZURE_APP_CONFIGURATION_CONNECTION_STRING }}
run: sudo sh scripts/populate-keys.sh
run: |
chmod +x ./scripts/populate-keys.sh
sh ./scripts/populate-keys.sh

- name: Running tests
run: npm run ci-test --if-present
Expand Down
4 changes: 3 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
],
"styles": [
"node_modules/datatables.net-dt/css/jquery.dataTables.css",
"node_modules/datatables.net-responsive-dt/css/responsive.dataTables.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss",
"node_modules/ngx-toastr/toastr.css",
Expand All @@ -39,7 +40,8 @@
"node_modules/datatables.net-buttons/js/buttons.colVis.js",
"node_modules/datatables.net-buttons/js/buttons.flash.js",
"node_modules/datatables.net-buttons/js/buttons.html5.js",
"node_modules/datatables.net-buttons/js/buttons.print.js"
"node_modules/datatables.net-buttons/js/buttons.print.js",
"node_modules/datatables.net-responsive/js/dataTables.responsive.js"
]
},
"configurations": {
Expand Down
8 changes: 0 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@
"@angular/platform-browser": "~10.2.2",
"@angular/platform-browser-dynamic": "~10.2.2",
"@angular/router": "~10.2.2",
"@azure/app-configuration": "^1.1.0",
"@azure/identity": "^1.1.0",
"@ngrx/effects": "^10.0.1",
"@ngrx/store": "^10.0.1",
"@ngrx/store-devtools": "^10.0.1",
"@types/datatables.net-buttons": "^1.4.3",
"angular-datatables": "^9.0.2",
"@azure/app-configuration": "^1.1.0",
"@azure/identity": "^1.1.0",
"bootstrap": "^4.4.1",
"datatables.net": "^1.10.21",
"datatables.net-buttons": "^1.6.2",
"datatables.net-buttons-dt": "^1.6.2",
"datatables.net-dt": "^1.10.21",
"datatables.net-responsive": "^2.2.6",
"datatables.net-responsive-dt": "^2.2.6",
"jquery": "^3.5.1",
"jszip": "^3.4.0",
"minimist": "^1.2.5",
Expand Down
3 changes: 3 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { HomeComponent } from './modules/home/home.component';
import { LoginComponent } from './modules/login/login.component';
import { CustomerComponent } from './modules/customer-management/pages/customer.component';
import { UsersComponent } from './modules/users/pages/users.component';
import { TechnologyReportComponent } from './modules/technology-report/pages/technology-report.component';
import { TechnologiesReportGuard } from './guards/technologies-report-guard/technologies-report.guard';

const routes: Routes = [
{
Expand All @@ -24,6 +26,7 @@ const routes: Routes = [
{ path: 'activities-management', component: ActivitiesManagementComponent },
{ path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent },
{ path: 'users', canActivate: [AdminGuard], component: UsersComponent },
{ path: 'technology-report', canActivate: [AdminGuard, TechnologiesReportGuard], component: TechnologyReportComponent},
{ path: '', pathMatch: 'full', redirectTo: 'time-clock' },
],
},
Expand Down
9 changes: 7 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
import { CommonModule, DatePipe } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { NgModule, Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { DataTablesModule } from 'angular-datatables';
Expand Down Expand Up @@ -73,6 +73,9 @@ import { LoadingBarComponent } from './modules/shared/components/loading-bar/loa
import { UsersComponent } from './modules/users/pages/users.component';
import { UsersListComponent } from './modules/users/components/users-list/users-list.component';
import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker';
// tslint:disable-next-line: max-line-length
import { TechnologyReportTableComponent } from './modules/technology-report/components/technology-report-table/technology-report-table.component';
import { TechnologyReportComponent } from './modules/technology-report/pages/technology-report.component';

const maskConfig: Partial<IConfig> = {
validation: false,
Expand Down Expand Up @@ -122,7 +125,9 @@ const maskConfig: Partial<IConfig> = {
DialogComponent,
LoadingBarComponent,
UsersComponent,
UsersListComponent
UsersListComponent,
TechnologyReportComponent,
TechnologyReportTableComponent,
],
imports: [
NgxMaskModule.forRoot(maskConfig),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { inject, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable, of } from 'rxjs';
import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service';
import { TechnologiesReportGuard } from './technologies-report.guard';

describe('TechnologiesReportGuard', () => {

let technologiesReportGuard: TechnologiesReportGuard;
let featureManagerService: FeatureManagerService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule ]
});
technologiesReportGuard = TestBed.inject(TechnologiesReportGuard);
featureManagerService = TestBed.inject(FeatureManagerService);
});

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

it('can activate the route when feature is enabled for user', () => {
spyOn(featureManagerService, 'isToggleEnabledForUser').and.returnValue(of(true));

const canActivate: Observable<boolean> = technologiesReportGuard.canActivate();

expect(featureManagerService.isToggleEnabledForUser).toHaveBeenCalled();
canActivate.subscribe(value => expect(value).toEqual(true));
});

it('can not active the route and is redirected to home if feature is not enabled for user', inject([Router], (router: Router) => {
spyOn(featureManagerService, 'isToggleEnabledForUser').and.returnValue(of(false));
spyOn(router, 'navigate').and.stub();

const canActivate: Observable<boolean> = technologiesReportGuard.canActivate();

expect(featureManagerService.isToggleEnabledForUser).toHaveBeenCalled();
canActivate.subscribe(value => expect(value).toEqual(false));
expect(router.navigate).toHaveBeenCalledWith(['']);
}));

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { map } from 'rxjs/operators';
import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service';

@Injectable({
providedIn: 'root'
})
export class TechnologiesReportGuard implements CanActivate {

constructor(
private featureManagerService: FeatureManagerService,
private router: Router
) { }

canActivate() {
return this.featureManagerService
.isToggleEnabledForUser('ui-list-technologies')
.pipe(map((enabled) => {
if (enabled === true) {
return true;
} else {
this.router.navigate(['']);
return false;
}
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ToastrService } from 'ngx-toastr';
import { formatDate } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { DATE_FORMAT } from 'src/environments/environment';
import * as entryActions from '../../../time-clock/store/entry.actions';
import {Store} from '@ngrx/store';
import {EntryState} from '../../../time-clock/store/entry.reducer';
Expand All @@ -28,8 +29,8 @@ export class TimeRangeFormComponent implements OnInit {

setInitialDataOnScreen() {
this.reportForm.setValue({
startDate: formatDate(moment().startOf('week').toString(), 'yyyy-MM-dd', 'en'),
endDate: formatDate(moment().endOf('week').toString(), 'yyyy-MM-dd', 'en')
startDate: formatDate(moment().startOf('week').toString(), DATE_FORMAT, 'en'),
endDate: formatDate(moment().endOf('week').toString(), DATE_FORMAT, 'en')
});
this.onSubmit();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { ProjectSelectedEvent } from './project-selected-event';
import { SaveEntryEvent } from './save-entry-event';
import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker';

import { DATE_FORMAT } from 'src/environments/environment';
import { DATE_FORMAT_YEAR } from 'src/environments/environment';

describe('DetailsFieldsComponent', () => {
type Merged = TechnologyState & ProjectState & EntryState;
Expand Down Expand Up @@ -67,8 +69,8 @@ describe('DetailsFieldsComponent', () => {
project_name: '',
activity_id: '',
uri: '',
start_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
end_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
start_date: formatDate(new Date(), DATE_FORMAT, 'en'),
end_date: formatDate(new Date(), DATE_FORMAT, 'en'),
start_hour: '00:00',
end_hour: '00:00',
description: '',
Expand Down Expand Up @@ -179,8 +181,8 @@ describe('DetailsFieldsComponent', () => {
project_name: '',
activity_id: '',
uri: '',
start_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
end_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
start_date: formatDate(new Date(), DATE_FORMAT, 'en'),
end_date: formatDate(new Date(), DATE_FORMAT, 'en'),
start_hour: '00:00',
end_hour: '00:00',
description: '',
Expand Down Expand Up @@ -315,7 +317,7 @@ describe('DetailsFieldsComponent', () => {
it('displays error message when the date selected is in the future', () => {
spyOn(toastrServiceStub, 'error');

const futureDate = moment().add(1, 'days').format('YYYY-MM-DD');
const futureDate = moment().add(1, 'days').format(DATE_FORMAT_YEAR);
component.entryForm.setValue({ ...formValues, start_date: futureDate, end_date: futureDate });
component.onSubmit();

Expand All @@ -325,8 +327,8 @@ describe('DetailsFieldsComponent', () => {
it('when start_date is in the future and end_date is OK then throws an error', () => {
spyOn(toastrServiceStub, 'error');

const futureDate = moment().add(1, 'days').format('YYYY-MM-DD');
const currentDate = moment().format('YYYY-MM-DD');
const futureDate = moment().add(1, 'days').format(DATE_FORMAT_YEAR);
const currentDate = moment().format(DATE_FORMAT_YEAR);
component.entryForm.setValue({ ...formValues, start_date: futureDate, end_date: currentDate });
component.onSubmit();

Expand All @@ -336,8 +338,8 @@ describe('DetailsFieldsComponent', () => {
it('when start_date is OK and end_date is in the future then throws an error future', () => {
spyOn(toastrServiceStub, 'error');

const futureDate = moment().add(1, 'days').format('YYYY-MM-DD');
const currentDate = moment().format('YYYY-MM-DD');
const futureDate = moment().add(1, 'days').format(DATE_FORMAT_YEAR);
const currentDate = moment().format(DATE_FORMAT_YEAR);
component.entryForm.setValue({ ...formValues, start_date: currentDate, end_date: futureDate });
component.onSubmit();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { EntryActionTypes } from './../../../time-clock/store/entry.actions';
import { SaveEntryEvent } from './save-entry-event';
import { ProjectSelectedEvent } from './project-selected-event';
import { get } from 'lodash';
import { DATE_FORMAT } from 'src/environments/environment';


type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
Expand Down Expand Up @@ -131,8 +132,8 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
project_id: this.entryToEdit.project_id,
activity_id: this.entryToEdit.activity_id,
description: this.entryToEdit.description,
start_date: formatDate(get(this.entryToEdit, 'start_date', '') , 'yyyy-MM-dd', 'en'),
end_date: formatDate(get(this.entryToEdit, 'end_date'), 'yyyy-MM-dd', 'en'),
start_date: formatDate(get(this.entryToEdit, 'start_date', '') ,DATE_FORMAT, 'en'),
end_date: formatDate(get(this.entryToEdit, 'end_date'), DATE_FORMAT, 'en'),
start_hour: formatDate(get(this.entryToEdit, 'start_date', '00:00'), 'HH:mm', 'en'),
end_hour: formatDate(get(this.entryToEdit, 'end_date', '00:00'), 'HH:mm', 'en'),
uri: this.entryToEdit.uri,
Expand All @@ -150,8 +151,8 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
project_id: '',
activity_id: '',
description: '',
start_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
end_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
start_date: formatDate(new Date(), DATE_FORMAT, 'en'),
end_date: formatDate(new Date(), DATE_FORMAT, 'en'),
start_hour: '00:00',
end_hour: '00:00',
uri: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import {RouterTestingModule} from '@angular/router/testing';
import {Router, Routes} from '@angular/router';
import {TimeClockComponent} from '../../../time-clock/pages/time-clock.component';
import {provideMockStore} from '@ngrx/store/testing';
import {of} from 'rxjs';
import {FeatureManagerService} from '../../feature-toggles/feature-toggle-manager.service';

describe('SidebarComponent', () => {
let component: SidebarComponent;
let fixture: ComponentFixture<SidebarComponent>;
let azureAdB2CServiceStubInjected;
let featureManagerServiceStubInjected: FeatureManagerService;
let router;
const routes: Routes = [
{path: 'time-clock', component: TimeClockComponent}
Expand Down Expand Up @@ -41,6 +44,7 @@ describe('SidebarComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(SidebarComponent);
azureAdB2CServiceStubInjected = TestBed.inject(AzureAdB2CService);
featureManagerServiceStubInjected = TestBed.inject(FeatureManagerService);
component = fixture.componentInstance;
fixture.detectChanges();
});
Expand Down Expand Up @@ -79,4 +83,23 @@ describe('SidebarComponent', () => {
expect(item.active).toBeFalse();
});
});

it('List Technologies item is added when feature flag "ui-list-technologies" is enabled for user', () => {
spyOn(featureManagerServiceStubInjected, 'isToggleEnabledForUser').and.returnValue(of(true));
const itemsSidebar = [];

component.toggleListTechnologies(itemsSidebar);

expect(itemsSidebar.length).toBe(1);
});

it('List Technologies item is not added when feature flag "ui-list-technologies" is disabled for user', () => {
spyOn(featureManagerServiceStubInjected, 'isToggleEnabledForUser').and.returnValue(of(false));
const itemsSidebar = [];

component.toggleListTechnologies(itemsSidebar);

expect(itemsSidebar.length).toBe(0);
});

});
24 changes: 23 additions & 1 deletion src/app/modules/shared/components/sidebar/sidebar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {ItemSidebar} from './models/item-sidebar.model';
import {NavigationStart, Router} from '@angular/router';
import {Observable} from 'rxjs';
import {filter} from 'rxjs/operators';
import { FeatureManagerService } from '../../feature-toggles/feature-toggle-manager.service';

@Component({
selector: 'app-sidebar',
Expand All @@ -15,7 +16,11 @@ export class SidebarComponent implements OnInit {
itemsSidebar: ItemSidebar[] = [];
navStart;

constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) {
constructor(
private azureAdB2CService: AzureAdB2CService,
private router: Router,
private featureManagerService: FeatureManagerService,
) {
this.navStart = this.router.events.pipe(
filter(evt => evt instanceof NavigationStart)
) as Observable<NavigationStart>;
Expand All @@ -24,6 +29,7 @@ export class SidebarComponent implements OnInit {
ngOnInit(): void {
this.toggleSideBar();
this.getSidebarItems();
this.toggleListTechnologies(this.itemsSidebar);
this.highlightMenuOption(this.router.routerState.snapshot.url);
this.navStart.subscribe(evt => {
this.highlightMenuOption(evt.url);
Expand Down Expand Up @@ -55,6 +61,22 @@ export class SidebarComponent implements OnInit {
}
}

toggleListTechnologies(itemsSidebar: ItemSidebar[]){
this.featureManagerService
.isToggleEnabledForUser('ui-list-technologies')
.subscribe((enabled) => {
if (enabled === true){
const listTechnologiesItem = {
route: '/technology-report',
icon: 'fas fa-user',
text: 'Technology Report',
active: false
};
itemsSidebar.push(listTechnologiesItem);
}
});
}

highlightMenuOption(route) {
this.itemsSidebar.map(item => item.active = false);
this.itemsSidebar.filter(item => item.route === route).map(item => item.active = true);
Expand Down
Loading