From 5d29b8e60ce6cf8c5ee9cc29084520a2e5891a7e Mon Sep 17 00:00:00 2001 From: Oscar Pallazhco Date: Thu, 20 Jan 2022 14:21:55 -0500 Subject: [PATCH 01/17] Tt 501 integrate reports with the new backend (#797) * refactor: TT-501 change of route * refactor: TT-501 change of name of route * refactor: TT-501 just change the url when in development Co-authored-by: Davcg5 --- .../modules/time-clock/services/entry.service.spec.ts | 11 ++++++----- src/app/modules/time-clock/services/entry.service.ts | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/modules/time-clock/services/entry.service.spec.ts b/src/app/modules/time-clock/services/entry.service.spec.ts index 3bfe37681..91c39a42a 100644 --- a/src/app/modules/time-clock/services/entry.service.spec.ts +++ b/src/app/modules/time-clock/services/entry.service.spec.ts @@ -105,10 +105,10 @@ describe('EntryService', () => { const pipe: DatePipe = new DatePipe('en'); const timeRange: TimeEntriesTimeRange = {start_date: yesterday, end_date: today}; const userId = '123'; - + const reportsUrl = service.urlInProduction ? service.baseUrl : service.baseUrl + '/report'; service.loadEntriesByTimeRange(timeRange, userId).subscribe(); - const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === service.baseUrl); + const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === reportsUrl); expect(loadEntryRequest.request.params.get('start_date')).toBe(pipe.transform(yesterday, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT)); expect(loadEntryRequest.request.params.get('end_date')).toBe(pipe.transform(today, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT)); @@ -120,10 +120,10 @@ describe('EntryService', () => { const today = moment(new Date()); const timeRange: TimeEntriesTimeRange = { start_date: yesterday, end_date: today }; const userId = '123'; - + const reportsUrl = service.urlInProduction ? service.baseUrl : service.baseUrl + '/report'; service.loadEntriesByTimeRange(timeRange, userId).subscribe(); - const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === service.baseUrl); + const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === reportsUrl); expect(loadEntryRequest.request.params.get('limit')).toEqual('9999'); }); @@ -132,10 +132,11 @@ describe('EntryService', () => { const today = moment(new Date()); const timeRange: TimeEntriesTimeRange = { start_date: yesterday, end_date: today }; const userId = '123'; + const reportsUrl = service.urlInProduction ? service.baseUrl : service.baseUrl + '/report'; service.loadEntriesByTimeRange(timeRange, userId).subscribe(); - const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === service.baseUrl); + const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === reportsUrl); const timezoneOffset = new Date().getTimezoneOffset().toString(); expect(loadEntryRequest.request.params.get('timezone_offset')).toEqual(timezoneOffset); diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index fa3e5bd24..4d92fbea7 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -68,7 +68,8 @@ export class EntryService { loadEntriesByTimeRange(range: TimeEntriesTimeRange, userId: string): Observable { const MAX_NUMBER_OF_ENTRIES_FOR_REPORTS = 9999; - return this.http.get(this.baseUrl, + const loadEntriesByTimeRangeURL = this.urlInProduction ? this.baseUrl : this.baseUrl + '/report'; + return this.http.get(loadEntriesByTimeRangeURL, { params: { start_date: this.datePipe.transform(range.start_date, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT), From d283ed77a3bf80a7a193ee14c4552e29221729b0 Mon Sep 17 00:00:00 2001 From: Bryan Aguilar <47910273+bryanAguilar001@users.noreply.github.com> Date: Fri, 28 Jan 2022 15:11:52 -0500 Subject: [PATCH 02/17] test: TT-281 UI Test Coverage Second Part (#800) * test: TT-281 UI Test Coverage Second Part * test: TT-281 UI Test Coverage Second Part * test: TT-281 UI Test Coverage Second Part * test: TT-281 UI Test Coverage Second Part --- .../store/activity-management.actions.ts | 8 +- .../activity-management.reducers.spec.ts | 21 +++++ ...gement-customer-projects.component.spec.ts | 13 +++ .../project-type-list.component.spec.ts | 13 +++ .../store/project-type.reducers.spec.ts | 2 +- .../services/project.service.spec.ts | 10 +++ .../components/store/project.actions.ts | 8 +- .../components/store/project.reducer.spec.ts | 27 +++++++ .../store/customer-management.actions.ts | 8 +- .../customer-management.reducers.spec.ts | 6 ++ .../services/azure.ad.b2c.service.spec.ts | 7 ++ .../time-entries-table.component.spec.ts | 12 +++ .../details-fields.component.spec.ts | 22 ++++- .../input-date/input-date.component.spec.ts | 36 +++++++++ .../feature-toggles.service.spec.ts | 50 +++++++----- .../shared/store/technology.actions.ts | 7 +- .../shared/store/technology.reducers.spec.ts | 7 ++ .../pages/time-clock.component.spec.ts | 6 ++ .../time-clock/store/entry.effects.spec.ts | 10 +++ .../time-clock/store/entry.reducer.spec.ts | 81 ++++++++++++++++++- .../pages/time-entries.component.spec.ts | 46 +++++++++++ .../pages/time-entries.component.ts | 2 +- src/app/modules/user/store/user.actions.ts | 7 +- .../modules/user/store/user.reducer.spec.ts | 9 ++- .../modules/users/store/user.reducers.spec.ts | 2 +- 25 files changed, 388 insertions(+), 32 deletions(-) diff --git a/src/app/modules/activities-management/store/activity-management.actions.ts b/src/app/modules/activities-management/store/activity-management.actions.ts index 95094b412..bf69a3c31 100644 --- a/src/app/modules/activities-management/store/activity-management.actions.ts +++ b/src/app/modules/activities-management/store/activity-management.actions.ts @@ -20,6 +20,7 @@ export enum ActivityManagementActionTypes { UNARCHIVE_ACTIVITY_FAIL = '[ActivityManagement] UNARCHIVE_ACTIVITY_FAIL', SET_ACTIVITY_ID_TO_EDIT = '[ActivityManagement] SET_ACTIVITY_ID_TO_EDIT', RESET_ACTIVITY_ID_TO_EDIT = '[ActivityManagement] RESET_ACTIVITY_ID_TO_EDIT', + DEFAULT_ACTIVITY = '[ActivityManagement] DEFAULT_ACTIVITY', } export class LoadActivities implements Action { @@ -118,6 +119,10 @@ export class ResetActivityToEdit implements Action { public readonly type = ActivityManagementActionTypes.RESET_ACTIVITY_ID_TO_EDIT; } +export class DefaultActivities implements Action { + public readonly type = ActivityManagementActionTypes.DEFAULT_ACTIVITY; +} + export type ActivityManagementActions = | LoadActivities | LoadActivitiesSuccess @@ -135,4 +140,5 @@ export type ActivityManagementActions = | UnarchiveActivitySuccess | UnarchiveActivityFail | SetActivityToEdit - | ResetActivityToEdit; + | ResetActivityToEdit + | DefaultActivities; diff --git a/src/app/modules/activities-management/store/activity-management.reducers.spec.ts b/src/app/modules/activities-management/store/activity-management.reducers.spec.ts index 7da4d4419..560e063a4 100644 --- a/src/app/modules/activities-management/store/activity-management.reducers.spec.ts +++ b/src/app/modules/activities-management/store/activity-management.reducers.spec.ts @@ -142,4 +142,25 @@ describe('activityManagementReducer', () => { expect(state.message).toEqual('Something went wrong unarchiving activities!'); expect(state.isLoading).toBeFalse(); }); + + it('on SetActivityidToEdit, message equal to \"Set activityIdToEdit property\"', () => { + const action = new actions.SetActivityToEdit('1'); + const state = activityManagementReducer(initialState, action); + expect(state.message).toEqual('Set activityIdToEdit property'); + expect(state.isLoading).toBeFalse(); + }); + + it('on ResetActivityIdToEdit, message equal to \"Reset activityIdToEdit property\"', () => { + const action = new actions.ResetActivityToEdit(); + const state = activityManagementReducer(initialState, action); + expect(state.message).toEqual('Reset activityIdToEdit property'); + expect(state.isLoading).toBeFalse(); + }); + + it('on DefaultAction, state equal to initial state', () => { + const action = new actions.DefaultActivities(); + const state = activityManagementReducer(initialState, action); + expect(state.data).toEqual(initialState.data); + }); + }); diff --git a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts index 90618e4ee..3668b3a1d 100644 --- a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts +++ b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts @@ -3,6 +3,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ManagementCustomerProjectsComponent } from './management-customer-projects.component'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { CustomerState } from '../../store'; +import { of } from 'rxjs'; describe('ManagmentCustomerProjectsComponent', () => { let component: ManagementCustomerProjectsComponent; @@ -38,6 +39,18 @@ describe('ManagmentCustomerProjectsComponent', () => { expect(component).toBeTruthy(); }); + it('set customerName on ngOnInit', () => { + spyOn(store, 'dispatch'); + spyOn(store, 'pipe').and.returnValue(of({ + id: 1, + name: 'project 4', + description: 'project 1 to test methos projectss', + status: 'inactive' + })); + component.ngOnInit(); + expect(component.customerName).toEqual('project 4'); + }); + it('should be enable tabs', () => { component.areTabsActive = false; component.activeTabs(true); diff --git a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts index e6b23b93f..beaac1a18 100644 --- a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts +++ b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts @@ -5,6 +5,7 @@ import { NgxPaginationModule } from 'ngx-pagination'; import { DeleteProjectType, SetProjectTypeToEdit } from './../../store/project-type.actions'; import { allProjectTypes, ProjectTypeState } from '../../store'; import { ProjectTypeListComponent } from './project-type-list.component'; +import { ProjectType } from 'src/app/modules/shared/models'; describe('ProjectTypeTableListComponent', () => { let component: ProjectTypeListComponent; @@ -68,4 +69,16 @@ describe('ProjectTypeTableListComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new SetProjectTypeToEdit('id')); }); + it('open the modal with the correct parameters', () => { + const item: ProjectType = { + id: `1`, + name: `Project 1`, + description: `Description project 1` + }; + component.openModal(item); + expect(component.idToDelete).toEqual(item.id); + expect(component.message).toEqual(`Are you sure you want to delete ${item.name}?`); + expect(component.showModal).toBe(true); + }); + }); diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts index e87804249..b46d0fe2c 100644 --- a/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts @@ -6,7 +6,7 @@ describe('projectTypeReducer', () => { const initialState: ProjectTypeState = { data: [], isLoading: false, message: '', projectTypeIdToEdit: '' }; const projectType: ProjectType = { id: '1', name: 'Training', description: 'It is good for learning' }; - it('on Default, ', () => { + it('on DefaultAction, state equal to initial state', () => { const action = new actions.DefaultProjectTypes(); const state = projectTypeReducer(initialState, action); expect(state.data).toEqual(initialState.data); diff --git a/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts b/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts index 496867eef..f1fd70831 100644 --- a/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts @@ -48,6 +48,16 @@ describe('ProjectService', () => { } )); + it('all projects are read using GET from url', () => { + const projectsFoundSize = projectsList.length; + service.getAllProjects().subscribe((projectsInResponse) => { + expect(projectsInResponse.length).toBe(projectsFoundSize); + }); + const getProjectsRequest = httpMock.expectOne(service.url); + expect(getProjectsRequest.request.method).toBe('GET'); + getProjectsRequest.flush(projectsList); + }); + it('projects are read using GET from url', () => { const projectsFoundSize = projectsList.length; service.url = '/projects'; diff --git a/src/app/modules/customer-management/components/projects/components/store/project.actions.ts b/src/app/modules/customer-management/components/projects/components/store/project.actions.ts index d0c26cc24..ef75d7efe 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.actions.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.actions.ts @@ -25,6 +25,7 @@ export enum ProjectActionTypes { UNARCHIVE_PROJECT = '[Projects] UNARCHIVE_PROJECT', UNARCHIVE_PROJECT_SUCCESS = '[Projects] UNARCHIVE_PROJECT_SUCCESS', UNARCHIVE_PROJECT_FAIL = '[Projects] UNARCHIVE_PROJECT_FAIL', + DEFAULT_PROJECT = '[Projects] DEFAULT_PROJECTS', } export class CleanCustomerProjects implements Action { @@ -154,6 +155,10 @@ export class UnarchiveProjectFail implements Action { constructor(public error: string) {} } +export class DefaultProjects implements Action { + public readonly type = ProjectActionTypes.DEFAULT_PROJECT; +} + export type ProjectActions = | CleanCustomerProjects | LoadProjects @@ -177,4 +182,5 @@ export type ProjectActions = | DeleteProjectFail | UnarchiveProject | UnarchiveProjectSuccess - | UnarchiveProjectFail; + | UnarchiveProjectFail + | DefaultProjects; diff --git a/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts b/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts index cf387017a..4e3cda813 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts @@ -22,6 +22,26 @@ describe('projectReducer', () => { }); it('on LoadProjects, isLoading is true', () => { + const action = new actions.LoadProjects(); + const state = projectReducer(initialState, action); + expect(state.isLoading).toEqual(true); + }); + + it('on LoadProjectsSucces, projectsFound are saved in the store', () => { + const projectsFound: Project[] = [project]; + const action = new actions.LoadProjectsSuccess(projectsFound); + const state = projectReducer(initialState, action); + expect(action.payload).toEqual([project]); + expect(state.isLoading).toEqual(false); + }); + + it('on LoadProjectsFail, loadProject equal []', () => { + const action = new actions.LoadProjectsFail('error'); + const state = projectReducer(initialState, action); + expect(state.isLoading).toEqual(false); + }); + + it('on LoadCustomerProjects, isLoading is true', () => { const action = new actions.LoadCustomerProjects('1'); const state = projectReducer(initialState, action); expect(state.isLoading).toEqual(true); @@ -201,4 +221,11 @@ describe('projectReducer', () => { expect(state.message).toEqual('Something went wrong unarchiving projects!'); expect(state.isLoading).toEqual(false); }); + + it('on DefaultAction, state equal to initial state', () => { + const action = new actions.DefaultProjects(); + const state = projectReducer(initialState, action); + expect(state).toEqual(initialState); + }); + }); diff --git a/src/app/modules/customer-management/store/customer-management.actions.ts b/src/app/modules/customer-management/store/customer-management.actions.ts index 2dc9ff43d..dc7a6c100 100644 --- a/src/app/modules/customer-management/store/customer-management.actions.ts +++ b/src/app/modules/customer-management/store/customer-management.actions.ts @@ -19,6 +19,7 @@ export enum CustomerManagementActionTypes { UNARCHIVE_CUSTOMER = '[CustomerManagement] UNARCHIVE_CUSTOMER', UNARCHIVE_CUSTOMER_SUCCESS = '[CustomerManagement] UNARCHIVE_CUSTOMER_SUCCESS', UNARCHIVE_CUSTOMER_FAIL = '[CustomerManagement] UNARCHIVE_CUSTOMER_FAIL', + DEFAULT_CUSTOMER = '[CustomerManagement] DEFAULT_CUSTOMER', } export class LoadCustomers implements Action { @@ -118,6 +119,10 @@ export class UnarchiveCustomerFail implements Action { constructor(public error: string) {} } +export class DefaultCustomer implements Action { + public readonly type = CustomerManagementActionTypes.DEFAULT_CUSTOMER; +} + export type CustomerManagementActions = | CreateCustomer | CreateCustomerSuccess @@ -135,4 +140,5 @@ export type CustomerManagementActions = | ResetCustomerToEdit | UnarchiveCustomer | UnarchiveCustomerSuccess - | UnarchiveCustomerFail; + | UnarchiveCustomerFail + | DefaultCustomer; diff --git a/src/app/modules/customer-management/store/customer-management.reducers.spec.ts b/src/app/modules/customer-management/store/customer-management.reducers.spec.ts index e662e6277..7dd8911ff 100644 --- a/src/app/modules/customer-management/store/customer-management.reducers.spec.ts +++ b/src/app/modules/customer-management/store/customer-management.reducers.spec.ts @@ -166,4 +166,10 @@ describe('customerManagementReducer', () => { expect(state.message).toEqual('Something went wrong unarchiving customer!'); expect(state.isLoading).toEqual(false); }); + + it('on DefaultAction, state equal to initial state', () => { + const action = new actions.DefaultCustomer(); + const state = customerManagementReducer(initialState, action); + expect(state.data).toEqual(initialState.data); + }); }); diff --git a/src/app/modules/login/services/azure.ad.b2c.service.spec.ts b/src/app/modules/login/services/azure.ad.b2c.service.spec.ts index 8020c05f9..baf6a364c 100644 --- a/src/app/modules/login/services/azure.ad.b2c.service.spec.ts +++ b/src/app/modules/login/services/azure.ad.b2c.service.spec.ts @@ -21,6 +21,7 @@ describe('AzureAdB2CService', () => { name: 'abc', idToken: { iss: ' http://hostname.com/12345/v0/', + emails: 'abcd' }, idTokenClaims: {}, sid: 'abc', @@ -179,4 +180,10 @@ describe('AzureAdB2CService', () => { expect(UserAgentApplication.prototype.getAccount).toHaveBeenCalled(); }); + + it('should get email', () => { + spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValues(account); + const email = service.getUserEmail(); + expect(email).toEqual('a'); + }); }); diff --git a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.spec.ts b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.spec.ts index 9712a7544..4f7c460f1 100644 --- a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.spec.ts +++ b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.spec.ts @@ -1,5 +1,6 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { DataTableDirective } from 'angular-datatables'; import { Entry } from 'src/app/modules/shared/models'; import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe'; import { getReportDataSource } from 'src/app/modules/time-clock/store/entry.selectors'; @@ -134,6 +135,17 @@ describe('Reports Page', () => { expect(component.bodyExportOptions(entry, row, column, node)).toBe('https://TT-392-uri'); }); + it('when the rerenderDataTable method is called and dtElement and dtInstance are defined, the destroy and next methods are called ', + () => { + component.dtElement = { + dtInstance: { + then : (dtInstance: DataTables.Api) => { dtInstance.destroy(); } + } + } as unknown as DataTableDirective; + spyOn(component.dtElement.dtInstance, 'then'); + component.ngAfterViewInit(); + expect(component.dtElement.dtInstance.then).toHaveBeenCalled(); + }); afterEach(() => { fixture.destroy(); diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts b/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts index bf029c755..4e452ede8 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts +++ b/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts @@ -240,7 +240,6 @@ describe('DetailsFieldsComponent', () => { technology: '', }; component.ngOnChanges(); - expect(component.shouldRestartEntry).toBeFalse(); expect(component.entryForm.value).toEqual(formValue); component.activities$.subscribe((item) => { @@ -730,6 +729,27 @@ describe('DetailsFieldsComponent', () => { expect(numberinISOFormat).toBe(expectedISOFormatNumbers[numberIndex]); }); }); + + it('when the user selects technologies, set them in the variable selectedTechnologies', () => { + const techs = ['php', 'angular']; + component.onTechnologiesUpdated(techs); + expect(component.selectedTechnologies).toEqual(techs); + }); + + it('when the user does not select a project, display a warning message.', () => { + spyOn(toastrServiceStub, 'warning'); + component.onclickFormAction(true); + expect(toastrServiceStub.warning).toHaveBeenCalled(); + }); + + it('if entry is set to project_name search_fiend is assigned in entryForm', () => { + const listProjects: Project[] = [{ id: 'id', name: 'abc', status: 'active', search_field: 'name'}]; + component.listProjects = listProjects; + component.entryToEdit = { ...entryToEdit }; + component.ngOnChanges(); + expect(component.entryForm.value.project_name).toBe('name'); + }); + /* TODO As part of https://github.com/ioet/time-tracker-ui/issues/424 a new parameter was added to the details-field-component, and now these couple of tests are failing. A solution to this error might be generate a Test Wrapper Component. More details here: diff --git a/src/app/modules/shared/components/input-date/input-date.component.spec.ts b/src/app/modules/shared/components/input-date/input-date.component.spec.ts index bf8e50de0..f2ccb6753 100644 --- a/src/app/modules/shared/components/input-date/input-date.component.spec.ts +++ b/src/app/modules/shared/components/input-date/input-date.component.spec.ts @@ -37,6 +37,42 @@ describe('InputDateComponent', () => { expect(component.value).toEqual('2020-05-20'); })); + it('it calls the close method if opened equals true', () => { + const datepicker: any = { opened : true, open : () => ({}), close : () => ({}) }; + spyOn(datepicker, 'close'); + component.openOrCloseDatePicker(datepicker); + expect(datepicker.close).toHaveBeenCalled(); + }); + + it('it calls the open method if opened equals false', () => { + const datepicker: any = { opened : false, open : () => ({}), close : () => ({}) }; + spyOn(datepicker, 'open'); + component.openOrCloseDatePicker(datepicker); + expect(datepicker.open).toHaveBeenCalled(); + }); + + it('isDisabled should be true if parameter is true', () => { + component.setDisabledState(true); + expect(component.isDisabled).toBe(true); + }); + + it('isDisabled should be false if parameter is false', () => { + component.setDisabledState(false); + expect(component.isDisabled).toBe(false); + }); + + it(`value should be '' in writeValue function when parameter is null`, () => { + const value: any = null; + component.writeValue(value); + expect(component.value).toEqual(''); + }); + + it(`value should be '' in writeValue function when parameter is not defined`, () => { + const value: any = undefined; + component.writeValue(value); + expect(component.value).toEqual(''); + }); + const params: boolean[] = [true, false]; params.forEach(disable => { it('when the disabled attribute is provided, it should disable the input ', fakeAsync(() => { diff --git a/src/app/modules/shared/feature-toggles/feature-toggles.service.spec.ts b/src/app/modules/shared/feature-toggles/feature-toggles.service.spec.ts index 6f4bac558..e2fa4f7c2 100644 --- a/src/app/modules/shared/feature-toggles/feature-toggles.service.spec.ts +++ b/src/app/modules/shared/feature-toggles/feature-toggles.service.spec.ts @@ -11,29 +11,36 @@ describe('FeatureTogglesService', () => { conditions: null, }; - it('toggles are read using azure configuration client', async () => { - const fakeAppConfigurationConnectionString = 'Endpoint=http://fake.foo;Id=fake.id;Secret=fake.secret'; - const featureToggleKey = 'foo'; - const featureToggleLabel = 'dev'; - const fakeResponse: GetConfigurationSettingResponse = { - isReadOnly: true, - key: featureToggleKey, - _response: { - request: null, - bodyAsText: 'any-response', - headers: null, - parsedHeaders: null, - status: 200 - }, - statusCode: 200, - value: JSON.stringify(anyToggle) - }; - const fakeConfigurationClient = new AppConfigurationClient(fakeAppConfigurationConnectionString); - const fakeGetConfigurationSetting = spyOn(fakeConfigurationClient, 'getConfigurationSetting').and.callFake( - () => of(fakeResponse).toPromise()); + const featureToggleKey = 'foo'; + const featureToggleLabel = 'dev'; - const service = new FeatureTogglesService(fakeConfigurationClient); + const fakeResponse: GetConfigurationSettingResponse = { + isReadOnly: true, + key: featureToggleKey, + _response: { + request: null, + bodyAsText: 'any-response', + headers: null, + parsedHeaders: null, + status: 200 + }, + statusCode: 200, + value: JSON.stringify(anyToggle) + }; + let fakeGetConfigurationSetting; + const fakeAppConfigurationConnectionString = 'Endpoint=http://fake.foo;Id=fake.id;Secret=fake.secret'; + let fakeAppConfigurationClient: any; + let service: FeatureTogglesService; + + beforeEach(() => { + fakeAppConfigurationClient = new AppConfigurationClient(fakeAppConfigurationConnectionString); + service = new FeatureTogglesService(fakeAppConfigurationClient); + fakeGetConfigurationSetting = spyOn(fakeAppConfigurationClient, 'getConfigurationSetting').and.callFake( + () => of(fakeResponse).toPromise()); + }); + + it('toggles are read using azure configuration client', async () => { service.getToggle(featureToggleKey, featureToggleLabel).subscribe((value) => { expect(fakeGetConfigurationSetting).toHaveBeenCalledWith( { key: `.appconfig.featureflag/${featureToggleKey}`, label: featureToggleLabel } @@ -41,4 +48,5 @@ describe('FeatureTogglesService', () => { expect(value).toEqual(anyToggle); }); }); + }); diff --git a/src/app/modules/shared/store/technology.actions.ts b/src/app/modules/shared/store/technology.actions.ts index a2fb2a415..03e06cb09 100644 --- a/src/app/modules/shared/store/technology.actions.ts +++ b/src/app/modules/shared/store/technology.actions.ts @@ -5,6 +5,7 @@ export enum TechnologyActionTypes { FIND_TECHNOLOGIES = '[Technology] FIND_TECHNOLOGIES', FIND_TECHNOLOGIES_SUCESS = '[Technology] FIND_TECHNOLOGIES_SUCESS', FIND_TECHNOLOGIES_FAIL = '[Technology] FIND_TECHNOLOGIES_FAIL ', + DEFAULT_TECHNOLOGIES = '[Technology] DEFAULT_TECHNOLOGY', } export class FindTechnology implements Action { @@ -25,4 +26,8 @@ export class FindTechnologyFail implements Action { constructor(public error: string) {} } -export type TechnologyActions = FindTechnology | FindTechnologySuccess | FindTechnologyFail; +export class DefaultTechnology implements Action { + public readonly type = TechnologyActionTypes.DEFAULT_TECHNOLOGIES; +} + +export type TechnologyActions = FindTechnology | FindTechnologySuccess | FindTechnologyFail | DefaultTechnology; diff --git a/src/app/modules/shared/store/technology.reducers.spec.ts b/src/app/modules/shared/store/technology.reducers.spec.ts index ac274e163..8793c34a4 100644 --- a/src/app/modules/shared/store/technology.reducers.spec.ts +++ b/src/app/modules/shared/store/technology.reducers.spec.ts @@ -23,4 +23,11 @@ describe('technologyReducer', () => { const state = technologyReducer(initialState, action); expect(state.technologyList).toEqual([]); }); + + it('on DefaultAction, state equal to initial state', () => { + const action = new actions.DefaultTechnology(); + const state = technologyReducer(initialState, action); + expect(state).toEqual(initialState); + }); + }); diff --git a/src/app/modules/time-clock/pages/time-clock.component.spec.ts b/src/app/modules/time-clock/pages/time-clock.component.spec.ts index 2a035c6c0..d5dd535fc 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.spec.ts +++ b/src/app/modules/time-clock/pages/time-clock.component.spec.ts @@ -131,6 +131,12 @@ describe('TimeClockComponent', () => { expect(azureAdB2CService.getName).toHaveBeenCalledTimes(0); }); + it('if activeTimeEntry is not defined, areFieldsVisible must be false', () => { + spyOn(store, 'pipe').and.returnValue(of(null)); + component.ngOnInit(); + expect(component.areFieldsVisible).toBe(false); + }); + it('stopEntry dispatch a StopTimeEntryRunning action', () => { spyOn(store, 'dispatch'); diff --git a/src/app/modules/time-clock/store/entry.effects.spec.ts b/src/app/modules/time-clock/store/entry.effects.spec.ts index 6fd6d4f78..27ec9b2d6 100644 --- a/src/app/modules/time-clock/store/entry.effects.spec.ts +++ b/src/app/modules/time-clock/store/entry.effects.spec.ts @@ -155,6 +155,15 @@ describe('TimeEntryActionEffects', () => { }); }); + it('LoadActiveEntryFail if an entry active is not found', async () => { + actions$ = of({ type: EntryActionTypes.LOAD_ACTIVE_ENTRY }); + const serviceSpy = spyOn(service, 'loadActiveEntry'); + serviceSpy.and.returnValue(of(null)); + effects.loadActiveEntry$.subscribe(action => { + expect(action.type).toEqual(EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL); + }); + }); + it('display a success message on UPDATE_ENTRY', async () => { actions$ = of({ type: EntryActionTypes.UPDATE_ENTRY, entry }); spyOn(toastrService, 'success'); @@ -389,4 +398,5 @@ describe('TimeEntryActionEffects', () => { expect(action.type).toEqual(EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL); }); }); + }); diff --git a/src/app/modules/time-clock/store/entry.reducer.spec.ts b/src/app/modules/time-clock/store/entry.reducer.spec.ts index dafce2ee7..447ca1312 100644 --- a/src/app/modules/time-clock/store/entry.reducer.spec.ts +++ b/src/app/modules/time-clock/store/entry.reducer.spec.ts @@ -72,7 +72,7 @@ describe('entryReducer', () => { expect(state.isLoading).toBe(true); }); - it('on Default, ', () => { + it('on DefaultAction, state equal to initial state', () => { const action = new actions.DefaultEntry(); const state = entryReducer(initialState, action); expect(state).toEqual(initialState); @@ -136,6 +136,30 @@ describe('entryReducer', () => { expect(state.isLoading).toEqual(true); }); + it('sort previous entries when a new one is entered', () => { + const newState: EntryState = { ...initialState, timeEntriesDataSource: { + data: [ + { + project_id: '123', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + } + ], + isLoading: false, + }}; + const entryCreated: Entry = { ...entry }; + entryCreated.end_date = null; + const action = new actions.CreateEntrySuccess(entryCreated); + const state = entryReducer(newState, action); + expect(state.active).toBe(entryCreated); + }); + it('on CreateEntrySuccess, if end_date is null then it is the active entry', () => { const entryCreated: Entry = { ...entry }; entryCreated.end_date = null; @@ -188,6 +212,28 @@ describe('entryReducer', () => { expect(state.timeEntriesDataSource.data).toEqual([]); }); + it('filter reportDataSource data when one is to be deleted', () => { + const newState: EntryState = { ...initialState, reportDataSource: { + data: [ + { + project_id: '123456', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + } + ], + isLoading: false, + }}; + const action = new actions.DeleteEntrySuccess('idxxx'); + const state = entryReducer(newState, action); + expect(state.reportDataSource.data.length).toEqual(1); + }); + it('on LoadEntriesFail, active tobe null', () => { const action = new actions.DeleteEntryFail('error'); const state = entryReducer(initialState, action); @@ -224,6 +270,39 @@ describe('entryReducer', () => { expect(state.isLoading).toEqual(false); }); + it('sort the list of entries when one is updated', () => { + const newState: EntryState = { ...initialState, timeEntriesDataSource: { + data: [ + { + project_id: '123', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + }, + { + project_id: '123456', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + } + ], + isLoading: false, + }}; + const action = new actions.UpdateEntrySuccess(entry); + const state = entryReducer(newState, action); + expect(state.isLoading).toEqual(false); + }); + it('on cleanEntryCreateError, createError to be null', () => { const action = new actions.CleanEntryCreateError(null); const state = entryReducer(initialState, action); diff --git a/src/app/modules/time-entries/pages/time-entries.component.spec.ts b/src/app/modules/time-entries/pages/time-entries.component.spec.ts index 9b263a500..7c1afcde4 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.spec.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.spec.ts @@ -712,4 +712,50 @@ describe('TimeEntriesComponent', () => { expect(HTMLTimeEntriesView).not.toBeNull(); }); + + it('open the modal with the correct parameters', () => { + const item: any = { + id: `1`, + activity_name: `Activity 1`, + }; + component.openModal(item); + expect(component.idToDelete).toEqual(item.id); + expect(component.message).toEqual(`Are you sure you want to delete ${item.activity_name}?`); + expect(component.showModal).toBe(true); + }); + + it('if there are no entries, the isTheEntryToEditTheLastOne function should return false', () => { + const entries: Entry[] = []; + expect(component.isTheEntryToEditTheLastOne(entries)).toBe(false); + }); + + it('should create new Entry even though the activeEntry overlaps', () => { + component.entryId = 'entry_2'; + const activeEntry = { + id: 'entry_1', + project_id: 'abc', + project_name: 'Time-tracker', + start_date: new Date('2020-02-05T15:36:15.887Z'), + end_date: new Date('2020-02-05T18:36:15.887Z'), + customer_name: 'ioet Inc.', + activity_id: 'development', + technologies: ['Angular', 'TypeScript'], + description: 'No comments', + uri: 'EY-25', + }; + const entryToSave = { + entry: { + project_id: 'project-id', + end_date: '2010-05-05T10:04', + start_date: null, + timezone_offset: 300, + }, + shouldRestartEntry: true + }; + spyOn(component, 'doSave'); + component.activeTimeEntry = activeEntry; + component.saveEntry(entryToSave); + expect(component.doSave).toHaveBeenCalledWith(entryToSave); + }); + }); diff --git a/src/app/modules/time-entries/pages/time-entries.component.ts b/src/app/modules/time-entries/pages/time-entries.component.ts index 49e2648ae..61734da27 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.ts @@ -122,7 +122,7 @@ export class TimeEntriesComponent implements OnInit, OnDestroy, AfterViewInit { return false; } } - private isTheEntryToEditTheLastOne(entries: Entry[]) { + public isTheEntryToEditTheLastOne(entries: Entry[]) { if (entries && entries.length > 0) { const lastEntry = entries[0]; return lastEntry.id === this.entryId; diff --git a/src/app/modules/user/store/user.actions.ts b/src/app/modules/user/store/user.actions.ts index b5b5a664b..1543327e7 100644 --- a/src/app/modules/user/store/user.actions.ts +++ b/src/app/modules/user/store/user.actions.ts @@ -5,6 +5,7 @@ export enum UserActionTypes { LOAD_USER = '[User] LOAD_USER', LOAD_USER_SUCCESS = '[User] LOAD_USER_SUCCESS', LOAD_USER_FAIL = '[User] LOAD_USER_FAIL', + DEFAULT_USER = '[User] DEFAULT_USER', } export class LoadUser implements Action { @@ -24,4 +25,8 @@ export class LoadUserFail implements Action { constructor(public error: string) {} } -export type UserActions = LoadUser | LoadUserSuccess | LoadUserFail; +export class DefaultUser implements Action { + public readonly type = UserActionTypes.DEFAULT_USER; +} + +export type UserActions = LoadUser | LoadUserSuccess | LoadUserFail | DefaultUser; diff --git a/src/app/modules/user/store/user.reducer.spec.ts b/src/app/modules/user/store/user.reducer.spec.ts index a73313362..9fadbf2d3 100644 --- a/src/app/modules/user/store/user.reducer.spec.ts +++ b/src/app/modules/user/store/user.reducer.spec.ts @@ -1,5 +1,5 @@ import { userReducer } from './user.reducer'; -import { LoadUser, LoadUserFail, LoadUserSuccess } from './user.actions'; +import { LoadUser, LoadUserFail, LoadUserSuccess, DefaultUser } from './user.actions'; import { User } from '../models/user'; describe('userReducer', () => { @@ -41,4 +41,11 @@ describe('userReducer', () => { expect(state).toEqual(initialState); }); + + it('on DefaultAction, state equal to initial state', () => { + const action = new DefaultUser(); + const state = userReducer(initialState, action); + expect(state).toEqual(initialState); + }); + }); diff --git a/src/app/modules/users/store/user.reducers.spec.ts b/src/app/modules/users/store/user.reducers.spec.ts index 8cc8ffac8..8096f1390 100644 --- a/src/app/modules/users/store/user.reducers.spec.ts +++ b/src/app/modules/users/store/user.reducers.spec.ts @@ -187,7 +187,7 @@ describe('userReducer', () => { expect(state.message).toBe('Something went wrong revoking access role to the user'); }); - it('on Default, ', () => { + it('on DefaultAction, state equal to initial state', () => { const action = new actions.DefaultUser(); const state = userReducer(initialState, action); expect(state).toEqual(initialState); From 6a137a099f36cf12c4821ff943ec2630b8e7c68d Mon Sep 17 00:00:00 2001 From: Rodrigo Farinango <47066093+rofarinango@users.noreply.github.com> Date: Mon, 31 Jan 2022 09:48:12 -0500 Subject: [PATCH 03/17] fix: TT-362-pagination-problem-solved (#801) Co-authored-by: Rodrigo Farinango --- .../time-entries/pages/time-entries.component.html | 9 +++------ .../modules/time-entries/pages/time-entries.component.ts | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/modules/time-entries/pages/time-entries.component.html b/src/app/modules/time-entries/pages/time-entries.component.html index f9b2eaade..5e44df0be 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.html +++ b/src/app/modules/time-entries/pages/time-entries.component.html @@ -40,13 +40,13 @@ - +
- @@ -62,9 +62,6 @@ - - - diff --git a/src/app/modules/time-entries/pages/time-entries.component.ts b/src/app/modules/time-entries/pages/time-entries.component.ts index 61734da27..0ecdbc921 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.ts @@ -50,7 +50,6 @@ export class TimeEntriesComponent implements OnInit, OnDestroy, AfterViewInit { isActiveEntryOverlapping = false; calendarView: CalendarView = CalendarView.Month; actualDate: Date; - readonly NO_DATA_MESSAGE: string = 'No data available in table'; constructor( private store: Store, private toastrService: ToastrService, From 3aff2e57a8dfaf8aed50e17e4425b504b1bb8d3f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 31 Jan 2022 14:50:22 +0000 Subject: [PATCH 04/17] chore(release): 1.60.6 [skip ci]nn --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ebe355f9..7963a9727 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.60.5", + "version": "1.60.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0ed0cc466..1601ff24b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.60.5", + "version": "1.60.6", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From 775474c39e994c2884c57e817758ef89a32ca7f7 Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Wed, 2 Feb 2022 15:51:07 -0500 Subject: [PATCH 05/17] fix: TT-529 sort by date not working correctly on reports (#805) --- .../time-entries-table/time-entries-table.component.ts | 3 ++- src/app/modules/time-clock/store/entry.effects.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.ts b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.ts index 5658a972e..9e6dcb415 100644 --- a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.ts +++ b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.ts @@ -50,7 +50,8 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn text: 'CSV', filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}` }, - ] + ], + columnDefs: [{ type: 'date', targets: 2 }] }; dtTrigger: Subject = new Subject(); @ViewChild(DataTableDirective, { static: false }) diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts index 98527cc58..40c1cb1c9 100644 --- a/src/app/modules/time-clock/store/entry.effects.ts +++ b/src/app/modules/time-clock/store/entry.effects.ts @@ -143,7 +143,7 @@ export class EntryEffects { if (error.status === 404) { return of(new actions.CreateEntry(entry)); } else { - this.toastrService.error('We could not clock in you, try again later.'); + this.toastrService.error('We could not clock you in, try again later.'); return of(new actions.CreateEntryFail('Error')); } }) From 3c07126670c5bcc1bf30eb3e7747315f9fdbe671 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 2 Feb 2022 20:53:25 +0000 Subject: [PATCH 06/17] chore(release): 1.60.7 [skip ci]nn --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7963a9727..1626535f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.60.6", + "version": "1.60.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1601ff24b..eb1fe59aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.60.6", + "version": "1.60.7", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From 0e06a98fedf8e7bf1c15106e905ade2f69c9cbfc Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Wed, 9 Feb 2022 11:59:45 -0500 Subject: [PATCH 07/17] feat: TT-536 remove date out in time entries (#806) --- .../details-fields.component.html | 25 ++----------- .../details-fields.component.spec.ts | 35 ------------------- 2 files changed, 2 insertions(+), 58 deletions(-) diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.html b/src/app/modules/shared/components/details-fields/details-fields.component.html index 3b737aab9..2719a149f 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.html +++ b/src/app/modules/shared/components/details-fields/details-fields.component.html @@ -72,7 +72,7 @@
- +
- -
- - -
- +
{ expect(component.projectSelected.emit).toHaveBeenCalledWith(data); }); - it('on selected start_date should change end_date', () => { - const expectedStartDate = '2020-02-05'; - - component.onStartDateChange(expectedStartDate); - fixture.detectChanges(); - const endDateInput: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#end_date'); - - expect(endDateInput.value).toEqual(expectedStartDate); - }); - - it('on selected end_date should not change start_date', () => { - const expectedStartDate = '2020-02-05'; - const expectedEndDate = '2020-02-06'; - - component.ngOnInit(); - fixture.detectChanges(); - const startDateInput: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#start_date'); - const endDateInput: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#end_date'); - startDateInput.value = expectedStartDate; - endDateInput.value = expectedEndDate; - endDateInput.dispatchEvent(new Event('#end_date')); - - expect(endDateInput.value).not.toEqual(startDateInput.value); - expect(startDateInput.value).toEqual(expectedStartDate); - }); - it('on get current date should return expected date', () => { const expectedDate = moment(new Date()).format(DATE_FORMAT_YEAR); @@ -560,15 +534,6 @@ describe('DetailsFieldsComponent', () => { expect(startDateInput.max).toEqual(expectedDate); }); - it('on the input with id #end_date we could get the current Date ', () => { - fixture.detectChanges(); - const expectedDate = moment(new Date()).format(DATE_FORMAT_YEAR); - const endDateInput = fixture.debugElement.nativeElement.querySelector('[id=end_date]'); - - expect(endDateInput.id).toEqual('end_date'); - expect(endDateInput.max).toEqual(expectedDate); - }); - const diffParams = [ { case: 'positive should return correctly diff', From 015a785eb9e660b52e9cdfbb3ff0b37c87ff9a7f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 9 Feb 2022 20:41:11 +0000 Subject: [PATCH 08/17] chore(release): 1.61.0 [skip ci]nn --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1626535f1..eac419d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.60.7", + "version": "1.61.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eb1fe59aa..d4b451b0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.60.7", + "version": "1.61.0", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From 3fe4574c0e74f8916f44b5ed81a7fd191ca2df9c Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Wed, 19 Jan 2022 11:26:35 -0500 Subject: [PATCH 09/17] feat: TT-511 containerize time tracker ui --- .dockerignore | 5 +++++ .gitignore | 3 +++ Dockerfile | 19 +++++++++++++++++++ Makefile | 32 ++++++++++++++++++++++++++++++++ docker-compose.yml | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..c6dfe6fa6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.github +Makefile +.gitignore +.env diff --git a/.gitignore b/.gitignore index 93e5cb260..6cf093c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ Thumbs.db # stryker temp files .stryker-tmp + +#ENV VARIABLES +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..493a2acff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# choose base image to build off of +FROM node:14 + +# set the current working directory for all commands +WORKDIR /time-tracker-ui + +# copy these over first and run 'npm install' so the node_modules will be cached +# until the package.json / lock changes +COPY package*.json . +RUN npm cache clean --force && npm install + +# copy over all code files +COPY . . + +# expose internal docker container port to external environment +EXPOSE 4200 + +# specify default command to run when we run the image +CMD /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..d9c861b52 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: help +help: + @echo "---------------HELP-----------------" + @echo "- make build --> Create docker image with dependencies needed" + @echo "- make run --> Execute docker container database from postgres, and api from image created previusly" + @echo "- make stop --> Stop container" + @echo "- make remove --> Delete container" + @echo "------------------------------------" + +.PHONY: build +build: + -docker rmi timetracker_ui + docker build -t timetracker_ui -f Dockerfile . + +.PHONY: run +run: + docker-compose up -d + docker logs -f timetracker_ui + +.PHONY: stop +stop: + docker-compose stop + +.PHONY: restart +restart: + docker-compose stop + docker-compose up -d + docker logs -f timetracker_ui + +.PHONY: remove +remove: + docker-compose down diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..7f314e2d6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.9' +services: + time-tracker-ui: + container_name: timetracker_ui + image: timetracker_ui + build: + context: . + dockerfile: ./Dockerfile + ports: + - 4200:4200 + working_dir: /time-tracker-ui + volumes: + - ./src:/time-tracker-ui/src/ + # - ./scripts:/time-tracker-ui/scripts/ + # - ./e2e:/time-tracker-ui/e2e/ + # - ./.browserslistrc:/time-tracker-ui/.browserslistrc + # - ./.dockerignore:/time-tracker-ui/.dockerignore + # - ./.editorconfig:/time-tracker-ui/.editorconfig + # - ./.eslintrc.js:/time-tracker-ui/.eslintrc.js + # - ./.prettierrc:/time-tracker-ui/.prettierrc + # - ./angular.json:/time-tracker-ui/angular.json + # - ./docker-compose.yml:/time-tracker-ui/docker-compose.yml + # - ./Dockerfile:/time-tracker-ui/Dockerfile + # - ./karma.conf.js:/time-tracker-ui/karma.conf.js + # - ./Makefile:/time-tracker-ui/Makefile + # - ./package.json:/time-tracker-ui/package.json + # - ./package-lock.json:/time-tracker-ui/package-lock.json + # - ./README.md:/time-tracker-ui/README.md + # - ./stryker.conf.json:/time-tracker-ui/stryker.conf.json + # - ./stryker.log:/time-tracker-ui/stryker.log + # - ./tailwind.config.js:/time-tracker-ui/tailwind.config.js + # - ./tsconfig.app.json:/time-tracker-ui/tsconfig.app.json + # - ./tsconfig.json:/time-tracker-ui/tsconfig.json + # - ./tsconfig.spec.json:/time-tracker-ui/tsconfig.spec.json + # - ./tslint.json:/time-tracker-ui/tslint.json + # - ./webpack.config.js:/time-tracker-ui/webpack.config.js From ff75680f66069059eae03721040eeeb263797a47 Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Fri, 21 Jan 2022 10:32:02 -0500 Subject: [PATCH 10/17] feat: TT-511 environment variables and tests setup --- .dockerignore | 2 ++ Dockerfile | 12 ----------- Makefile | 20 ++++++++++++++++++- docker-compose.yml | 50 +++++++++++++++++++++++++--------------------- package-lock.json | 5 +++++ package.json | 6 ++++-- scripts/setenv.ts | 34 +++++++++++++++++++++++++++++++ 7 files changed, 91 insertions(+), 38 deletions(-) create mode 100644 scripts/setenv.ts diff --git a/.dockerignore b/.dockerignore index c6dfe6fa6..ebc93de12 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,3 +3,5 @@ node_modules Makefile .gitignore .env +keys.ts +.keys.json diff --git a/Dockerfile b/Dockerfile index 493a2acff..1e069dcae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,7 @@ -# choose base image to build off of FROM node:14 -# set the current working directory for all commands WORKDIR /time-tracker-ui -# copy these over first and run 'npm install' so the node_modules will be cached -# until the package.json / lock changes COPY package*.json . RUN npm cache clean --force && npm install - -# copy over all code files COPY . . - -# expose internal docker container port to external environment -EXPOSE 4200 - -# specify default command to run when we run the image -CMD /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck diff --git a/Makefile b/Makefile index d9c861b52..ba797f157 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,11 @@ help: @echo "- make build --> Create docker image with dependencies needed" @echo "- make run --> Execute docker container database from postgres, and api from image created previusly" @echo "- make stop --> Stop container" + @echo "- make remove --> Restart container" @echo "- make remove --> Delete container" + @echo "- make test --> Run all tests on docker container" + @echo "- make login --> Login in respository of docker images" + @echo "- make publish --> Publish the container image" @echo "------------------------------------" .PHONY: build @@ -14,7 +18,7 @@ build: .PHONY: run run: - docker-compose up -d + docker-compose --env-file ./.env up -d docker logs -f timetracker_ui .PHONY: stop @@ -30,3 +34,17 @@ restart: .PHONY: remove remove: docker-compose down + +.PHONY: test +test: + docker-compose --env-file ./.env up -d + docker exec -it timetracker_ui bash -c "npm test" + +.PHONY: login +login: + az acr login --name timetrackerdevregistry + +.PHONY: publish +publish: + docker tag timetracker_ui:latest timetrackerdevregistry.azurecr.io/timetracker_ui:latest + docker push timetrackerdevregistry.azurecr.io/timetracker_ui:latest diff --git a/docker-compose.yml b/docker-compose.yml index 7f314e2d6..baa689f21 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,29 +8,33 @@ services: dockerfile: ./Dockerfile ports: - 4200:4200 + - 9876:9876 + environment: + AUTHORITY: ${AUTHORITY} + CLIENT_ID: ${CLIENT_ID} + SCOPES: ${SCOPES} + STACK_EXCHANGE_ID: ${STACK_EXCHANGE_ID} + STACK_EXCHANGE_ACCESS_TOKEN: ${STACK_EXCHANGE_ACCESS_TOKEN} + AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${AZURE_APP_CONFIGURATION_CONNECTION_STRING} working_dir: /time-tracker-ui + command: bash -c "npm run config && /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck" volumes: - ./src:/time-tracker-ui/src/ - # - ./scripts:/time-tracker-ui/scripts/ - # - ./e2e:/time-tracker-ui/e2e/ - # - ./.browserslistrc:/time-tracker-ui/.browserslistrc - # - ./.dockerignore:/time-tracker-ui/.dockerignore - # - ./.editorconfig:/time-tracker-ui/.editorconfig - # - ./.eslintrc.js:/time-tracker-ui/.eslintrc.js - # - ./.prettierrc:/time-tracker-ui/.prettierrc - # - ./angular.json:/time-tracker-ui/angular.json - # - ./docker-compose.yml:/time-tracker-ui/docker-compose.yml - # - ./Dockerfile:/time-tracker-ui/Dockerfile - # - ./karma.conf.js:/time-tracker-ui/karma.conf.js - # - ./Makefile:/time-tracker-ui/Makefile - # - ./package.json:/time-tracker-ui/package.json - # - ./package-lock.json:/time-tracker-ui/package-lock.json - # - ./README.md:/time-tracker-ui/README.md - # - ./stryker.conf.json:/time-tracker-ui/stryker.conf.json - # - ./stryker.log:/time-tracker-ui/stryker.log - # - ./tailwind.config.js:/time-tracker-ui/tailwind.config.js - # - ./tsconfig.app.json:/time-tracker-ui/tsconfig.app.json - # - ./tsconfig.json:/time-tracker-ui/tsconfig.json - # - ./tsconfig.spec.json:/time-tracker-ui/tsconfig.spec.json - # - ./tslint.json:/time-tracker-ui/tslint.json - # - ./webpack.config.js:/time-tracker-ui/webpack.config.js + - ./scripts:/time-tracker-ui/scripts/ + - ./e2e:/time-tracker-ui/e2e/ + - ./coverage:/time-tracker-ui/coverage + - ./.browserslistrc:/time-tracker-ui/.browserslistrc + - ./.dockerignore:/time-tracker-ui/.dockerignore + - ./.editorconfig:/time-tracker-ui/.editorconfig + - ./.eslintrc.js:/time-tracker-ui/.eslintrc.js + - ./.prettierrc:/time-tracker-ui/.prettierrc + - ./angular.json:/time-tracker-ui/angular.json + - ./karma.conf.js:/time-tracker-ui/karma.conf.js + - ./package.json:/time-tracker-ui/package.json + - ./stryker.conf.json:/time-tracker-ui/stryker.conf.json + - ./tailwind.config.js:/time-tracker-ui/tailwind.config.js + - ./tsconfig.app.json:/time-tracker-ui/tsconfig.app.json + - ./tsconfig.json:/time-tracker-ui/tsconfig.json + - ./tsconfig.spec.json:/time-tracker-ui/tsconfig.spec.json + - ./tslint.json:/time-tracker-ui/tslint.json + - ./webpack.config.js:/time-tracker-ui/webpack.config.js diff --git a/package-lock.json b/package-lock.json index eac419d50..d03a23ce6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7877,6 +7877,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-14.2.0.tgz", + "integrity": "sha512-05POuPJyPpO6jqzTNweQFfAyMSD4qa4lvsMOWyTRTdpHKy6nnnN+IYWaXF+lHivhBH/ufDKlR4IWCAN3oPnHuw==" + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", diff --git a/package.json b/package.json index d4b451b0d..10186100b 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,11 @@ "name": "time-tracker", "version": "1.61.0", "scripts": { + "config": "ts-node ./scripts/setenv.ts", "preinstall": "npx npm-force-resolutions", "ng": "ng", - "start": "ng serve", - "build": "ng build --prod", + "start": "npm run config && ng serve", + "build": "npm run config && ng build --prod", "test": "ng test", "test-headless": "ng test --browsers ChromeHeadless", "ci-test": "ng test --no-watch --no-progress --browsers ChromeHeadless", @@ -44,6 +45,7 @@ "datatables.net-responsive": "2.2.6", "datatables.net-responsive-dt": "2.2.6", "date-fns": "2.22.1", + "dotenv": "^14.2.0", "jquery": "3.5.1", "jszip": "3.7.0", "minimist": "1.2.5", diff --git a/scripts/setenv.ts b/scripts/setenv.ts new file mode 100644 index 000000000..0c07a1b30 --- /dev/null +++ b/scripts/setenv.ts @@ -0,0 +1,34 @@ +const { writeFile } = require('fs'); +require('dotenv').config(); + +const pathJs = `./src/environments/keys.ts` +const contentKeys = +`export const AUTHORITY = '${process.env.AUTHORITY}'; +export const CLIENT_ID = '${process.env.CLIENT_ID}'; +export const SCOPES = ['${process.env.SCOPES}']; +export const STACK_EXCHANGE_ID = '${process.env.STACK_EXCHANGE_ID}'; +export const STACK_EXCHANGE_ACCESS_TOKEN = '${process.env.STACK_EXCHANGE_ACCESS_TOKEN}'; +export const AZURE_APP_CONFIGURATION_CONNECTION_STRING = '${process.env.AZURE_APP_CONFIGURATION_CONNECTION_STRING}'; +`; + +writeFile(pathJs, contentKeys, function (err) { + if (err) { + console.log(err); + } + console.log(`Wrote variables to ${pathJs}`); +}); + +const pathJson = `./src/environments/.keys.json` +const contentKeysJson = +`{ + "authority": "${process.env.AUTHORITY_JSON}", + "client_id": "${process.env.CLIENT_ID_JSON}", + "scopes": ["${process.env.SCOPES_JSON}"] +}`; + +writeFile(pathJson, contentKeysJson, function (err) { + if (err) { + console.log(err); + } + console.log(`Wrote variables to ${pathJson}`); +}); From 128d71dfeba7f0e7fe022e552f91e0c81f5b5080 Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Fri, 21 Jan 2022 11:09:52 -0500 Subject: [PATCH 11/17] fix: TT-511 add missing environment variables --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index baa689f21..042f3648c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,9 @@ services: STACK_EXCHANGE_ID: ${STACK_EXCHANGE_ID} STACK_EXCHANGE_ACCESS_TOKEN: ${STACK_EXCHANGE_ACCESS_TOKEN} AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${AZURE_APP_CONFIGURATION_CONNECTION_STRING} + AUTHORITY_JSON: ${AUTHORITY_JSON} + CLIENT_ID_JSON: ${CLIENT_ID_JSON} + SCOPES_JSON: ${SCOPES_JSON} working_dir: /time-tracker-ui command: bash -c "npm run config && /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck" volumes: From 3e5a455fe4a1c8f889d87e605b9f0e294d201cfe Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Fri, 21 Jan 2022 12:04:04 -0500 Subject: [PATCH 12/17] fix: TT-511 update .dockerignore --- .dockerignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index ebc93de12..2605843ba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,5 +3,5 @@ node_modules Makefile .gitignore .env -keys.ts -.keys.json +src/environments/keys.ts +src/environments/.keys.json From 55d06c231e6fa044de3a590cb6e82fedbfe0f4a4 Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Fri, 21 Jan 2022 14:38:50 -0500 Subject: [PATCH 13/17] refactor: TT-511 solve comments --- Dockerfile | 3 +-- Makefile | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1e069dcae..2faa87c33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,5 @@ FROM node:14 WORKDIR /time-tracker-ui -COPY package*.json . -RUN npm cache clean --force && npm install COPY . . +RUN npm cache clean --force && npm install diff --git a/Makefile b/Makefile index ba797f157..b6bc77e7e 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ help: @echo "---------------HELP-----------------" @echo "- make build --> Create docker image with dependencies needed" - @echo "- make run --> Execute docker container database from postgres, and api from image created previusly" + @echo "- make run --> Execute timetracker_ui docker container" @echo "- make stop --> Stop container" @echo "- make remove --> Restart container" @echo "- make remove --> Delete container" From b0dfd5a96c2a26dd30b20ec2592e1e98458250bc Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Tue, 25 Jan 2022 16:15:10 -0500 Subject: [PATCH 14/17] feat: TT-511 multi-stage builds in dockerfile and add user to run commands inside docker --- .dockerignore | 2 ++ Dockerfile | 34 +++++++++++++++++++++++++++++++--- Makefile | 2 +- docker-compose.yml | 30 +++++++++--------------------- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/.dockerignore b/.dockerignore index 2605843ba..3306ad8e7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,7 @@ node_modules .github +dist +coverage Makefile .gitignore .env diff --git a/Dockerfile b/Dockerfile index 2faa87c33..4dc97ae09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,34 @@ -FROM node:14 +FROM node:14 AS development -WORKDIR /time-tracker-ui +ENV USERNAME timetracker +ENV HOME /home/${USERNAME} -COPY . . +RUN useradd -ms /bin/bash ${USERNAME} + +WORKDIR ${HOME}/time-tracker-ui +RUN chown ${USERNAME}:${USERNAME} -R ${HOME} +COPY --chown=${USERNAME}:${USERNAME} . . + +USER ${USERNAME} +RUN npm cache clean --force && npm install +EXPOSE 4200 +EXPOSE 9876 +CMD npm run config && ${HOME}/time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck + + + +FROM node:14 AS production + +ENV USERNAME timetracker +ENV HOME /home/${USERNAME} + +RUN useradd -m -d ${HOME} -s /bin/bash ${USERNAME} + +WORKDIR ${HOME}/time-tracker-ui +RUN chown ${USERNAME}:${USERNAME} -R ${HOME} +COPY --chown=${USERNAME}:${USERNAME} . . + +USER ${USERNAME} RUN npm cache clean --force && npm install +EXPOSE 4200 +CMD npm run config && /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck --prod \ No newline at end of file diff --git a/Makefile b/Makefile index b6bc77e7e..dc3ae5407 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ help: .PHONY: build build: -docker rmi timetracker_ui - docker build -t timetracker_ui -f Dockerfile . + docker-compose build .PHONY: run run: diff --git a/docker-compose.yml b/docker-compose.yml index 042f3648c..d7516c1a1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: container_name: timetracker_ui image: timetracker_ui build: + target: development context: . dockerfile: ./Dockerfile ports: @@ -19,25 +20,12 @@ services: AUTHORITY_JSON: ${AUTHORITY_JSON} CLIENT_ID_JSON: ${CLIENT_ID_JSON} SCOPES_JSON: ${SCOPES_JSON} - working_dir: /time-tracker-ui - command: bash -c "npm run config && /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck" volumes: - - ./src:/time-tracker-ui/src/ - - ./scripts:/time-tracker-ui/scripts/ - - ./e2e:/time-tracker-ui/e2e/ - - ./coverage:/time-tracker-ui/coverage - - ./.browserslistrc:/time-tracker-ui/.browserslistrc - - ./.dockerignore:/time-tracker-ui/.dockerignore - - ./.editorconfig:/time-tracker-ui/.editorconfig - - ./.eslintrc.js:/time-tracker-ui/.eslintrc.js - - ./.prettierrc:/time-tracker-ui/.prettierrc - - ./angular.json:/time-tracker-ui/angular.json - - ./karma.conf.js:/time-tracker-ui/karma.conf.js - - ./package.json:/time-tracker-ui/package.json - - ./stryker.conf.json:/time-tracker-ui/stryker.conf.json - - ./tailwind.config.js:/time-tracker-ui/tailwind.config.js - - ./tsconfig.app.json:/time-tracker-ui/tsconfig.app.json - - ./tsconfig.json:/time-tracker-ui/tsconfig.json - - ./tsconfig.spec.json:/time-tracker-ui/tsconfig.spec.json - - ./tslint.json:/time-tracker-ui/tslint.json - - ./webpack.config.js:/time-tracker-ui/webpack.config.js + - ./src:/home/timetracker/time-tracker-ui/src/ + - ./scripts:/home/timetracker/time-tracker-ui/scripts/ + - ./e2e:/home/timetracker/time-tracker-ui/e2e/ + - ./coverage:/home/timetracker/time-tracker-ui/coverage + - ./angular.json:/home/timetracker/time-tracker-ui/angular.json + - ./karma.conf.js:/home/timetracker/time-tracker-ui/karma.conf.js + - ./package.json:/home/timetracker/time-tracker-ui/package.json + - ./webpack.config.js:/home/timetracker/time-tracker-ui/webpack.config.js From 40a7d83c8172f9a9fa4992f560249359c588ac0d Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Thu, 27 Jan 2022 10:25:48 -0500 Subject: [PATCH 15/17] feat: TT-511 create build and docker image for production --- .dockerignore | 1 - Dockerfile | 33 ++++++++++++++++++++++----------- Makefile | 49 ++++++++++++++++++++++++++++++++++++++----------- nginx.conf | 12 ++++++++++++ 4 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore index 3306ad8e7..59dd4cdfc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,5 @@ dist coverage Makefile .gitignore -.env src/environments/keys.ts src/environments/.keys.json diff --git a/Dockerfile b/Dockerfile index 4dc97ae09..bb4f8565c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,8 @@ ENV HOME /home/${USERNAME} RUN useradd -ms /bin/bash ${USERNAME} WORKDIR ${HOME}/time-tracker-ui +COPY [^.env]* . . RUN chown ${USERNAME}:${USERNAME} -R ${HOME} -COPY --chown=${USERNAME}:${USERNAME} . . USER ${USERNAME} RUN npm cache clean --force && npm install @@ -17,18 +17,29 @@ CMD npm run config && ${HOME}/time-tracker-ui/node_modules/.bin/ng serve --host -FROM node:14 AS production +FROM node:14 AS build -ENV USERNAME timetracker -ENV HOME /home/${USERNAME} +WORKDIR /usr/local/app +COPY ./ /usr/local/app/ -RUN useradd -m -d ${HOME} -s /bin/bash ${USERNAME} +RUN npm install +RUN npm run build -WORKDIR ${HOME}/time-tracker-ui -RUN chown ${USERNAME}:${USERNAME} -R ${HOME} -COPY --chown=${USERNAME}:${USERNAME} . . + + +FROM nginx:1.21 AS production + +ENV USERNAME nginx + +COPY nginx.conf /etc/nginx/conf.d/ +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /usr/local/app/dist/time-tracker /usr/share/nginx/html + +RUN chown -R ${USERNAME}:${USERNAME} /var/cache/nginx && \ + chown -R ${USERNAME}:${USERNAME} /var/log/nginx && \ + chown -R ${USERNAME}:${USERNAME} /etc/nginx/conf.d +RUN touch /var/run/nginx.pid && chown -R ${USERNAME}:${USERNAME} /var/run/nginx.pid USER ${USERNAME} -RUN npm cache clean --force && npm install -EXPOSE 4200 -CMD npm run config && /time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck --prod \ No newline at end of file + +EXPOSE 4200 \ No newline at end of file diff --git a/Makefile b/Makefile index dc3ae5407..6d7142547 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,21 @@ .PHONY: help help: @echo "---------------HELP-----------------" - @echo "- make build --> Create docker image with dependencies needed" + + @echo "- make build --> Create docker image with dependencies needed for development" @echo "- make run --> Execute timetracker_ui docker container" - @echo "- make stop --> Stop container" - @echo "- make remove --> Restart container" - @echo "- make remove --> Delete container" - @echo "- make test --> Run all tests on docker container" + @echo "- make stop --> Stop container timetracker_ui" + @echo "- make restart --> Restart container timetracker_ui" + @echo "- make remove --> Delete container timetracker_ui" + @echo "- make test --> Run all tests on docker container timetracker_ui" + @echo "- make publish --> Publish the container image timetracker_ui" + + @echo "- make build_prod --> Create docker image with dependencies needed for production" + @echo "- make run_prod --> Execute timetracker_ui_prod docker container" + @echo "- make remove_prod --> Delete container timetracker_ui_prod" + @echo "- make publish_prod --> Publish the container image timetracker_ui_prod" + @echo "- make login --> Login in respository of docker images" - @echo "- make publish --> Publish the container image" @echo "------------------------------------" .PHONY: build @@ -38,13 +45,33 @@ remove: .PHONY: test test: docker-compose --env-file ./.env up -d - docker exec -it timetracker_ui bash -c "npm test" - -.PHONY: login -login: - az acr login --name timetrackerdevregistry + docker exec -it timetracker_ui bash -c "npm run test" .PHONY: publish publish: docker tag timetracker_ui:latest timetrackerdevregistry.azurecr.io/timetracker_ui:latest docker push timetrackerdevregistry.azurecr.io/timetracker_ui:latest + +.PHONY: build_prod +build_prod: + -docker rmi timetracker_ui_prod + docker build --target production -t timetracker_ui_prod -f Dockerfile . + +.PHONY: run_prod +run_prod: + docker run -d -p 4200:4200 --name timetracker_ui_prod timetracker_ui_prod + docker logs -f timetracker_ui_prod + +.PHONY: remove_prod +remove_prod: + docker stop timetracker_ui_prod + docker rm timetracker_ui_prod + +.PHONY: publish_prod +publish_prod: + docker tag timetracker_ui_prod:latest timetrackerdevregistry.azurecr.io/timetracker_ui_prod:latest + docker push timetrackerdevregistry.azurecr.io/timetracker_ui_prod:latest + +.PHONY: login +login: + az acr login --name timetrackerdevregistry \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 000000000..824374fdc --- /dev/null +++ b/nginx.conf @@ -0,0 +1,12 @@ +server { + listen 4200; + + root /usr/share/nginx/html; + index index.html; + + server_name _; + + location / { + try_files $uri /index.html; + } +} \ No newline at end of file From 73514aca5dcf9a078194ade2f6dbd60f0e34d668 Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Mon, 31 Jan 2022 12:57:44 -0500 Subject: [PATCH 16/17] refactor: TT-511 solve comments --- .dockerignore | 4 +-- Dockerfile | 19 ++++++-------- Makefile | 72 +++++++++++++++++++++++---------------------------- 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/.dockerignore b/.dockerignore index 59dd4cdfc..557f156b4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,5 +4,5 @@ dist coverage Makefile .gitignore -src/environments/keys.ts -src/environments/.keys.json +*keys.ts +*.keys.json diff --git a/Dockerfile b/Dockerfile index bb4f8565c..57674ee4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,9 @@ ENV HOME /home/${USERNAME} RUN useradd -ms /bin/bash ${USERNAME} WORKDIR ${HOME}/time-tracker-ui -COPY [^.env]* . . -RUN chown ${USERNAME}:${USERNAME} -R ${HOME} +COPY . . +RUN rm -f .env +RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui USER ${USERNAME} RUN npm cache clean --force && npm install @@ -17,23 +18,19 @@ CMD npm run config && ${HOME}/time-tracker-ui/node_modules/.bin/ng serve --host -FROM node:14 AS build - -WORKDIR /usr/local/app -COPY ./ /usr/local/app/ - -RUN npm install +FROM development as build +COPY .env . RUN npm run build FROM nginx:1.21 AS production -ENV USERNAME nginx +ENV USERNAME app +RUN useradd -ms /bin/bash ${USERNAME} -COPY nginx.conf /etc/nginx/conf.d/ COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=build /usr/local/app/dist/time-tracker /usr/share/nginx/html +COPY --from=build /home/timetracker/time-tracker-ui/dist/time-tracker /usr/share/nginx/html RUN chown -R ${USERNAME}:${USERNAME} /var/cache/nginx && \ chown -R ${USERNAME}:${USERNAME} /var/log/nginx && \ diff --git a/Makefile b/Makefile index 6d7142547..0d44c2b6e 100644 --- a/Makefile +++ b/Makefile @@ -1,77 +1,71 @@ -.PHONY: help -help: - @echo "---------------HELP-----------------" - - @echo "- make build --> Create docker image with dependencies needed for development" - @echo "- make run --> Execute timetracker_ui docker container" - @echo "- make stop --> Stop container timetracker_ui" - @echo "- make restart --> Restart container timetracker_ui" - @echo "- make remove --> Delete container timetracker_ui" - @echo "- make test --> Run all tests on docker container timetracker_ui" - @echo "- make publish --> Publish the container image timetracker_ui" - - @echo "- make build_prod --> Create docker image with dependencies needed for production" - @echo "- make run_prod --> Execute timetracker_ui_prod docker container" - @echo "- make remove_prod --> Delete container timetracker_ui_prod" - @echo "- make publish_prod --> Publish the container image timetracker_ui_prod" +override SHELL := /bin/bash - @echo "- make login --> Login in respository of docker images" - @echo "------------------------------------" +.PHONY: help +help: ## Show this help message. + @echo 'Usage:' + @echo ' make [target] ...' + @echo + @echo 'Targets:' + @grep --no-filename -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: build -build: - -docker rmi timetracker_ui +build: ## Create docker image with dependencies needed for development. docker-compose build +.PHONY: cleanup +cleanup: ## Delete image timetracker_ui + docker rmi timetracker_ui + .PHONY: run -run: +run: ## Execute timetracker_ui docker containe. docker-compose --env-file ./.env up -d + +.PHONY: logs +logs: ## Show logs of timetracker_ui. docker logs -f timetracker_ui .PHONY: stop -stop: +stop: ## Stop container timetracker_ui. docker-compose stop .PHONY: restart -restart: +restart: ## Restart container timetracker_ui. docker-compose stop docker-compose up -d - docker logs -f timetracker_ui .PHONY: remove -remove: +remove: ## Delete container timetracker_ui. docker-compose down .PHONY: test -test: +test: ## Run all tests on docker container timetracker_ui. docker-compose --env-file ./.env up -d docker exec -it timetracker_ui bash -c "npm run test" .PHONY: publish -publish: - docker tag timetracker_ui:latest timetrackerdevregistry.azurecr.io/timetracker_ui:latest - docker push timetrackerdevregistry.azurecr.io/timetracker_ui:latest +publish: ## Publish the container image timetracker_ui. + docker tag timetracker_ui:latest $(registry_url)/timetracker_ui:latest + docker push $(registry_url)/timetracker_ui:latest .PHONY: build_prod -build_prod: - -docker rmi timetracker_ui_prod +build_prod: ## Create docker image with dependencies needed for production. docker build --target production -t timetracker_ui_prod -f Dockerfile . .PHONY: run_prod -run_prod: +run_prod: ## Execute timetracker_ui_prod docker container. docker run -d -p 4200:4200 --name timetracker_ui_prod timetracker_ui_prod - docker logs -f timetracker_ui_prod .PHONY: remove_prod -remove_prod: +remove_prod: ## Delete container timetracker_ui_pro. docker stop timetracker_ui_prod docker rm timetracker_ui_prod .PHONY: publish_prod -publish_prod: - docker tag timetracker_ui_prod:latest timetrackerdevregistry.azurecr.io/timetracker_ui_prod:latest - docker push timetrackerdevregistry.azurecr.io/timetracker_ui_prod:latest +publish_prod: ## Publish the container image timetracker_ui_prod. + docker tag timetracker_ui_prod:latest $(registry_url)/timetracker_ui_prod:latest + docker push $(registry_url)/timetracker_ui_prod:latest .PHONY: login -login: - az acr login --name timetrackerdevregistry \ No newline at end of file +login: ## Login in respository of docker images. + az acr login --name timetrackerdevregistry From 7f0690f4a504133e705ed3b19a6eea40042ca588 Mon Sep 17 00:00:00 2001 From: Michael Villarruel Date: Wed, 9 Feb 2022 16:46:27 -0500 Subject: [PATCH 17/17] fix: TT-511 changes in makefile to solve comments --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0d44c2b6e..d86ace915 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ restart: ## Restart container timetracker_ui. .PHONY: remove remove: ## Delete container timetracker_ui. - docker-compose down + docker-compose down --volumes --remove-orphans --rmi local .PHONY: test test: ## Run all tests on docker container timetracker_ui. @@ -68,4 +68,4 @@ publish_prod: ## Publish the container image timetracker_ui_prod. .PHONY: login login: ## Login in respository of docker images. - az acr login --name timetrackerdevregistry + az acr login --name $(container_registry)
{{NO_DATA_MESSAGE}}
{{ entry.start_date | date: 'MM/dd/yyyy' }} {{ entry.start_date | date: 'HH:mm' }} - {{ entry.end_date | date: 'HH:mm' }}