From 4f51335a6a9dc1d22ad22b4a2148e77dbc2ae6e6 Mon Sep 17 00:00:00 2001 From: Andres C Date: Mon, 24 Oct 2022 19:37:38 -0400 Subject: [PATCH 01/66] fix url for auth app --- .dev.env | Bin 745 -> 746 bytes src/app/modules/login/login.component.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.dev.env b/.dev.env index c947820272533b337ee430bd2f0668ee2c54f825..03e64abb03f99501b6a4342e3f57ec3714cf0112 100644 GIT binary patch literal 746 zcmV}70?-%r;SC0e?uU5 z?@NkS7RP}vN759HGn?UrtlU-k&EkD|ex`gwvPnu1t(-(5BwczSFEIVQ}#zR zye=3~E8;NL2+D7<<2TEf%^h+J{cB;m%E1IuhO3eVWBL&q*lyudp)>YW?1_%=5FEO5 z&z6flvJI(oakH#5#v-gzOpawcfNwiiQ%meKLt@n?)6O&1^vR!KX`69K!~(n+7c4Zk zgkQ}QI%S)Osox-o*Y{-T`SzxrAKD3gTA*OK?*9DQmX0OD9e~t`y+>UCj8P4ibvHUZ zKmZ>FYNE7`qd~S|ai#J(m9_p~^OdMFKn=J(fz2Q^D0Mw}^q~T=_sjW_LkboTO)%U2 z&la6vs5vQ7wXG*4Gky>8Nj$W2noDWYVR>Vd!ckx%~WkUjL2|HO(OcdDL#$35-U89J)5& z#LR>HYgeKU_$Hi^%&yEs@WmI>5qoozNmS?Cj8ykkoOc!r^buViUc`=?BL<35L^E&kgL&6(X>-Z*CkMp;^*<&~)a&Av&bs_we%?6x^Cm?Ug cQXW^)(geK7R?lV;5_vcjf;C89_Sb>u4xou|VgLXD literal 745 zcmV6<=U9CNN}8gXcT^rQ19WtYo~_0`okmAfu0Bf!w%*${3!NZHFmb##Nl zgn!~;$MWTMPH<07-Nzw)jl&PRZyN8}=f^@al~3`s%B)!a%UzG{z62LVy^8pU3c=|q z^jGQvTlT||1M*Kyb9WT7R8`0$uT?XL&FUA4LMwNoMQ>xJ{F`{lvRL4)i+$IE7%8gP zws($xB2l#mU?4+$A@4%j3TBBWRk>I0>G?TaR%jTu&Ww296NqLrqQCHGI$V&WN~@mE zO6>;J{8bN8jD!Ousmb2|q@9Xc$4?T`ON&ArM&TvBuB+JS0jy7{k`q&s1pvO>_ahAp z^IyQ7J4r3G_}8<>s`a(xJubKSKF{D8(9-lCN-V^?8(N$@44~r-&=}G2ZE=kA`$Jzr zPPx^2dn_TSE+f;{`^k=nsnDok%zbIUv04FiBVY{5Q&D2Qr4i4x`2~fxLs>os0lz%*yl;Azry&AxmuI3M-7!@!O76)~1FL<%)d+k9Pne7V-f_X`%eCJAoFJw7E2%Ig9jSu1MnGl>=0Gt0!75xg=Cu`sPJ$h`L02egwP32NPeQf?u2s+&W;uyrt(UY3ByP zvl1=PdzYrwcPFK{GvVuO=1vX~Kyh3^^@$g+F^Kcq4frq?DPKc7^B>X!wZx+$g59wn bfqwuW+6JwE^?l^}NRDTArIDZPP^m6&Z+~(= diff --git a/src/app/modules/login/login.component.ts b/src/app/modules/login/login.component.ts index 070a2f38..b6982f20 100644 --- a/src/app/modules/login/login.component.ts +++ b/src/app/modules/login/login.component.ts @@ -68,7 +68,7 @@ export class LoginComponent implements OnInit { } loginAuth() { - window.location.href = `${this.authUrl}/authn/login/${this.authAppName}`; + window.location.href = `${this.authUrl}authn/login/${this.authAppName}`; } } From c27026eb4de698f837f5d1122926d63bfa8139ac Mon Sep 17 00:00:00 2001 From: Andres Cabrera <85083117+andresacg30@users.noreply.github.com> Date: Tue, 25 Oct 2022 08:24:12 -0400 Subject: [PATCH 02/66] Tta 174 create pop up message for redirecting users from legacy to production (#935) * feat: TT-174 pop-up message for redirect to b2 from legacy * change variable from let to const * v2-redirect component * create html for figma design * erase images bg * Montserrat Font * add media screen for responsiveness * fix: TT-174 css * final styling * fix sonarcloud problems * fix tests errors * fix bad naming for css * fix import errors * abigails comments * adding test for conditional env checking * add title for html * remove app-routing-module from test * exclude test files * fix: TTA-174 update to check if it is legacy Co-authored-by: Nicole Garcia Co-authored-by: Andres Cabrera Co-authored-by: mmaquina --- src/app/app-routing.module.ts | 55 ++++-- src/app/app.module.ts | 2 + .../v2-redirect/v2-redirect.component.css | 164 ++++++++++++++++++ .../v2-redirect/v2-redirect.component.html | 47 +++++ .../v2-redirect/v2-redirect.component.spec.ts | 25 +++ .../v2-redirect/v2-redirect.component.ts | 10 ++ src/assets/img/decoration.png | Bin 0 -> 8278 bytes src/assets/img/main-image.png | Bin 0 -> 84342 bytes tsconfig.spec.json | 3 + 9 files changed, 288 insertions(+), 18 deletions(-) create mode 100644 src/app/modules/v2-redirect/v2-redirect.component.css create mode 100644 src/app/modules/v2-redirect/v2-redirect.component.html create mode 100644 src/app/modules/v2-redirect/v2-redirect.component.spec.ts create mode 100644 src/app/modules/v2-redirect/v2-redirect.component.ts create mode 100644 src/assets/img/decoration.png create mode 100644 src/assets/img/main-image.png diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 4cb50a9b..f5517232 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -11,27 +11,46 @@ 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 { V2RedirectComponent } from './modules/v2-redirect/v2-redirect.component'; +import { EnvironmentType } from 'src/environments/enum'; +import { environment } from 'src/environments/environment'; -const routes: Routes = [ - { - path: '', - component: HomeComponent, - canActivate: [LoginGuard], - children: [ - { path: 'reports', canActivate: [AdminGuard], component: ReportsComponent }, - { path: 'time-clock', component: TimeClockComponent }, - { path: 'time-entries', component: TimeEntriesComponent }, - { path: 'activities-management', component: ActivitiesManagementComponent }, - { path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent }, - { path: 'users', canActivate: [AdminGuard], component: UsersComponent }, - { path: '', pathMatch: 'full', redirectTo: 'time-clock' }, - ], - }, - { path: 'login', component: LoginComponent }, -]; +const isNotLegacy: boolean = environment.production !== EnvironmentType.TT_PROD_LEGACY; +let routes: Routes; +if (isNotLegacy) { + routes = [ + { + path: '', + component: HomeComponent, + canActivate: [LoginGuard], + children: [ + { path: 'reports', canActivate: [AdminGuard], component: ReportsComponent }, + { path: 'time-clock', component: TimeClockComponent }, + { path: 'time-entries', component: TimeEntriesComponent }, + { path: 'activities-management', component: ActivitiesManagementComponent }, + { path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent }, + { path: 'users', canActivate: [AdminGuard], component: UsersComponent }, + { path: '', pathMatch: 'full', redirectTo: 'time-clock' }, + ], + }, + { path: 'login', component: LoginComponent } + ]; + +} else { + routes = [ + { + path: '', + children: [ + { path: '**', component: V2RedirectComponent }, + ], + }, + ]; +} @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) -export class AppRoutingModule { } +export class AppRoutingModule { + +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a7115745..04e1ad0d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -97,6 +97,7 @@ import { SearchUserComponent } from './modules/shared/components/search-user/sea import { TimeRangeCustomComponent } from './modules/reports/components/time-range-custom/time-range-custom.component'; import { TimeRangeHeaderComponent } from './modules/reports/components/time-range-custom/time-range-header/time-range-header.component'; import { TimeRangeOptionsComponent } from './modules/reports/components/time-range-custom/time-range-options/time-range-options.component'; +import { V2RedirectComponent } from './modules/v2-redirect/v2-redirect.component'; import { SpinnerOverlayComponent } from './modules/shared/components/spinner-overlay/spinner-overlay.component'; import { SpinnerInterceptor } from './modules/shared/interceptors/spinner.interceptor'; @@ -158,6 +159,7 @@ const maskConfig: Partial = { TimeRangeCustomComponent, TimeRangeHeaderComponent, TimeRangeOptionsComponent, + V2RedirectComponent, SpinnerOverlayComponent, ], imports: [ diff --git a/src/app/modules/v2-redirect/v2-redirect.component.css b/src/app/modules/v2-redirect/v2-redirect.component.css new file mode 100644 index 00000000..85e23a77 --- /dev/null +++ b/src/app/modules/v2-redirect/v2-redirect.component.css @@ -0,0 +1,164 @@ +.btn-primary { + background-color: #F85221; + border-radius: 30px; + border: none; +} + +.btn-primary:hover { + background-color: #bf3816; +} + +h2 { + font-weight: 600; + color: #5B4F62; +} + +h1 { + font-weight: 800; + font-size:4em; + color: #5B4F62; + +} + +h3 { + font-weight: 500; + font-size: 2em; + color: #831C86; +} + +#main-image { + padding-top: 20%; + max-width: 100%; +} + +.grid { + display: grid; + grid-template-columns: [x0] 2fr [x1] 1fr [x2]; + justify-items: center; +} + + +#logo { + padding-top: 20px; + padding-left: 25px; + position:absolute; + max-width: 18rem; + left: 0; + top: 0; + z-index: 10; +} + +.decoration{ + z-index: 0; + position: absolute; + bottom:0; + right:0; +} + +.text{ + padding-top: 50%; + padding-right: 30%; +} + + +.redirection { + padding-top:1rem; + text-align: center; +} + +body { + text-align: center; + font-family: 'Montserrat', sans-serif; + } + +@media screen and (max-width: 1307px) { + + #main-image { + padding-top: 6%; + } + h1 { + color:#831C86; + } + + .grid { + display: grid; + grid-template-columns: 1fr; + position: relative; + width:auto; + } + + .text{ + padding-top: 2rem; + padding-right: 0px; + text-align: center; + + } + + .redirection { + padding-top: 0px; + padding-right: 0px; + margin-bottom: 30px; + text-align: center; + } + + #quote { + display: none; + } + +} + + +@media screen and (max-width: 430px) { + .btn-primary { + font-size:small; + + } + body{ + text-align: center; + } + h1 { + font-size: 2rem; + color:#831C86; + } + h2 { + font-size: 1rem; + } + .grid { + display: grid; + grid-template-columns: 1fr; + position: relative; + width:auto; + } + + .text{ + padding-top: 2rem; + padding-right: 0px; + text-align: center; + + } + + .redirection { + padding-top: 0px; + padding-right: 0px; + margin-bottom: 30px; + text-align: center; + } + + #quote { + display: none; + } + + .decoration { + display: none; + } + #logo { + padding-right: auto; + padding-left: auto; + padding-top: 20px; + position:absolute; + max-width: 80%; + left: 0; + top: 0; + z-index: 10; + } +} diff --git a/src/app/modules/v2-redirect/v2-redirect.component.html b/src/app/modules/v2-redirect/v2-redirect.component.html new file mode 100644 index 00000000..26b2c591 --- /dev/null +++ b/src/app/modules/v2-redirect/v2-redirect.component.html @@ -0,0 +1,47 @@ + + + + Time Tracker + + + + + + + + +
+ +
+
+ decoration blue splash +
+
+
+ person moving boxes +
+
+

WE HAVE MOVED

+

The world is in
constant evolution,
SO ARE WE.

+
+
+
+

GO TO TIME TRACKER V2

+
+ +
+
+ + diff --git a/src/app/modules/v2-redirect/v2-redirect.component.spec.ts b/src/app/modules/v2-redirect/v2-redirect.component.spec.ts new file mode 100644 index 00000000..19d380ef --- /dev/null +++ b/src/app/modules/v2-redirect/v2-redirect.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { V2RedirectComponent } from './v2-redirect.component'; + +describe('V2RedirectComponent', () => { + let component: V2RedirectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ V2RedirectComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(V2RedirectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/v2-redirect/v2-redirect.component.ts b/src/app/modules/v2-redirect/v2-redirect.component.ts new file mode 100644 index 00000000..61ccb090 --- /dev/null +++ b/src/app/modules/v2-redirect/v2-redirect.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-v2-redirect', + templateUrl: './v2-redirect.component.html', + styleUrls: ['./v2-redirect.component.css'] +}) +export class V2RedirectComponent { + +} diff --git a/src/assets/img/decoration.png b/src/assets/img/decoration.png new file mode 100644 index 0000000000000000000000000000000000000000..31ef4e8948aa39602902ecd09a4944cb917794a8 GIT binary patch literal 8278 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 literal 0 HcmV?d00001 diff --git a/src/assets/img/main-image.png b/src/assets/img/main-image.png new file mode 100644 index 0000000000000000000000000000000000000000..a0758159007eb9fb99877fffeb2af130329fdf42 GIT binary patch literal 84342 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 Date: Tue, 25 Oct 2022 08:30:04 -0400 Subject: [PATCH 03/66] fix legacy environment --- Makefile | 2 +- src/environments/environment.prodlegacy.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ebdd5391..8be575f6 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ publish_prod: ## Upload a docker image to the prod azure container registry acr= docker push $(acr).azurecr.io/timetracker_ui:$(image_tag) .PHONY: login -login: ## Login in respository of docker images. +login: ## Login in respository of docker images az acr login --name $(acr) .PHONY: release diff --git a/src/environments/environment.prodlegacy.ts b/src/environments/environment.prodlegacy.ts index 2ff198b9..67f0d76e 100644 --- a/src/environments/environment.prodlegacy.ts +++ b/src/environments/environment.prodlegacy.ts @@ -4,6 +4,8 @@ export const environment = { production: EnvironmentType.TT_PROD_LEGACY, timeTrackerApiUrl: process.env["API_URL"], stackexchangeApiUrl: 'https://api.stackexchange.com', + authUrl: process.env['AUTH_URL'], + authAppName: process.env['AUTH_APP_NAME'] }; export const AUTHORITY = process.env["AUTHORITY"]; From 387de8b8465f89d5647a9f3d51ec0b3ebeac2c13 Mon Sep 17 00:00:00 2001 From: Andres C Date: Tue, 25 Oct 2022 09:31:16 -0400 Subject: [PATCH 04/66] add slash to login redirection --- src/app/modules/login/login.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/login/login.component.ts b/src/app/modules/login/login.component.ts index b6982f20..070a2f38 100644 --- a/src/app/modules/login/login.component.ts +++ b/src/app/modules/login/login.component.ts @@ -68,7 +68,7 @@ export class LoginComponent implements OnInit { } loginAuth() { - window.location.href = `${this.authUrl}authn/login/${this.authAppName}`; + window.location.href = `${this.authUrl}/authn/login/${this.authAppName}`; } } From 0e04fe133b8654225c68ead49250b1f1913a4316 Mon Sep 17 00:00:00 2001 From: Andres Cabrera <85083117+andresacg30@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:15:16 -0400 Subject: [PATCH 05/66] This reverts commit 5863564d3f8d2dfe938ed92761fcb039cd12fb84. (#946) --- .dev.env | Bin 746 -> 608 bytes .github/workflows/CD-time-tracker-ui.yml | 2 - .github/workflows/CI-time-tracker-ui.yml | 2 - .github/workflows/time-tracker-ui-ci.yml | 6 +- .prod.env | Bin 697 -> 623 bytes .stage.env | Bin 723 -> 687 bytes Makefile | 2 +- README.md | 7 -- docker-compose.yml | 4 -- scripts/populate-keys.sh | 2 - .../services/activity.service.ts | 8 +-- .../services/project-type.service.ts | 8 +-- .../components/services/project.service.ts | 12 ++-- .../services/customer.service.ts | 8 +-- src/app/modules/login/login.component.html | 11 +++- src/app/modules/login/login.component.spec.ts | 10 --- src/app/modules/login/login.component.ts | 61 ++++++++++++++---- .../login/services/login.service.spec.ts | 11 +--- .../modules/login/services/login.service.ts | 21 ++---- .../dark-mode/dark-mode.component.spec.ts | 3 +- .../components/sidebar/sidebar.component.html | 10 +-- .../components/sidebar/sidebar.component.scss | 6 +- .../sidebar/sidebar.component.spec.ts | 2 +- .../feature-toggle-cookies.service.spec.ts | 3 +- .../feature-toggle-general.service.spec.ts | 3 +- .../pages/time-clock.component.spec.ts | 3 +- .../time-clock/services/entry.service.ts | 25 +++---- src/app/modules/user/services/user.service.ts | 2 +- .../users-list/users-list.component.spec.ts | 3 +- .../modules/users/services/users.service.ts | 10 +-- src/environments/environment.prod.ts | 2 - src/environments/environment.prodlegacy.ts | 2 +- src/environments/environment.ts | 3 - src/polyfills.ts | 4 -- webpack.config.js | 2 - 35 files changed, 114 insertions(+), 144 deletions(-) diff --git a/.dev.env b/.dev.env index 03e64abb03f99501b6a4342e3f57ec3714cf0112..7e4a97803a72483670c5c6415995ff46a1d44506 100644 GIT binary patch literal 608 zcmV-m0-yZ=M@dveQdv+`0Qfqz7!$jCQU`uJzN2|mp?J7H>}P4@mo#`HX``acA(Ou- z{y5M%c!5U@tGJu;{4shY=$R%)auJ+lMih&5WzNzaO31PhaL_}eFYO6a*Wa4b_ExNn z-kXQFD(rcenfTX#l%7>2&^N9Xy5eduJ2BrDiDqWmB9qU_SnRo@ebwge>wjWC2kXQorrusb-^M^Ho6BNMJYIj(lg{CzAlr7+) z62GT>rVbyV0_E&&KQT^=VI~0Ug3)iQ%)&;Y>OL$~4mI?o7fACBX7Ec8m6cNOxzzgTGMTx%3DJ>&* uODPU|w71~j7Y77A0q?xb`Wws28M=#)+dMn literal 746 zcmV}70?-%r;SC0e?uU5 z?@NkS7RP}vN759HGn?UrtlU-k&EkD|ex`gwvPnu1t(-(5BwczSFEIVQ}#zR zye=3~E8;NL2+D7<<2TEf%^h+J{cB;m%E1IuhO3eVWBL&q*lyudp)>YW?1_%=5FEO5 z&z6flvJI(oakH#5#v-gzOpawcfNwiiQ%meKLt@n?)6O&1^vR!KX`69K!~(n+7c4Zk zgkQ}QI%S)Osox-o*Y{-T`SzxrAKD3gTA*OK?*9DQmX0OD9e~t`y+>UCj8P4ibvHUZ zKmZ>FYNE7`qd~S|ai#J(m9_p~^OdMFKn=J(fz2Q^D0Mw}^q~T=_sjW_LkboTO)%U2 z&la6vs5vQ7wXG*4Gky>8Nj$W2noDWYVR>Vd!ckx%~WkUjL2|HO(OcdDL#$35-U89J)5& z#LR>HYgeKU_$Hi^%&yEs@WmI>5qoozNmS?Cj8ykkoOc!r^buViUc`=?BL<35L^E&kgL&6(X>-Z*CkMp;^*<&~)a&Av&bs_we%?6x^Cm?Ug cQXW^)(geK7R?lV;5_vcjf;C89_Sb>u4xou|VgLXD diff --git a/.github/workflows/CD-time-tracker-ui.yml b/.github/workflows/CD-time-tracker-ui.yml index 35f33462..f4c22fa2 100644 --- a/.github/workflows/CD-time-tracker-ui.yml +++ b/.github/workflows/CD-time-tracker-ui.yml @@ -34,8 +34,6 @@ jobs: CLIENT_ID: ${{ secrets.client_id }} CLIENT_URL: ${{ secrets.client_url }} STACK_EXCHANGE_ID: ${{ secrets.stack_exchange_id }} - AUTH_URL: ${{ secrets.AUTH_URL }} - AUTH_APP_NAME: ${{ secrets.AUTH_APP_NAME }} STACK_EXCHANGE_ACCESS_TOKEN: ${{ secrets.stack_exchange_access_token }} AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${{ secrets.azure_app_configuration_connection_string }} run: | diff --git a/.github/workflows/CI-time-tracker-ui.yml b/.github/workflows/CI-time-tracker-ui.yml index 39e7b208..6c971a73 100644 --- a/.github/workflows/CI-time-tracker-ui.yml +++ b/.github/workflows/CI-time-tracker-ui.yml @@ -46,8 +46,6 @@ jobs: SCOPES: ${{ secrets.SCOPES }} CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_URL : ${{ secrets.CLIENT_URL }} - AUTH_URL: ${{ secrets.AUTH_URL }} - AUTH_APP_NAME: ${{ secrets.AUTH_APP_NAME }} 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 }} diff --git a/.github/workflows/time-tracker-ui-ci.yml b/.github/workflows/time-tracker-ui-ci.yml index 9a461e93..d7992606 100644 --- a/.github/workflows/time-tracker-ui-ci.yml +++ b/.github/workflows/time-tracker-ui-ci.yml @@ -29,16 +29,12 @@ jobs: with: ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} - - name: Unlock DEV secrets - uses: sliteteam/github-action-git-crypt-unlock@1.2.0 - env: - GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_DEFAULT }} - - name: build docker run: make build - name: Running tests run: | + chmod -R 777 ./$home make test - name: Generate coverage report env: diff --git a/.prod.env b/.prod.env index 7faa2df46d12fe61ea012c0a21924623d1219f36..141800a505ae6667a0c9578f44c297d9e43868c6 100644 GIT binary patch literal 623 zcmV-#0+9UxM@dveQdv+`04$x=-hyPkebi*k6*MXbdDamde~h^m#Nwhe3~3TP1%@GLFKj# z+{LPs5Rvmc_E#o$vB*Ngtv(0x8!wp2oyJO@L0SN|jMq`&s5ge_6|zG~E)NfYr+ne! zzh7R6mho}^IX^o$7<)ev4ut;+0_yDy7jDN`G>`;6;C^gI=IqlG3`XouGzS>mUQwpt zIEtXG8E8a~QuX@xI+|}{zvf_w;^Dz8ZKaJ~@eYdU_9~_RNoyB&EA9wkD_-9=(ilQz za)Q?8n0(%t>SMtLCT?~T#!e`vaZ{l`^ZP`?Z5W0uds6oK6Y(kw!W?e4C!<<{siELYTK)W*G6|@8o)Ta(M50aW_Pq#qqZ1B_Gv9ev4{`c38>`&OIV-Wkx26Cv8QZ(n{O2kyzLDQm5vAWVuEL{qQ+dPLrhJ5UC7s?ESqHAaZ^{{iM z^!As-0Y^g6w&ezIZ#GGX^~V09qhPl~KDV5VdgYrg^k5_2Nq z;r6&W%trs3KiA`*h_BwS>{4^sSK3idDsylGe#~Z>V)x}wBc_V JRo;)X{2+Cng93urBQsk(H8Fyu6eCi*Y|qb>kuFahJWgAg(eR!+si-f#{sQymCG_Zt@p2S7igBdi{-)?>D+_ z*Tu!545JOFp42nvl7%he3_1OIw2GqhBIOQTDS8fblt}ss!R8m+`>js+6I-wWzaU&$!cSq=r?9xS@)-1+%UPgKmm`vYx|1|jDpHqv*vANd&k~B{6^}i5 zO*BL~9dPgXC&Fi_D}Lc<@OB(tDRX19z}#%2y_lUdwO_@sQ8`Dq;yaFIzY1{B2ophl zFHwglb_NL>{9!ny-H~>mRRI#&9sxC)jg)<7`x%lyXVnkNBPl*x7{s&y{=(D;Ixt#$RWIn{a>%GXV`5Uz)7%hI7u46Wh#6(q6u4g4HPP5NxTN*u;9Bf z8%)d-CbxZHO%}GVm>TzJ@n0GMsb(o;&iA%gS%b$|tNvK4>0FPmpS2&JhR2qr7Cx4-8|4UE*C2XAf*^S_%~0 ftsjO|S3^5u$ZBg2m?*S6tS4p{4Xp4g@la$NpO{5S diff --git a/.stage.env b/.stage.env index 4be383343028dc474bbbbbf392f7a78d16fe08b1..563577e93a8564cbf7982429e8ed80e5c14b1589 100644 GIT binary patch literal 687 zcmV;g0#N+`M@dveQdv+`08R5iLPajdLgvHvEYPw^Nm=1;BvC$NmMv zX%$@7k1r@W1b!>phe?)-GG9Qx<|5!cuoOMOZb-9EsbFDaZ^ATj?hgeV`(}^YN)shg zLJoc_nQ4jPra983qj&Z?GJpk@!2y5zPp^;8{%x#Rt(+MMm2H}&7Fp!+PPtutA679i=;5x~2%3(`Bw zCfTON%ZP2vbVMYejQ9*+RhZ6y_X(h?C(I(?FWWgdH1R{H*;|E5Ipr5RHGDbRVem^v z7sv9!S)4{>bTF%VLf6Pt+?&3XSt{%tD}P^IDs%qScbdWvG(eQiguE1wJP>&K!m-zXO{+;d4r3uhpYF>R<}ZFN zV~t;X;lv1i8u1Jxk?dqg%!F&Xr-tMyr@P)6b`g$|X+>u;Sm?TeM=qsc@z58xCe4E@>n|ZJg{M{_$YLPx{8b92&)dza zB!;}Q0m-(xnNnAp!T*ISX$vs=>HoSJ+c>f1Gng)-uq%I!IImy&DDpP^xfT57B2ku5 z!g#=!+Yc4JgewGFLz~S-vC>`b&}oDmJwAI$!_*ihw&Na-{XKlk|Ff>4fZyeaEe99RbXED-8Ca++IzuXlbMC$}72&*=dR`Vtt>jS4k6U{u+>UIs_@n2bKu1`3z6?OpIQ!k4_K z4xnyat6r{{vucKomtrfU`RIdO<9Ggb>Mg zsf4el==na3G2#Z-ZmuRQi3&$@sh2dkgQP~>(2n_Q%wS$<`d^1&JdMP|aBnpjX${TA Fec@IoX(Rvu diff --git a/Makefile b/Makefile index 8be575f6..db0a0ca2 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ remove: ## Delete container timetracker_ui. .PHONY: test test: ## Run all tests on docker container timetracker_ui at the CLI. docker-compose build timetracker_ui_test - docker-compose --env-file=.dev.env up -d timetracker_ui_test + docker-compose up -d timetracker_ui_test docker logs -f timetracker_ui_test .PHONY: testdev diff --git a/README.md b/README.md index ce0baa2f..94e90975 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,6 @@ In project path, open your favourite command line and run `npm install` in order # Prepare your environment -### **Local DNS Configuration** - -To test the application in a local environment please modify you `/etc/hosts` on Linux/Mac. In Windows `C:\Windows\System32\Drivers\etc\hosts` and add this line: -```text -127.0.0.1 timetracker-dev.ioet.com -``` - ### Set environment variables **1**. Using GPG create your key by running this command in your favourite command shell: `gpg --generate-key`. diff --git a/docker-compose.yml b/docker-compose.yml index 7d1f6e50..da0835dd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,6 @@ services: timetracker_ui: container_name: timetracker_ui image: timetracker_ui - env_file: - - .dev.env build: context: . dockerfile: ./Docker/Dockerfile.dev @@ -16,8 +14,6 @@ services: API_URL: ${API_URL} CLIENT_ID: ${CLIENT_ID} CLIENT_URL: ${CLIENT_URL} - AUTH_URL: ${AUTH_URL} - AUTH_APP_NAME: ${AUTH_APP_NAME} SCOPES: ${SCOPES} STACK_EXCHANGE_ID: ${STACK_EXCHANGE_ID} STACK_EXCHANGE_ACCESS_TOKEN: ${STACK_EXCHANGE_ACCESS_TOKEN} diff --git a/scripts/populate-keys.sh b/scripts/populate-keys.sh index 0a86def5..7dd58421 100644 --- a/scripts/populate-keys.sh +++ b/scripts/populate-keys.sh @@ -5,8 +5,6 @@ echo "API_URL='$API_URL'" >> .env echo "AUTHORITY='$AUTHORITY'" >> .env echo "CLIENT_ID='$CLIENT_ID'" >> .env echo "CLIENT_URL='$CLIENT_URL'" >> .env -echo "AUTH_URL='$AUTH_URL'" >> .env -echo "AUTH_APP_NAME='$AUTH_APP_NAME'" >> .env echo "SCOPES='$SCOPES'" >> .env echo "STACK_EXCHANGE_ID='$STACK_EXCHANGE_ID'" >> .env echo "STACK_EXCHANGE_ACCESS_TOKEN='$STACK_EXCHANGE_ACCESS_TOKEN'" >> .env diff --git a/src/app/modules/activities-management/services/activity.service.ts b/src/app/modules/activities-management/services/activity.service.ts index d413e0c9..e17cb728 100644 --- a/src/app/modules/activities-management/services/activity.service.ts +++ b/src/app/modules/activities-management/services/activity.service.ts @@ -14,7 +14,7 @@ export class ActivityService { constructor(private http: HttpClient) {} getActivities(): Observable { - return this.http.get(this.baseUrl, { withCredentials: true }); + return this.http.get(this.baseUrl); } createActivity(activityData): Observable { @@ -23,12 +23,12 @@ export class ActivityService { tenant_id: '4225ab1e-1033-4a5f-8650-0dd4950f38c8', }; - return this.http.post(this.baseUrl, body, { withCredentials: true }); + return this.http.post(this.baseUrl, body); } deleteActivity(acitivityId: string): Observable { const url = `${this.baseUrl}/${acitivityId}`; - return this.http.delete(url, { withCredentials: true }); + return this.http.delete(url); } updateActivity(activityData): Observable { @@ -38,6 +38,6 @@ export class ActivityService { ...activityData, }; - return this.http.put(url, body, { withCredentials: true }); + return this.http.put(url, body); } } diff --git a/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts b/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts index 1c54fbea..6d9e672c 100644 --- a/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts +++ b/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts @@ -15,20 +15,20 @@ export class ProjectTypeService { getProjectTypes(customerId: any): Observable { const params = new HttpParams().set('customer_id', customerId.customerId); - return this.http.get(this.baseUrl, { params, withCredentials: true }); + return this.http.get(this.baseUrl, { params }); } createProjectType(projectTypeData): Observable { - return this.http.post(this.baseUrl, projectTypeData, { withCredentials: true }); + return this.http.post(this.baseUrl, projectTypeData); } deleteProjectType(projectTypeId: string): Observable { const url = `${this.baseUrl}/${projectTypeId}`; - return this.http.delete(url, { withCredentials: true }); + return this.http.delete(url); } updateProjectType(projectTypeData): Observable { const url = `${this.baseUrl}/${projectTypeData.id}`; - return this.http.put(url, projectTypeData, { withCredentials: true }); + return this.http.put(url, projectTypeData); } } diff --git a/src/app/modules/customer-management/components/projects/components/services/project.service.ts b/src/app/modules/customer-management/components/projects/components/services/project.service.ts index 4ae84c09..c5b431b1 100644 --- a/src/app/modules/customer-management/components/projects/components/services/project.service.ts +++ b/src/app/modules/customer-management/components/projects/components/services/project.service.ts @@ -17,19 +17,19 @@ export class ProjectService { getProjects(customerId: any): Observable { const params = new HttpParams().set('customer_id', customerId.customerId); - return this.http.get(this.url, { params, withCredentials: true }); + return this.http.get(this.url, { params }); } getAllProjects(): Observable { - return this.http.get(this.url, { withCredentials: true }); + return this.http.get(this.url); } getRecentProjects(): Observable { - return this.http.get(`${this.url}/recent`, { withCredentials: true }); + return this.http.get(`${this.url}/recent`); } createProject(projectData): Observable { - return this.http.post(this.url, projectData, { withCredentials: true }); + return this.http.post(this.url, projectData); } updateProject(projectData): Observable { @@ -39,12 +39,12 @@ export class ProjectService { projectData.status = 1; } } - return this.http.put(`${this.url}/${id}`, projectData, { withCredentials: true }); + return this.http.put(`${this.url}/${id}`, projectData); } deleteProject(projectId: string): Observable { return this.isDevelopmentOrProd ? this.http.put(`${this.url}/${projectId}`, { status: 0 }) - : this.http.delete(`${this.url}/${projectId}`, { withCredentials: true }); + : this.http.delete(`${this.url}/${projectId}`); } } diff --git a/src/app/modules/customer-management/services/customer.service.ts b/src/app/modules/customer-management/services/customer.service.ts index 880e58fc..cc930a54 100644 --- a/src/app/modules/customer-management/services/customer.service.ts +++ b/src/app/modules/customer-management/services/customer.service.ts @@ -13,20 +13,20 @@ export class CustomerService { constructor(private http: HttpClient) {} createCustomer(customerData): Observable { - return this.http.post(this.baseUrl, customerData, { withCredentials: true }); + return this.http.post(this.baseUrl, customerData); } getCustomers(): Observable { - return this.http.get(this.baseUrl, { withCredentials: true }); + return this.http.get(this.baseUrl); } deleteCustomer(customerId: string): Observable { const url = `${this.baseUrl}/${customerId}`; - return this.http.delete(url, { withCredentials: true }); + return this.http.delete(url); } updateCustomer(customerData): Observable { const url = `${this.baseUrl}/${customerData.id}`; - return this.http.put(url, customerData, { withCredentials: true }); + return this.http.put(url, customerData); } } diff --git a/src/app/modules/login/login.component.html b/src/app/modules/login/login.component.html index 43e07d27..9ad2d90d 100644 --- a/src/app/modules/login/login.component.html +++ b/src/app/modules/login/login.component.html @@ -11,6 +11,15 @@

Please log in

- +
+
+
diff --git a/src/app/modules/login/login.component.spec.ts b/src/app/modules/login/login.component.spec.ts index 99cf8b6b..9de35924 100644 --- a/src/app/modules/login/login.component.spec.ts +++ b/src/app/modules/login/login.component.spec.ts @@ -99,16 +99,6 @@ describe('LoginComponent', () => { expect(component).toBeTruthy(); }); - it('should set local storage when the component is initialized and is not legacy production', inject([Router], (router: Router) => { - component.isProduction = false; - spyOn(loginService, 'getUser').and.returnValue(of(userTest)); - spyOn(loginService, 'setLocalStorage'); - - component.ngOnInit(); - - expect(loginService.setLocalStorage).toHaveBeenCalled(); - })); - it('should sign up or login with google if is not logged-in into the app on Production', inject([Router], (router: Router) => { spyOn(azureAdB2CService, 'isLogin').and.returnValue(false); spyOn(azureAdB2CService, 'setCookies').and.returnValue(); diff --git a/src/app/modules/login/login.component.ts b/src/app/modules/login/login.component.ts index 070a2f38..792fc56e 100644 --- a/src/app/modules/login/login.component.ts +++ b/src/app/modules/login/login.component.ts @@ -22,8 +22,6 @@ declare global { export class LoginComponent implements OnInit { isProduction = environment.production === EnvironmentType.TT_PROD_LEGACY; cliendId = CLIENT_URL; - authUrl = environment.authUrl; - authAppName = environment.authAppName; auth2: any; @@ -36,16 +34,57 @@ export class LoginComponent implements OnInit { private ngZone?: NgZone ) {} + + googleAuthSDK() { + const sdkLoaded = 'googleSDKLoaded'; + const gapi = 'gapi'; + + (window as any)[sdkLoaded] = () => { + (window as any)[gapi].load('auth2', () => { + this.auth2 = ( window as any)[gapi].auth2.init({ + client_id: this.cliendId, + plugin_name: 'login', + cookiepolicy: 'single_host_origin', + scope: 'profile email' + }); + }); + }; + + (async (d, s, id) => { + const keyGoogle = 'src'; + const gjs = d.getElementsByTagName(s)[1]; + let js = gjs; + if (d.getElementById(id)) { return; } + js = d.createElement(s); js.id = id; + js[keyGoogle] = 'https://accounts.google.com/gsi/client'; + gjs.parentNode.insertBefore(js, gjs); + })(document, 'script', 'async defer'); + } + ngOnInit() { - if (!this.isProduction) { - this.loginService.getUser(null).subscribe((resp) => { - this.loginService.setCookies(); - const tokenObject = JSON.stringify(resp); - const tokenJson = JSON.parse(tokenObject); - this.loginService.setLocalStorage('user', tokenJson.token); - this.ngZone.run(() => this.router.navigate([''])); + + this.googleAuthSDK(); + if (this.isProduction && this.azureAdB2CService.isLogin()) { + this.router.navigate(['']); + } else { + this.loginService.isLogin().subscribe(isLogin => { + if (isLogin) { + this.router.navigate(['']); + } }); } + window.handleCredentialResponse = (response) => { + const {credential = ''} = response; + this.featureToggleCookiesService.setCookies(); + this.loginService.setLocalStorage('idToken', credential); + this.loginService.getUser(credential).subscribe((resp) => { + this.loginService.setCookies(); + const tokenObject = JSON.stringify(resp); + const tokenJson = JSON.parse(tokenObject); + this.loginService.setLocalStorage('user', tokenJson.token); + this.ngZone.run(() => this.router.navigate([''])); + }); + }; } login(): void { @@ -67,8 +106,4 @@ export class LoginComponent implements OnInit { } } - loginAuth() { - window.location.href = `${this.authUrl}/authn/login/${this.authAppName}`; - } - } diff --git a/src/app/modules/login/services/login.service.spec.ts b/src/app/modules/login/services/login.service.spec.ts index 67fe3498..94e246cc 100644 --- a/src/app/modules/login/services/login.service.spec.ts +++ b/src/app/modules/login/services/login.service.spec.ts @@ -4,7 +4,6 @@ import { TestBed } from '@angular/core/testing'; import { JwtHelperService } from '@auth0/angular-jwt'; import { SocialAuthService } from 'angularx-social-login'; import { CookieService } from 'ngx-cookie-service'; -import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; import { LoginService } from './login.service'; @@ -24,11 +23,11 @@ describe('LoginService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + imports: [HttpClientTestingModule], providers: [ { providers: CookieService, useValue: cookieStoreStub }, { provide: SocialAuthService, useValue: socialAuthServiceStub }, - { provide: HttpClient, useValue: httpClientSpy }, + { provide: HttpClient, useValue: httpClientSpy } ], }); service = TestBed.inject(LoginService); @@ -123,7 +122,6 @@ describe('LoginService', () => { it('should logout with social angularx-social-login', () => { spyOn(cookieService, 'deleteAll').and.returnValue(); - spyOn(service, 'invalidateSessionCookie').and.returnValue(of(true)); service.logout(); @@ -131,11 +129,6 @@ describe('LoginService', () => { expect(cookieService.deleteAll).toHaveBeenCalled(); }); - it('should return an http observable when call invalidateSessionCooke', () => { - const result = service.invalidateSessionCookie(); - expect(result).toBeDefined(); - }); - it('should call cookieService when app is isLegacyProd', () => { service.isLegacyProd = true; service.localStorageKey = 'user2'; diff --git a/src/app/modules/login/services/login.service.ts b/src/app/modules/login/services/login.service.ts index 061a98cb..8a086982 100644 --- a/src/app/modules/login/services/login.service.ts +++ b/src/app/modules/login/services/login.service.ts @@ -1,12 +1,11 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable, NgZone } from '@angular/core'; +import { Injectable } from '@angular/core'; import { CookieService } from 'ngx-cookie-service'; import { EnvironmentType, UserEnum } from 'src/environments/enum'; import { environment } from 'src/environments/environment'; import { JwtHelperService } from '@auth0/angular-jwt'; import { map } from 'rxjs/operators'; import { of } from 'rxjs'; -import { Router } from '@angular/router'; @Injectable({ providedIn: 'root' @@ -16,29 +15,18 @@ export class LoginService { helper: JwtHelperService; isLegacyProd: boolean = environment.production === EnvironmentType.TT_PROD_LEGACY; localStorageKey = this.isLegacyProd ? 'user2' : 'user'; - ngZone?: NgZone; - constructor( private http?: HttpClient, private cookieService?: CookieService, - private router?: Router, ) { this.baseUrl = `${environment.timeTrackerApiUrl}/users`; this.helper = new JwtHelperService(); - this.router = router; } logout() { - localStorage.clear(); this.cookieService.deleteAll(); - this.invalidateSessionCookie().toPromise().then(() => { - this.router.navigate(['login']); - }); - } - - invalidateSessionCookie() { - return this.http.post(`${this.baseUrl}/logout`, null, { withCredentials: true }); + localStorage.clear(); } isLogin() { @@ -104,7 +92,7 @@ export class LoginService { token: tokenString, }; - return this.http.post(`${this.baseUrl}/login`, body, { withCredentials: true }); + return this.http.post(`${this.baseUrl}/login`, body); } setCookies() { @@ -121,7 +109,7 @@ export class LoginService { isValidToken(token: string) { const body = { token }; - return this.http.post(`${this.baseUrl}/validate-token`, body, { withCredentials: true }).pipe( + return this.http.post(`${this.baseUrl}/validate-token`, body).pipe( map((response) => { const responseString = JSON.stringify(response); const responseJson = JSON.parse(responseString); @@ -132,4 +120,5 @@ export class LoginService { }) ); } + } diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts index cf421cb9..e9e81b6f 100644 --- a/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts @@ -7,7 +7,6 @@ import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggl import { FeatureToggleModel } from '../../feature-toggles/feature-toggle.model'; import { FeatureFilterModel } from '../../feature-toggles/filters/feature-filter.model'; import { DarkModeComponent } from './dark-mode.component'; -import { RouterTestingModule } from '@angular/router/testing'; describe('DarkModeComponent', () => { let component: DarkModeComponent; @@ -18,7 +17,7 @@ describe('DarkModeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DarkModeComponent], - imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + imports: [HttpClientTestingModule], providers: [{ provide: SocialAuthService, useValue: socialAuthServiceStub }] }).compileComponents(); }); diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.html b/src/app/modules/shared/components/sidebar/sidebar.component.html index d76e5da9..66d7881b 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.html +++ b/src/app/modules/shared/components/sidebar/sidebar.component.html @@ -17,10 +17,10 @@

{{ item.text }}

@@ -40,10 +40,10 @@

{{ item.text }}
-
+
- + \ No newline at end of file diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.scss b/src/app/modules/shared/components/sidebar/sidebar.component.scss index 5b7ecb78..7b1bfa5f 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.scss +++ b/src/app/modules/shared/components/sidebar/sidebar.component.scss @@ -4,10 +4,6 @@ body { overflow-x: hidden; } -button.logout:active { - outline: none; -} - #sidebar-wrapper { min-height: 100vh; margin-left: -15rem; @@ -80,4 +76,4 @@ button.logout:active { height: -webkit-calc(100vh - 1vh); height: -o-calc(100vh - 1vh); height: calc(100vh - 1vh); -} +} \ No newline at end of file diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts b/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts index de989047..7603ca63 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts +++ b/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts @@ -14,7 +14,7 @@ describe('SidebarComponent', () => { let azureAdB2CServiceStubInjected; let loginServiceStubInjected: LoginService; let userInfoService: UserInfoService; - let router: Router; + let router; const routes: Routes = [{ path: 'time-clock', component: TimeClockComponent }]; const azureAdB2CServiceStub = { diff --git a/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts b/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts index 0d21d38b..1fe77a7f 100644 --- a/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts +++ b/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts @@ -7,7 +7,6 @@ import { FeatureToggleGeneralService } from '../feature-toggle-general/feature-t import { FeatureToggleModel } from '../feature-toggle.model'; import { TargetingFeatureFilterModel } from '../filters/targeting/targeting-feature-filter.model'; import { FeatureToggleCookiesService } from './feature-toggle-cookies.service'; -import { RouterTestingModule } from '@angular/router/testing'; describe('FeatureToggleCookiesService', () => { let cookieService: CookieService; @@ -17,7 +16,7 @@ describe('FeatureToggleCookiesService', () => { const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['authState']); beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + imports: [HttpClientTestingModule], providers: [CookieService, FeatureToggleGeneralService, { provide: SocialAuthService, useValue: socialAuthServiceStub } ] diff --git a/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts b/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts index 8a8e8eb3..60becca2 100644 --- a/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts +++ b/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts @@ -6,7 +6,6 @@ import { FeatureToggleModel } from '../feature-toggle.model'; import { TargetingFeatureFilterModel } from '../filters/targeting/targeting-feature-filter.model'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { SocialAuthService } from 'angularx-social-login'; -import { RouterTestingModule } from '@angular/router/testing'; describe('FeatureToggleGeneralService', () => { @@ -16,7 +15,7 @@ describe('FeatureToggleGeneralService', () => { const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['authState']); beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + imports: [HttpClientTestingModule], providers: [ { provide: FeatureManagerService }, { provide: SocialAuthService, useValue: socialAuthServiceStub } 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 8f9b7075..d5dd535f 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 @@ -14,7 +14,6 @@ import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.co import { ToastrService } from 'ngx-toastr'; import { LoginService } from '../../login/services/login.service'; import { SocialAuthService } from 'angularx-social-login'; -import { RouterTestingModule } from '@angular/router/testing'; describe('TimeClockComponent', () => { let component: TimeClockComponent; @@ -56,7 +55,7 @@ describe('TimeClockComponent', () => { beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, RouterTestingModule], + imports: [HttpClientTestingModule], declarations: [TimeClockComponent, ProjectListHoverComponent, FilterProjectPipe, EntryFieldsComponent], providers: [ FormBuilder, diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index 76c970da..e8b9a1b6 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -23,53 +23,49 @@ export class EntryService { urlInProductionLegacy = environment.production === EnvironmentType.TT_PROD_LEGACY; loadActiveEntry(): Observable { - return this.http.get(`${this.baseUrl}/running`, { withCredentials: true }); + return this.http.get(`${this.baseUrl}/running`); } loadEntries(date): Observable { const timezoneOffset = new Date().getTimezoneOffset(); - return this.http.get( - `${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`, - { withCredentials: true } - ); + return this.http.get(`${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); } createEntry(entryData): Observable { - return this.http.post(this.baseUrl, entryData, { withCredentials: true }); + return this.http.post(this.baseUrl, entryData); } updateEntry(entryData): Observable { const {id} = entryData; - return this.http.put(`${this.baseUrl}/${id}`, entryData, { withCredentials: true }); + return this.http.put(`${this.baseUrl}/${id}`, entryData); } deleteEntry(entryId: string): Observable { const url = `${this.baseUrl}/${entryId}`; - return this.http.delete(url, { withCredentials: true }); + return this.http.delete(url); } stopEntryRunning(idEntry: string): Observable { return (this.urlInProductionLegacy ? - this.http.post(`${this.baseUrl}/${idEntry}/stop`, null, { withCredentials: true }) : - this.http.put(`${this.baseUrl}/stop`, null, { withCredentials: true }) ); + this.http.post(`${this.baseUrl}/${idEntry}/stop`, null) : this.http.put(`${this.baseUrl}/stop`, null) ); } restartEntry(idEntry: string): Observable { const url = `${this.baseUrl}/${idEntry}/restart`; - return this.http.post(url, null, { withCredentials: true }); + return this.http.post(url, null); } summary(): Observable { const timeOffset = new Date().getTimezoneOffset(); const summaryUrl = `${this.baseUrl}/summary?time_offset=${timeOffset}`; - return this.http.get(summaryUrl, { withCredentials: true }); + return this.http.get(summaryUrl); } findEntriesByProjectId(projectId: string): Observable { const startDate = this.getDateLastMonth(); const endDate = this.getCurrentDate(); const findEntriesByProjectURL = `${this.baseUrl}?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`; - return this.http.get(findEntriesByProjectURL, { withCredentials: true }); + return this.http.get(findEntriesByProjectURL); } loadEntriesByTimeRange(range: TimeEntriesTimeRange, userId: string): Observable { @@ -83,8 +79,7 @@ export class EntryService { user_id: userId, limit: `${MAX_NUMBER_OF_ENTRIES_FOR_REPORTS}`, timezone_offset : new Date().getTimezoneOffset().toString(), - }, - withCredentials: true + } } ); } diff --git a/src/app/modules/user/services/user.service.ts b/src/app/modules/user/services/user.service.ts index fffa3268..0eeb6a9e 100644 --- a/src/app/modules/user/services/user.service.ts +++ b/src/app/modules/user/services/user.service.ts @@ -16,6 +16,6 @@ export class UserService { loadUser(userId: any): Observable { const url = `${this.baseUrl}/${userId}`; - return this.http.get(url, { withCredentials: true }); + return this.http.get(url); } } diff --git a/src/app/modules/users/components/users-list/users-list.component.spec.ts b/src/app/modules/users/components/users-list/users-list.component.spec.ts index 87f23992..ada0b01e 100644 --- a/src/app/modules/users/components/users-list/users-list.component.spec.ts +++ b/src/app/modules/users/components/users-list/users-list.component.spec.ts @@ -9,7 +9,6 @@ import { DataTablesModule } from 'angular-datatables'; import { GrantUserRole, RevokeUserRole } from '../../store/user.actions'; import { ROLES } from '../../../../../environments/environment'; import { LoginService } from '../../../login/services/login.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; import { UserInfoService } from 'src/app/modules/user/services/user-info.service'; @@ -45,7 +44,7 @@ describe('UsersListComponent', () => { beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NgxPaginationModule, DataTablesModule, HttpClientTestingModule, RouterTestingModule.withRoutes([])], + imports: [NgxPaginationModule, DataTablesModule, HttpClientTestingModule], declarations: [UsersListComponent], providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }, diff --git a/src/app/modules/users/services/users.service.ts b/src/app/modules/users/services/users.service.ts index 6a36bd2a..5f6ba6ea 100644 --- a/src/app/modules/users/services/users.service.ts +++ b/src/app/modules/users/services/users.service.ts @@ -15,30 +15,30 @@ export class UsersService { baseUrl = `${environment.timeTrackerApiUrl}/users`; loadUsers(): Observable { - return this.http.get(this.baseUrl, { withCredentials: true }); + return this.http.get(this.baseUrl); } grantRole(userId: string, roleId: string): Observable { const url = this.isProductionLegacy ? `${this.baseUrl}/${userId}/roles/${roleId}/grant` : `${this.baseUrl}/${userId}/${roleId}/grant`; - return this.http.post(url, null, { withCredentials: true }); + return this.http.post(url, null); } revokeRole(userId: string, roleId: string): Observable { const url = this.isProductionLegacy ? `${this.baseUrl}/${userId}/roles/${roleId}/revoke` : `${this.baseUrl}/${userId}/${roleId}/revoke`; - return this.http.post(url, null, { withCredentials: true }); + return this.http.post(url, null); } addUserToGroup(userId: string, group: string): Observable { return this.http.post(`${this.baseUrl}/${userId}/groups/add`, { group_name: group, - }, { withCredentials: true }); + }); } removeUserFromGroup(userId: string, group: string): Observable { return this.http.post(`${this.baseUrl}/${userId}/groups/remove`, { group_name: group, - }, { withCredentials: true }); + }); } } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index e679026f..cc90f444 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -4,8 +4,6 @@ export const environment = { production: EnvironmentType.TT_PROD, timeTrackerApiUrl: process.env["API_URL"], stackexchangeApiUrl: 'https://api.stackexchange.com', - authUrl: process.env['AUTH_URL'], - authAppName: process.env['AUTH_APP_NAME'], }; export const AUTHORITY = process.env["AUTHORITY"]; diff --git a/src/environments/environment.prodlegacy.ts b/src/environments/environment.prodlegacy.ts index 67f0d76e..340d9fa8 100644 --- a/src/environments/environment.prodlegacy.ts +++ b/src/environments/environment.prodlegacy.ts @@ -2,7 +2,7 @@ import { EnvironmentType } from './enum'; export const environment = { production: EnvironmentType.TT_PROD_LEGACY, - timeTrackerApiUrl: process.env["API_URL"], + timeTrackerApiUrl: process.env["API_URL"], stackexchangeApiUrl: 'https://api.stackexchange.com', authUrl: process.env['AUTH_URL'], authAppName: process.env['AUTH_APP_NAME'] diff --git a/src/environments/environment.ts b/src/environments/environment.ts index e724acd9..f5e34a55 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -8,14 +8,11 @@ export const environment = { production: EnvironmentType.TT_DEV, timeTrackerApiUrl: process.env['API_URL'], stackexchangeApiUrl: 'https://api.stackexchange.com', - authUrl: process.env['AUTH_URL'], - authAppName: process.env['AUTH_APP_NAME'], }; export const AUTHORITY = process.env['AUTHORITY']; export const CLIENT_ID = process.env['CLIENT_ID']; export const CLIENT_URL = process.env['CLIENT_URL']; -export const AUTH_URL = process.env['AUTH_URL']; export const SCOPES = process.env['SCOPES'].split(','); export const STACK_EXCHANGE_ID = process.env['STACK_EXCHANGE_ID']; export const STACK_EXCHANGE_ACCESS_TOKEN = process.env['STACK_EXCHANGE_ACCESS_TOKEN']; diff --git a/src/polyfills.ts b/src/polyfills.ts index 1c221773..03711e5d 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -61,7 +61,3 @@ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ - -(window as any).process = { - env: { DEBUG: undefined }, -}; diff --git a/webpack.config.js b/webpack.config.js index 35792935..44c1ce49 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,8 +13,6 @@ module.exports = (config) => { 'process.env.AUTHORITY': JSON.stringify(process.env["AUTHORITY"]), 'process.env.API_URL':JSON.stringify(process.env["API_URL"]), 'process.env.CLIENT_ID':JSON.stringify(process.env["CLIENT_ID"]), - 'process.env.AUTH_URL':JSON.stringify(process.env["AUTH_URL"]), - 'process.env.AUTH_APP_NAME':JSON.stringify(process.env["AUTH_APP_NAME"]), 'process.env.CLIENT_URL':JSON.stringify(process.env["CLIENT_URL"]), 'process.env.SCOPES':JSON.stringify(process.env["SCOPES"]), 'process.env.STACK_EXCHANGE_ID':JSON.stringify(process.env["STACK_EXCHANGE_ID"]), From ab393cf7a01542b3efc3f328fa76968d2ed14d80 Mon Sep 17 00:00:00 2001 From: Andres C Date: Thu, 27 Oct 2022 14:30:14 -0400 Subject: [PATCH 06/66] fix: added valid stage envs --- .stage.env | Bin 687 -> 639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.stage.env b/.stage.env index 563577e93a8564cbf7982429e8ed80e5c14b1589..7e97985779683edd93f1f6db371dc95ee7477102 100644 GIT binary patch literal 639 zcmV-_0)YJhM@dveQdv+`0AS#hnTtw&YS9a`U7zlaLM$)a-nh4(wgba!!iLp8pOxbQ&{d!Tp*(A<`XP7!1 zm|qML1##Nh3tA{blahW+2>Tpm+x|zGVmyyS;+9I5F8{QT7V3o*Vd>+!DfEFG~Y2#szf>IBIzQ z^iq-=y=%@vTn11-#?~I>C$Q$>mdImE&WgD1n?XeuY5k4}SAVYo0IfIF7sw`rW$hE| zeXmoUXkwlrM3;B99Y_HSktrBrz|nsf#F>m3XVgzTl?B{#DXC#ntt%3txh`a3*d%yl zR?HBvIYY3?v|i(+{b4uHp!AeSMRVECvz1Uk|I37iR8V(yo>s^1N8q(F!}|XBT3(;M zN@i1~@ugnoI@{tfU*+>Qh{dI^u4Xq1tik#bZHOQha!a0 zug8vm>yGse%frK#bfZPTgv2sYb-yyA#!k%};PR)L_p1uaQBkN;OF9jH5x?t-db9SL Zz;M+b3%On@XFOGRrX<`o0U}8M$NmMv zX%$@7k1r@W1b!>phe?)-GG9Qx<|5!cuoOMOZb-9EsbFDaZ^ATj?hgeV`(}^YN)shg zLJoc_nQ4jPra983qj&Z?GJpk@!2y5zPp^;8{%x#Rt(+MMm2H}&7Fp!+PPtutA679i=;5x~2%3(`Bw zCfTON%ZP2vbVMYejQ9*+RhZ6y_X(h?C(I(?FWWgdH1R{H*;|E5Ipr5RHGDbRVem^v z7sv9!S)4{>bTF%VLf6Pt+?&3XSt{%tD}P^IDs%qScbdWvG(eQigu Date: Thu, 27 Oct 2022 14:30:54 -0400 Subject: [PATCH 07/66] fix: added valid prod envs --- .prod.env | Bin 623 -> 633 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.prod.env b/.prod.env index 141800a505ae6667a0c9578f44c297d9e43868c6..b245c8510ddd48767e8c79053504caa5b3974306 100644 GIT binary patch literal 633 zcmV-<0*3tnM@dveQdv+`06D=)kO@E7;9^|+;!>FZL*lJxhabw9(B7$8{kphT_>*n5 z&2Sz|Z2F)7SlZ%3GJN{6lyIVS`GkV2^ImO4f8E%!m*;vYqzXtX6#A)()QB~0Nha?? zF%5K(dy^YShuWkcWBVRUs_IWGw4H&i%OoSiRneR`$88Sd2{7L;Ck8uxdKF|E^MUWc zY)018!uTD+H&11}hi~iA#wU^gyYyR$k0g^!u_oDt$6)rAr0~#h^D)GasA&aUGagkB zBKfxz*TF#;o%Y6UEwQ20Nle`SV_c-97*R%#z0`Voe7Y#S#uLr!pPCHAw!H;X^fA9r zRPKM);~bLh>*4mv(=&)?5!P+fwA?pnY4WXG@kg}|&tSOy809}Cw4E+|see+mszZ-k zL2_0d`IyFt688s8946t-R#yJRS#{%x#pRyRT=)ueT&vnj2b>QWmteO&SCmQIe@6Zu zx|OSU0B_9(`=!DTkbI0eGVNNrnvzqWQFAcuA&sT^@(m z11pvZA=JOGl)qqyo6LQEjs$s^ z-#?Gif+pn`Ojw#ik!MVYW;G)o>!4Oc`>yeF-91k^BHxcGutu1IMFu$1--WUT83%d$ z%X;!u`VhcT7vi;!bE^<_R$L+jk{C#zjgl3?(W?c_2rE2I(Kd4mgR>mvhNMPZlwVHy TbC;Pvpf4sOe^RMqjdJyfD_TIc literal 623 zcmV-#0+9UxM@dveQdv+`04$x=-hyPkebi*k6*MXbdDamde~h^m#Nwhe3~3TP1%@GLFKj# z+{LPs5Rvmc_E#o$vB*Ngtv(0x8!wp2oyJO@L0SN|jMq`&s5ge_6|zG~E)NfYr+ne! zzh7R6mho}^IX^o$7<)ev4ut;+0_yDy7jDN`G>`;6;C^gI=IqlG3`XouGzS>mUQwpt zIEtXG8E8a~QuX@xI+|}{zvf_w;^Dz8ZKaJ~@eYdU_9~_RNoyB&EA9wkD_-9=(ilQz za)Q?8n0(%t>SMtLCT?~T#!e`vaZ{l`^ZP`?Z5W0uds6oK6Y(kw!W?e4C!<<{siELYTK)W*G6|@8o)Ta(M50aW_Pq#qqZ1B_Gv9ev4{`c38>`&OIV-Wkx26Cv8QZ(n{O2kyzLDQm5vAWVuEL{qQ+dPLrhJ5UC7s?ESqHAaZ^{{iM z^!As-0Y^g6w&ezIZ#GGX^~V09qhPl~KDV5VdgYrg^k5_2Nq z;r6&W%trs3KiA`*h_BwS>{4^sSK3idDsylGe#~Z>V)x}wBc_V JRo;)X{2+ Date: Thu, 27 Oct 2022 14:31:44 -0400 Subject: [PATCH 08/66] fix: added valid env envs --- .dev.env | Bin 608 -> 607 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.dev.env b/.dev.env index 7e4a97803a72483670c5c6415995ff46a1d44506..5db598c8d5fc1f09be3da5d92834511cff4108ad 100644 GIT binary patch literal 607 zcmV-l0-*f>M@dveQdv+`0H!&9AOfcH?Q=@FZP%E3cm18^r4p$3Fa6v>3=q{6PtZG_ z7OtGV)c8~IY?eDmRXU*f__2jx{=hQXxORwdY4EGcj^Rs}r9iXpH^d1@#F5}?j2LGR zh1DFCCc<`8SD7@lm1Yx5=&sA-c{DQs$#Qd44l^bBG*vSy#WX1(|9noVld!q=v~K~U zgPPGZ9q+e)xRQYwqx|Uf*Z8!5Ll5ySv`llf4|^#mwFw3EisnAT&|$eFEkw47MCyzU z{x8Y6%w``KeF?`Q`9?a56OOMV2k%WFG=H%5kEjWh+EK1&VSyh%P|3cJr|wGPN7kmp zj71$@tM^HXYI0&WapAimfYS=dwK|Gw*`8M zPOo-FDs`tkK9Wvh=pq7kX`G-+BLfsP!Qn*t@IO22J(eDAWMri9<0F+9-z7r0R}7k` t^RtsHZbmj}P4@mo#`HX``acA(Ou- z{y5M%c!5U@tGJu;{4shY=$R%)auJ+lMih&5WzNzaO31PhaL_}eFYO6a*Wa4b_ExNn z-kXQFD(rcenfTX#l%7>2&^N9Xy5eduJ2BrDiDqWmB9qU_SnRo@ebwge>wjWC2kXQorrusb-^M^Ho6BNMJYIj(lg{CzAlr7+) z62GT>rVbyV0_E&&KQT^=VI~0Ug3)iQ%)&;Y>OL$~4mI?o7fACBX7Ec8m6cNOxzzgTGMTx%3DJ>&* uODPU|w71~j7Y77A0q?xb`Wws28M=#)+dMn From 3341e03c457a85325c5fa82afa689603006902f7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 27 Oct 2022 18:34:40 +0000 Subject: [PATCH 09/66] chore(release): 1.75.40 [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 9f01219e..346262d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.75.24", + "version": "1.75.40", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index bbeb9c3f..0fad3f42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.75.24", + "version": "1.75.40", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From eed3671a7df106dac6c90bc79084c8baae7d7df8 Mon Sep 17 00:00:00 2001 From: Andres C Date: Thu, 27 Oct 2022 15:07:21 -0400 Subject: [PATCH 10/66] fix legacy routing --- src/environments/environment.prodlegacy.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/environments/environment.prodlegacy.ts b/src/environments/environment.prodlegacy.ts index 340d9fa8..c372c3ae 100644 --- a/src/environments/environment.prodlegacy.ts +++ b/src/environments/environment.prodlegacy.ts @@ -4,8 +4,6 @@ export const environment = { production: EnvironmentType.TT_PROD_LEGACY, timeTrackerApiUrl: process.env["API_URL"], stackexchangeApiUrl: 'https://api.stackexchange.com', - authUrl: process.env['AUTH_URL'], - authAppName: process.env['AUTH_APP_NAME'] }; export const AUTHORITY = process.env["AUTHORITY"]; From bb0477193d5e28c4fc4e15964fd5ef297eafdfdc Mon Sep 17 00:00:00 2001 From: mmaquina Date: Fri, 28 Oct 2022 12:31:33 -0300 Subject: [PATCH 11/66] TTA-196-duration-column-formatting-ui (#941) * refactor: TTA-196 duration as float on reports * changed pipe function * refactor: TTA-196 display hours as float * refactor: TTA-196 fix imports and lint errors * refactor: TTA-196 number of fixed digits as constant with name * fix: TTA-196 variable was ill defined * refactor: TTA-196 2 digits for fixed point duration of time entries in reports * fix: TTA-196 fixed tests for 2 fixed point digits. TODO: use that constant in tests Co-authored-by: mmaquina --- .gitignore | 1 + src/app/app.module.ts | 2 + .../time-entries-table.component.html | 128 ++++++++++-------- .../time-entries-table.component.spec.ts | 3 +- .../time-entries-table.component.ts | 1 - .../interceptors/spinner.interceptor.spec.ts | 8 +- .../interceptors/spinner.interceptor.ts | 7 +- .../substract-date-return-float.pipe.spec.ts | 46 +++++++ .../substract-date-return-float.pipe.ts | 26 ++++ .../services/spinner-overlay.service.ts | 2 +- 10 files changed, 154 insertions(+), 70 deletions(-) create mode 100644 src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.spec.ts create mode 100644 src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.ts diff --git a/.gitignore b/.gitignore index 2312832f..64fbbec7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ testem.log /typings debug.log *.vscode +.hintrc # System Files .DS_Store diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 04e1ad0d..d01a2896 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -72,6 +72,7 @@ import { UserEffects } from './modules/user/store/user.effects'; import { EntryEffects } from './modules/time-clock/store/entry.effects'; import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor'; import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe'; +import { SubstractDatePipeDisplayAsFloat } from './modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe'; import { TechnologiesComponent } from './modules/shared/components/technologies/technologies.component'; import { TimeEntriesSummaryComponent } from './modules/time-clock/components/time-entries-summary/time-entries-summary.component'; import { TimeDetailsPipe } from './modules/time-clock/pipes/time-details.pipe'; @@ -138,6 +139,7 @@ const maskConfig: Partial = { CreateProjectTypeComponent, EntryFieldsComponent, SubstractDatePipe, + SubstractDatePipeDisplayAsFloat, TechnologiesComponent, SearchUserComponent, TimeEntriesSummaryComponent, diff --git a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.html b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.html index e5809d4b..256ab28b 100644 --- a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.html +++ b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.html @@ -1,64 +1,74 @@
- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + +
SelectedIDUser emailDateDurationTime inTime outProjectProject IDCustomerCustomer IDActivityTicketDescriptionTechnologies
{{ entry.id }}{{ entry.owner_email }} - {{ entry.start_date | date: 'MM/dd/yyyy' }} - - {{ entry.end_date | substractDate: entry.start_date }} - {{ dateTimeOffset.parseDateTimeOffset(entry.start_date,entry.timezone_offset) }}{{ dateTimeOffset.parseDateTimeOffset(entry.end_date , entry.timezone_offset) }}{{ entry.project_name }}{{ entry.project_id }}{{ entry.customer_name }}{{ entry.customer_id }}{{ entry.activity_name }} - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - -
SelectedIDUser emailDateDurationTime inTime outProjectProject IDCustomerCustomer IDActivityTicketDescriptionTechnologies
+ + {{ entry.id }}{{ entry.owner_email }} + {{ entry.start_date | date: 'MM/dd/yyyy' }} + + {{ entry.end_date | substractDateDisplayAsFloat: entry.start_date }} + {{ dateTimeOffset.parseDateTimeOffset(entry.start_date, entry.timezone_offset) }}{{ dateTimeOffset.parseDateTimeOffset(entry.end_date, entry.timezone_offset) }}{{ entry.project_name }}{{ entry.project_id }}{{ entry.customer_name }}{{ entry.customer_id }}{{ entry.activity_name }} + + {{ entry.uri }} - - {{ entry.description }} - -
- {{ technology }} -
-
-
+
+
{{ entry.description }} + +
+ {{ technology }} +
+
+
+
+
+ Total: {{ this.resultSum.hours }} hours, {{ this.resultSum.minutes }} minutes,
+ Total hours entries selected: {{ resultSumEntriesSelected.hours }} hours, + {{ resultSumEntriesSelected.minutes }} minutes
-
Total: {{this.resultSum.hours}} hours, {{this.resultSum.minutes}} minutes, -
Total hours entries selected: {{resultSumEntriesSelected.hours}} hours, {{resultSumEntriesSelected.minutes}} minutes
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 30c2d788..6f312ff8 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 @@ -4,6 +4,7 @@ import { DataTablesModule } from 'angular-datatables'; import { NgxPaginationModule } from 'ngx-pagination'; import { Entry } from 'src/app/modules/shared/models'; import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe'; +import { SubstractDatePipeDisplayAsFloat } from 'src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe'; import { getReportDataSource, getResultSumEntriesSelected } from 'src/app/modules/time-clock/store/entry.selectors'; import { EntryState } from '../../../time-clock/store/entry.reducer'; import { TimeEntriesTableComponent } from './time-entries-table.component'; @@ -80,7 +81,7 @@ describe('Reports Page', () => { waitForAsync(() => { TestBed.configureTestingModule({ imports: [NgxPaginationModule, DataTablesModule], - declarations: [TimeEntriesTableComponent, SubstractDatePipe], + declarations: [TimeEntriesTableComponent, SubstractDatePipe, SubstractDatePipeDisplayAsFloat], providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }], }).compileComponents(); 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 502d6a7c..8b4d5ab8 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 @@ -183,4 +183,3 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn return this.resultSumEntriesSelected; } } - diff --git a/src/app/modules/shared/interceptors/spinner.interceptor.spec.ts b/src/app/modules/shared/interceptors/spinner.interceptor.spec.ts index aa7d28bb..b34ec1ed 100644 --- a/src/app/modules/shared/interceptors/spinner.interceptor.spec.ts +++ b/src/app/modules/shared/interceptors/spinner.interceptor.spec.ts @@ -16,11 +16,11 @@ describe('SpinnerInterceptorService test', () => { ], }); - class MockHttpHandler implements HttpHandler { - handle(req: HttpRequest): Observable> { - return of(new HttpResponse()); - } + class MockHttpHandler implements HttpHandler { + handle(req: HttpRequest): Observable> { + return of(new HttpResponse()); } + } let overlay: Overlay; let httpHandler: HttpHandler; diff --git a/src/app/modules/shared/interceptors/spinner.interceptor.ts b/src/app/modules/shared/interceptors/spinner.interceptor.ts index 20062827..a6923414 100644 --- a/src/app/modules/shared/interceptors/spinner.interceptor.ts +++ b/src/app/modules/shared/interceptors/spinner.interceptor.ts @@ -17,14 +17,13 @@ export class SpinnerInterceptor implements HttpInterceptor { req: HttpRequest, next: HttpHandler ): Observable> { - if(req.url.endsWith('recent')){ + if (req.url.endsWith('recent')) { const spinnerSubscription: Subscription = this.spinnerOverlayService.spinner$.subscribe(); - return next + return next .handle(req) .pipe(finalize(() => spinnerSubscription.unsubscribe())); - }else{ + } else { return next.handle(req); } - } } diff --git a/src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.spec.ts b/src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.spec.ts new file mode 100644 index 00000000..fb5ea1cb --- /dev/null +++ b/src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.spec.ts @@ -0,0 +1,46 @@ +import { SubstractDatePipeDisplayAsFloat } from './substract-date-return-float.pipe'; + +describe('SubstractDatePipeDisplayAsFloat', () => { + it('create an instance', () => { + const pipe = new SubstractDatePipeDisplayAsFloat(); + expect(pipe).toBeTruthy(); + }); + + /*TODO: tests will be more robust if they take into account FIXED_POINT_DIGITS*/ + it('returns the date diff as float hours (xx.xx)', () => { + [ + { endDate: '2021-04-11T10:20:00Z', startDate: '2021-04-11T08:00:00Z', expectedDiff: '2.33' }, + { endDate: '2021-04-11T17:40:00Z', startDate: '2021-04-11T17:10:00Z', expectedDiff: '0.50' }, + { endDate: '2021-04-11T18:18:00Z', startDate: '2021-04-11T18:00:00Z', expectedDiff: '0.30' }, + { endDate: '2021-04-12T12:18:00Z', startDate: '2021-04-11T10:00:00Z', expectedDiff: '26.30' }, + { endDate: '2021-04-12T10:01:00Z', startDate: '2021-04-12T10:00:00Z', expectedDiff: '0.02' }, + { endDate: '2021-04-11T11:27:00Z', startDate: '2021-04-11T10:03:45Z', expectedDiff: '1.39' }, + ].forEach(({ startDate, endDate, expectedDiff }) => { + const fromDate = new Date(endDate); + const substractDate = new Date(startDate); + + const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate); + + expect(diff).toBe(expectedDiff); + }); + }); + + it('returns -.- if fromDate is null', () => { + const fromDate = null; + const substractDate = new Date('2011-04-11T08:00:30Z'); + + const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate); + + expect(diff).toBe('-.-'); + }); + + it('returns -.- if substractDate is null', () => { + const fromDate = new Date('2011-04-11T08:00:30Z'); + const substractDate = null; + + const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate); + + expect(diff).toBe('-.-'); + }); + +}); diff --git a/src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.ts b/src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.ts new file mode 100644 index 00000000..318dae1d --- /dev/null +++ b/src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import * as moment from 'moment'; + +const FIXED_POINT_DIGITS = 2; +@Pipe({ + name: 'substractDateDisplayAsFloat' +}) +export class SubstractDatePipeDisplayAsFloat implements PipeTransform { + + transform(fromDate: Date, substractDate: Date): string { + + if (fromDate === null || substractDate === null) { + return '-.-'; + } + + const startDate = moment(substractDate); + const endDate = moment(fromDate); + const duration = this.getTimeDifference(startDate, endDate); + return duration.asHours().toFixed(FIXED_POINT_DIGITS).toString(); + } + + getTimeDifference(substractDate: moment.Moment, fromDate: moment.Moment): moment.Duration { + return moment.duration(fromDate.diff(substractDate)); + } + +} diff --git a/src/app/modules/shared/services/spinner-overlay.service.ts b/src/app/modules/shared/services/spinner-overlay.service.ts index 1b74a6f2..dc00becf 100644 --- a/src/app/modules/shared/services/spinner-overlay.service.ts +++ b/src/app/modules/shared/services/spinner-overlay.service.ts @@ -9,8 +9,8 @@ import { SpinnerOverlayComponent } from './../components/spinner-overlay/spinner providedIn: 'root', }) export class SpinnerOverlayService { - public overlayRef: OverlayRef = undefined; static spinner$: any; + public overlayRef: OverlayRef = undefined; constructor(private readonly overlay: Overlay) {} From 90a1cca9c5eeb4b94ba6225cbd35b1f1990134ab Mon Sep 17 00:00:00 2001 From: Andres Cabrera <85083117+andresacg30@users.noreply.github.com> Date: Thu, 3 Nov 2022 12:08:29 -0400 Subject: [PATCH 12/66] fix: TTA-209 add subscription to login component for redirecting user (#949) * fix: TTA-209 add subscription to login component for redirecting users when token expires to login page * TTA-209 added redirectioning for any 401 status response --- src/app/modules/login/login.component.ts | 5 +---- .../interceptors/inject.token.interceptor.ts | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app/modules/login/login.component.ts b/src/app/modules/login/login.component.ts index 792fc56e..91ad64a6 100644 --- a/src/app/modules/login/login.component.ts +++ b/src/app/modules/login/login.component.ts @@ -64,15 +64,12 @@ export class LoginComponent implements OnInit { ngOnInit() { this.googleAuthSDK(); - if (this.isProduction && this.azureAdB2CService.isLogin()) { - this.router.navigate(['']); - } else { this.loginService.isLogin().subscribe(isLogin => { if (isLogin) { this.router.navigate(['']); } }); - } + window.handleCredentialResponse = (response) => { const {credential = ''} = response; this.featureToggleCookiesService.setCookies(); diff --git a/src/app/modules/shared/interceptors/inject.token.interceptor.ts b/src/app/modules/shared/interceptors/inject.token.interceptor.ts index b1310044..d045b0e6 100644 --- a/src/app/modules/shared/interceptors/inject.token.interceptor.ts +++ b/src/app/modules/shared/interceptors/inject.token.interceptor.ts @@ -4,18 +4,22 @@ import { HttpInterceptor, HttpHandler, HttpRequest, + HttpErrorResponse, } from '@angular/common/http'; import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; import { AzureAdB2CService } from 'src/app/modules/login/services/azure.ad.b2c.service'; import { environment } from './../../../../environments/environment'; import { EnvironmentType } from 'src/environments/enum'; import { LoginService } from '../../login/services/login.service'; +import { catchError } from 'rxjs/operators'; +import { Router } from '@angular/router'; @Injectable() export class InjectTokenInterceptor implements HttpInterceptor { isProduction = environment.production === EnvironmentType.TT_PROD_LEGACY; - constructor(private azureAdB2CService: AzureAdB2CService, private loginService: LoginService) { } + constructor(private azureAdB2CService: AzureAdB2CService, private loginService: LoginService, private router: Router) { } intercept(request: HttpRequest, next: HttpHandler): Observable> { if (request.url.startsWith(environment.timeTrackerApiUrl)) { @@ -25,7 +29,17 @@ export class InjectTokenInterceptor implements HttpInterceptor { headers: request.headers.set('Authorization', 'Bearer ' + token) }); - return next.handle(requestWithHeaders); + return next.handle(requestWithHeaders) + .pipe( + tap(() => { }, (err: any) => { + if (err instanceof HttpErrorResponse) { + if (err.status === 401) { + this.loginService.logout(); + window.open("/login", "_self") + } + } + }) + ); } else { return next.handle(request); } From ff13573ecdf83ceb3778bebff3a32b179a003872 Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:09:18 -0500 Subject: [PATCH 13/66] fix: TTA-214-add-loader-to-the-log-in-button (#947) Co-authored-by: Nicole Garcia --- src/app/modules/shared/interceptors/spinner.interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/shared/interceptors/spinner.interceptor.ts b/src/app/modules/shared/interceptors/spinner.interceptor.ts index a6923414..2cf717ce 100644 --- a/src/app/modules/shared/interceptors/spinner.interceptor.ts +++ b/src/app/modules/shared/interceptors/spinner.interceptor.ts @@ -17,7 +17,7 @@ export class SpinnerInterceptor implements HttpInterceptor { req: HttpRequest, next: HttpHandler ): Observable> { - if (req.url.endsWith('recent')) { + if (req.url.endsWith('recent') || req.url.endsWith('login')) { const spinnerSubscription: Subscription = this.spinnerOverlayService.spinner$.subscribe(); return next .handle(req) From 0498d2eef5a5ee0069559136a9ddab249342f928 Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:09:58 -0500 Subject: [PATCH 14/66] fix: TTA-216-fix-sort-by-options-in-reports-page (#948) Co-authored-by: Nicole Garcia --- .../time-entries-table/time-entries-table.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8b4d5ab8..dfa94b87 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 @@ -68,7 +68,7 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}` }, ], - columnDefs: [{ type: 'date', targets: 2}, {orderable: false, targets: [0]}], + columnDefs: [{ type: 'date', targets: 3}, {orderable: false, targets: [0]}], order: [[1, 'asc'], [2, 'desc'], [4, 'desc']] }; dtTrigger: Subject = new Subject(); From 60bf73c92a7870af24a90c2aa4a0b72d6527cde4 Mon Sep 17 00:00:00 2001 From: Andres C Date: Wed, 16 Nov 2022 10:47:54 -0400 Subject: [PATCH 15/66] added ignacio key to stage and prod --- .../C0B8FDDB97F4FAA788831E2C922549E3E0324188.gpg | Bin 0 -> 603 bytes .../C0B8FDDB97F4FAA788831E2C922549E3E0324188.gpg | Bin 0 -> 606 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/PROD/0/C0B8FDDB97F4FAA788831E2C922549E3E0324188.gpg create mode 100644 .git-crypt/keys/STAGE/0/C0B8FDDB97F4FAA788831E2C922549E3E0324188.gpg diff --git a/.git-crypt/keys/PROD/0/C0B8FDDB97F4FAA788831E2C922549E3E0324188.gpg b/.git-crypt/keys/PROD/0/C0B8FDDB97F4FAA788831E2C922549E3E0324188.gpg new file mode 100644 index 0000000000000000000000000000000000000000..08dd3d7b9d31172db880fa35792fa16cbe0bde8b GIT binary patch literal 603 zcmV-h0;K(g0gMCLH*yMRPv!vu3;$F2F*lpot5%Q!ma z6=2DyaWGu)pbetXMa+NReagxU_#^cs_gU%$w~n>{SSfJEd!PS9&DJ^Ik43`lWtdeo zZbofLYnwH;K}m}!a5wa_W_Q`E5X!YR^`_2^hnU%k13*GWws`TrUp41rHOM>e-H|NK zK`w~ABBc%dQ&e~EMAV)W2MZVx$SJ7Y(70*)HRtWuHB=mfY%sXEnB~&I2?2w2s_PHw zfNJ8}-V;_|Z@*3g2!Wl2h(skdK+?WAN3cv~h7>np+EZKUJhXvH#v53rF&VyXq5E0r zdSKp}Tm_~dg|)@=m!AXclIz{DElv$8@vKT+PA}!G>UT3wNQ3ur#Hl?{&S)4Fh)Mwf zUnRZLAQ6cm&BXB!1I`vnp?ciANa%EJ1tt@){}ws?X&$ig>!oP}w ziNyj>-v{+fb;Dpz0B8-Wl1%s-=EKr0%UD9?36GPgOd-L*s12OF6@v0nv5~#Wne$O* zbIi9tUi0)A`Ys(yIUEfo%7;8E=yJODwGyhuZ6MwtnwID3p><3=+90#xj%U{s9e7R! z1mQSlp|46nfRbkD2=Kjn-0Ec8WuG+i+D;k8$~sSl+MCX(?f@y?i2G)+a7PqpPUO;xCgZ!1=1UK@K2?4XI1$j5GpEoJYxCL` zwqF8C0K!b}m_M4lRw;l^+=p6sbXppJ(YJmcLrTE`4Jj`qM%1tceA{zz#t>`CX{i%K z&k|&|ExHhGiwy>n*A7&eP#&%#kV?Zy+Vco9P|ZvI7ORPE0Sh;YTUhJ@yyU?+DSGd( s+kEHYg886Ho~b>`HVKA+rDl?ka+iXfBD)6pNU|o47Y@yO3j-rC2f1=0`~Uy| literal 0 HcmV?d00001 From 1e23750c3c8e7346f8ac2477a81651a53553572d Mon Sep 17 00:00:00 2001 From: mmaquina Date: Tue, 22 Nov 2022 15:52:29 -0300 Subject: [PATCH 16/66] hot-fix: TTA-666 injectTokenInterceptor injectTokenInterceptor now takes 2 args instead of 3 the last one wasn't used --- src/app/modules/shared/interceptors/inject.token.interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/shared/interceptors/inject.token.interceptor.ts b/src/app/modules/shared/interceptors/inject.token.interceptor.ts index d045b0e6..58752c9a 100644 --- a/src/app/modules/shared/interceptors/inject.token.interceptor.ts +++ b/src/app/modules/shared/interceptors/inject.token.interceptor.ts @@ -19,7 +19,7 @@ import { Router } from '@angular/router'; @Injectable() export class InjectTokenInterceptor implements HttpInterceptor { isProduction = environment.production === EnvironmentType.TT_PROD_LEGACY; - constructor(private azureAdB2CService: AzureAdB2CService, private loginService: LoginService, private router: Router) { } + constructor(private azureAdB2CService: AzureAdB2CService, private loginService: LoginService) { } intercept(request: HttpRequest, next: HttpHandler): Observable> { if (request.url.startsWith(environment.timeTrackerApiUrl)) { From a939732f9d28dfe8f95f5fee3e53ccf0ec32a379 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Fri, 25 Nov 2022 17:28:25 -0300 Subject: [PATCH 17/66] add jerson (#957) --- .../E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg | Bin 0 -> 597 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/default/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg diff --git a/.git-crypt/keys/default/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg b/.git-crypt/keys/default/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg new file mode 100644 index 0000000000000000000000000000000000000000..3b109da75583cb0d90019125209ec50c4b732e66 GIT binary patch literal 597 zcmV-b0;>Im0gMBfDd@=Dfl)I73;h{RjZ!=qFQ5Cjhn0YxZ@%h+otw0q{EX>q@dr{$ z^;0?}V$j|iL~apKQR<4(Jj|EOMJ#rT9Car+QFcwH_Ytj2iG^OZAUHm)=XVWa-uy1N zJmwxxGMHprj#7!4yQe~nNZ9rirnM=}H0|5D^SQ4inrEDcU&3;KG? zc-j=OUz-N32SoW?;3_qzlL_u0nTOR{0tm_-FJ|M=#h#;Z6ztW0+=_e|w~(mlX3+kS zcJ0ykAU2S|30x6Qgfmm8HF{Q&pw4{8le0G~jJ}Z0UhG1)mF698G3BhleBr-RiL827 z$rxqBkK+b69NBejGnF0={URG{ggX&d6NVdGM^`e1<%2tJ!kxb>po9? zL6YLb%!`l0|AkspB3E; zs*fR_whqWZguoPYQ8-C{sO768+OVEbVhN#DphGRVVmzKaBM?!r$skRa3V9F2{?7x)j--KZ0IH{ literal 0 HcmV?d00001 From db3b23298b26921ab78bc1f3e11f88bd2d61f28c Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Mon, 28 Nov 2022 13:31:41 -0500 Subject: [PATCH 18/66] =?UTF-8?q?refactor:=20TTA-232-change-warning-messag?= =?UTF-8?q?e-when-entries-summary-are-not=E2=80=A6=20(#955)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: TTA-232-change-warning-message-when-entries-summary-are-not-found * refactor: change warning message * refactor: change message Co-authored-by: Nicole Garcia --- src/app/modules/time-clock/store/entry.effects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts index f9fc9d76..19b9611a 100644 --- a/src/app/modules/time-clock/store/entry.effects.ts +++ b/src/app/modules/time-clock/store/entry.effects.ts @@ -46,7 +46,7 @@ export class EntryEffects { this.entryService.summary().pipe( map((response) => { if (!response){ - this.toastrService.warning('Your summary information could not be loaded'); + this.toastrService.warning("It's a brand new month! You don't have any time entries yet."); } return new actions.LoadEntriesSummarySuccess(response); }), From d71cd4fa48a968b794fcb66b9e40f42360579490 Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Mon, 12 Dec 2022 10:12:45 -0500 Subject: [PATCH 19/66] fix: TTL-727-bug-fix-error-400-on-project-update-function (#960) * refactor: TTL-727-bug-as-a-software-developer-i-want-to-fix-error-400-on-project-creation * refactor: inactivated project message Co-authored-by: Nicole Garcia --- .../components/projects/components/store/project.effects.ts | 4 ++-- src/app/modules/shared/messages.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/modules/customer-management/components/projects/components/store/project.effects.ts b/src/app/modules/customer-management/components/projects/components/store/project.effects.ts index 5067b1b8..3ba0dcc4 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.effects.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.effects.ts @@ -1,4 +1,4 @@ -import { INFO_SAVED_SUCCESSFULLY, INFO_DELETE_SUCCESSFULLY } from '../../../../../shared/messages'; +import { INFO_SAVED_SUCCESSFULLY, PROJECT_DEACTIVATED_SUCCESSFULLY } from '../../../../../shared/messages'; import { Injectable } from '@angular/core'; import { of, Observable } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; @@ -108,7 +108,7 @@ export class ProjectEffects { mergeMap((projectId) => this.projectService.deleteProject(projectId).pipe( map(() => { - this.toastrService.success(INFO_DELETE_SUCCESSFULLY); + this.toastrService.success(PROJECT_DEACTIVATED_SUCCESSFULLY); return new actions.DeleteProjectSuccess(projectId); }), catchError((error) => { diff --git a/src/app/modules/shared/messages.ts b/src/app/modules/shared/messages.ts index 2721cd2a..47281399 100644 --- a/src/app/modules/shared/messages.ts +++ b/src/app/modules/shared/messages.ts @@ -1,3 +1,4 @@ export const INFO_SAVED_SUCCESSFULLY = 'The data has been saved successfully'; export const INFO_DELETE_SUCCESSFULLY = 'The data has been deleted successfully'; export const UNEXPECTED_ERROR = 'An unexpected error happened, please try again later'; +export const PROJECT_DEACTIVATED_SUCCESSFULLY = 'The project has been inactivated successfully'; From 4ef6ec2f794714672c119a8b903ca1803f7d2cb8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 12 Dec 2022 15:14:37 +0000 Subject: [PATCH 20/66] chore(release): 2.1.2 [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 346262d6..067a1018 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.75.40", + "version": "2.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0fad3f42..35a39487 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.75.40", + "version": "2.1.2", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From 156544a8cf8645901e10e1b1ebb9007c8f0a9ca4 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Mon, 12 Dec 2022 18:29:57 -0300 Subject: [PATCH 21/66] Tta 189 create an anchor for the edit a customer action (#953) * noworking commit * refactor: TTA-189 dropped unused function * refactor: TTA-189 dropped another unused function * refactor: TTA-189 erased test Test erased for case being already covered in separate existing test. * refactor: TTA-189 scrollToCustomerForm * refactor: TTA-189 scrollToCustomer --- .../customer-list.component.html | 19 +++++++++---------- .../customer-list.component.spec.ts | 6 +++--- .../customer-list/customer-list.component.ts | 12 +++++++++++- .../pages/customer.component.html | 13 +++++++++---- .../pages/customer.component.ts | 7 +++++++ 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html index 32f29025..f1bae3ec 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html @@ -14,25 +14,24 @@ Visibility - - + + {{ customer.id }} {{ customer.name }} - + @@ -62,5 +61,5 @@ [title]="'Edit Customer'" [body]="message" (closeModalEvent)="closeModal()" - > +> diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts index 9a3cd495..c51cdef9 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts @@ -84,7 +84,7 @@ describe('CustomerTableListComponent', () => { it('Onclick Edit, if there are changes, the modal must be presented ', () => { component.hasChange = true; - const expectMessage = 'Do you have changes in a client, do you want to discard them?'; + const expectMessage = 'You have changes in a client, do you want to discard them?'; component.editCustomer('1'); @@ -92,7 +92,7 @@ describe('CustomerTableListComponent', () => { expect(component.showModal).toBeTrue(); }); - it('onClick edit, if there are not have changes dispatch SetCustomerToEdit, enable customer form and hidden modal', () => { + it('onClick edit, if there are no unsaved changes dispatch SetCustomerToEdit, enable customer form and hide modal', () => { component.hasChange = false; spyOn(store, 'dispatch'); @@ -115,7 +115,7 @@ describe('CustomerTableListComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectTypeToEdit()); }); - it('when you click close modal, you should close the modal, discard the current changes and load a new client for edit', () => { + it('when you click close modal, modal should close, discard the current changes and load a new client to edit', () => { spyOn(component.changeValueShowCustomerForm, 'emit'); spyOn(store, 'dispatch'); diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts index dbb0d79a..438e2fed 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts @@ -19,6 +19,12 @@ import { ResetProjectToEdit, SetProjectToEdit } from '../../../projects/componen import { ResetProjectTypeToEdit, SetProjectTypeToEdit } from '../../../projects-type/store'; import { UnarchiveCustomer } from '../../../../store/customer-management.actions'; + +export function scrollToCustomerForm(): void { + const element = document.getElementById('customerForm'); + element.scrollIntoView(); +} + @Component({ selector: 'app-customer-list', templateUrl: './customer-list.component.html', @@ -117,7 +123,7 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { editCustomer(customerId: string) { this.idToEdit = customerId; if (this.hasChange) { - this.message = 'Do you have changes in a client, do you want to discard them?'; + this.message = 'You have changes in a client, do you want to discard them?'; this.showModal = true; } else { this.showCustomerForm = true; @@ -199,4 +205,8 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { this.store.dispatch(new UnarchiveCustomer(this.idToDelete, this.changeOppositeStatus(this.statusToEdit))); } + goToCustomerForm(){ + scrollToCustomerForm(); + } + } diff --git a/src/app/modules/customer-management/pages/customer.component.html b/src/app/modules/customer-management/pages/customer.component.html index 39c79b4e..bb435039 100644 --- a/src/app/modules/customer-management/pages/customer.component.html +++ b/src/app/modules/customer-management/pages/customer.component.html @@ -1,7 +1,7 @@
- +
-
-
- +
+
+
+ +
diff --git a/src/app/modules/customer-management/pages/customer.component.ts b/src/app/modules/customer-management/pages/customer.component.ts index 42ee3056..c9795ed8 100644 --- a/src/app/modules/customer-management/pages/customer.component.ts +++ b/src/app/modules/customer-management/pages/customer.component.ts @@ -2,6 +2,8 @@ import { Store } from '@ngrx/store'; import { Customer } from 'src/app/modules/shared/models'; import { SetCustomerToEdit } from 'src/app/modules/customer-management/store'; import { Component } from '@angular/core'; +import { scrollToCustomerForm } from '../components/customer-info/components/customer-list/customer-list.component'; + @Component({ selector: 'app-customer', @@ -28,4 +30,9 @@ export class CustomerComponent { getChangesInputs(event) { this.hasChangeComponent = event; } + + goToCustomerForm(){ + scrollToCustomerForm(); + } + } From af256b0705451710211a60f05c3e28e67dda8b21 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Thu, 15 Dec 2022 14:06:34 -0300 Subject: [PATCH 22/66] add abi as collaborator (#963) --- .../A1D0772E0FDAFC4FD268F6B4CA937C82C0E0550C.gpg | Bin 0 -> 725 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/default/0/A1D0772E0FDAFC4FD268F6B4CA937C82C0E0550C.gpg diff --git a/.git-crypt/keys/default/0/A1D0772E0FDAFC4FD268F6B4CA937C82C0E0550C.gpg b/.git-crypt/keys/default/0/A1D0772E0FDAFC4FD268F6B4CA937C82C0E0550C.gpg new file mode 100644 index 0000000000000000000000000000000000000000..61dcca7c588bf0faa3911e647a0f3e2240307537 GIT binary patch literal 725 zcmV;`0xJE50t^FCozbPFvO&@T5CD?OU>XR)&)VU`;mKb*izpdYet5#LPi_RBv!|U= zazq*_;fFs@l$uM`Z8^aa-^Ya$vIW=iUWqlxlZUk| z4Xm98FAstSu4aE~9VV&@60qtg?`KT@#k3zylGRUae1-`AHUdmHwwbWJilM>&;nrT_ zL#QllG+M34ekpN%4Uwb>u*PF-=l?jjJ{g!44!Ww8<`{_yB;mHxB5Qk%icF>zdkuV% zF{DW1zpH6Y5E&G+HN4~GCwID%uC)e{z2t{KgRe60-Ojo22w9!@*DtK=V`u^@*1xBA zJ#8mh5*7PEAI`3TV4-{nHRS;@m~Wu9#V}{ zu{E92N0$rvJ9>oZ>=ABa_0US0Pe;8&xl4lL;8}Dq;i**K2>tgvp{$IwuA~%_IT-S~ zG5x0BRflF~A1E9m#*G7{&#YiW3&IK<8v!8te4JZcf`bh<$I`T7|4t2ppo|RV&Sa>o zaU$}!5iFv1y(an%AQjenJTEhed}Xb*SgJKj#{KeaFF#KjJMg9$V>x=CJ4{}J+1^T} zpN1vMlVQBfsI`VUxu`Jb$5R`ALe7oHSEu)LO`P=QuQMg#;H*ed2<45yHmZqL%d0|5}+*$D_%ucfQvLlX!o#S(wHJ4pm z3X2mBA0b{81@sBJ)%c4By`PWoOPKL9zg;ai)Ynt6&bIIoQ9}TEA6PpV@Q%y?>~w%= z%AkC=04|!x>e!ru_v%-%e#PuWx83!509e^JF}rtvBtUHU$bHr|N=YD`gk5hbo#S+? zJi93YOsQncryuQRXk?hpU5ED8DP;6gqD@ Date: Fri, 16 Dec 2022 11:33:28 -0300 Subject: [PATCH 23/66] Add abi as collaborator (#965) added abi as collaborator --- .../32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg | Bin 0 -> 597 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/default/0/32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg diff --git a/.git-crypt/keys/default/0/32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg b/.git-crypt/keys/default/0/32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg new file mode 100644 index 0000000000000000000000000000000000000000..f67c04ca376d151c7233abdaf77ee5ed7ed06143 GIT binary patch literal 597 zcmV-b0;>Im0gMCt5Y`ncaUp~O3;#u^mxBwz4UXpcm7G;QRS5f~e~HNc(pL+1raA6= z&foqDUMvXucJ)+}MbSDY4?e6*C%{JzYFxi6vS%{PYi@M31C-JwjBYrUgFEBy&wP7J zDme@xe4tpKSshlifs}ObyM1up8l#0NXS%>;eAxM6Ekh@im!Rgct=}^PMu({z_4ATJ zRXqjpA8EMll)OGT;lUqpNI!?xJo$s!h*azuAEr^oIdmX1d8Q9Mbb@Kw61M3Kz0E}k zC)ULcGx`hF4EFhBXAhM$J(g{0iZ4CcsPjMgj?mM~>PJT?b9EnC7<)#T8#dVm zePVXISPTNjW@kN++QJF*kx#yuGhAEf?6uB3TeQ}Y8k~+s&<8h3OeI?*H4fMHyhMa# zd7@4DR+kTIAhfmmMJ($lmE^R)OPgn*iVUgV=Bh7uKUu!0|DKrf%P$& z8f^pAz}ySUcgUhC~9Fcr|~_2KRS^VkB`|&YW^y2*=*x7J0w{C8(CojE7t{9 jE~!C+?!**=)w*^HdV=(xK;I|seU~73Y*17KdnH-IJ75@) literal 0 HcmV?d00001 From 1d1644aa6375d9682679509f2c9f1cd96dc235d5 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Fri, 16 Dec 2022 12:43:09 -0300 Subject: [PATCH 24/66] refactor: TTL-720 increased build speed (#964) * refactor: TTL-720 increased chown speed * refactor: TTL-720 added newline at end of file --- Docker/Dockerfile.dev | 11 +++++------ Docker/Dockerfile.test | 11 ++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Docker/Dockerfile.dev b/Docker/Dockerfile.dev index 3dbb7041..31d0ab57 100644 --- a/Docker/Dockerfile.dev +++ b/Docker/Dockerfile.dev @@ -2,15 +2,14 @@ FROM node:14 ENV USERNAME timetracker ENV HOME /home/${USERNAME} -RUN useradd -ms /bin/bash ${USERNAME} +RUN useradd --create-home -ms /bin/bash ${USERNAME} WORKDIR ${HOME}/time-tracker-ui -COPY package.json package-lock.json ./ +COPY package*.json ./ +RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui RUN npm cache clean --force && npm install -COPY . . -RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui \ - && chmod -R 777 ${HOME}/time-tracker-ui +COPY --chown=${USERNAME}:${USERNAME} . . USER ${USERNAME} EXPOSE 4200 -CMD ${HOME}/time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck +CMD ${HOME}/time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck=false --poll 2000 diff --git a/Docker/Dockerfile.test b/Docker/Dockerfile.test index ae536a54..8c6b483f 100644 --- a/Docker/Dockerfile.test +++ b/Docker/Dockerfile.test @@ -31,14 +31,15 @@ RUN mkdir -p /opt/selenium \ && curl http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -o /opt/selenium/chromedriver_linux64.zip \ && cd /opt/selenium; unzip /opt/selenium/chromedriver_linux64.zip; rm -rf chromedriver_linux64.zip; ln -fs /opt/selenium/chromedriver /usr/local/bin/chromedriver; -RUN useradd -ms /bin/bash ${USERNAME} + +RUN useradd --create-home -ms /bin/bash ${USERNAME} WORKDIR ${HOME}/time-tracker-ui -COPY package.json package-lock.json ./ -RUN npm cache clean --force && npm install -COPY . . +COPY package*.json ./ RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui -RUN chmod -R 777 ${HOME}/time-tracker-ui +RUN npm cache clean --force && npm install +COPY --chown=${USERNAME}:${USERNAME} . . + USER ${USERNAME} EXPOSE 4200 From 98d7c4280391ff145b7ed32d2b0a74b6e93f945f Mon Sep 17 00:00:00 2001 From: mmaquina Date: Fri, 30 Dec 2022 16:16:04 -0300 Subject: [PATCH 25/66] Added jerson and fausto as stage and prod collaborators --- .../E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg | Bin 0 -> 603 bytes .../E596ED2AB82FBF820CC8EE869442AE57E34F8756.gpg | Bin 0 -> 603 bytes .../E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg | Bin 0 -> 606 bytes .../E596ED2AB82FBF820CC8EE869442AE57E34F8756.gpg | Bin 0 -> 606 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/PROD/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg create mode 100644 .git-crypt/keys/PROD/0/E596ED2AB82FBF820CC8EE869442AE57E34F8756.gpg create mode 100644 .git-crypt/keys/STAGE/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg create mode 100644 .git-crypt/keys/STAGE/0/E596ED2AB82FBF820CC8EE869442AE57E34F8756.gpg diff --git a/.git-crypt/keys/PROD/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg b/.git-crypt/keys/PROD/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg new file mode 100644 index 0000000000000000000000000000000000000000..d52ab0957dd68685382278f37955c0819adf0fb5 GIT binary patch literal 603 zcmV-h0;K(g0gMBfDd@=Dfl)I73;rjtohG=?5Kbnsh6MPXnEcxl08vv@kc_~EtG`#~ zw?2a6$6eras9<3`K(K!eZRFzz^?0FK5lYDEzGpX$Q0Pg5Bq|%ma5B%iT)B!9vy|g< znqY)#u(^5|QL&xfntM=1H;DY^ky5z_BFQ1`wVJbhH7DRpAP4sj#itKc`qwnpC7hd} z)keTC3yRPbSK)OCqn>+qLNZYgup$0HPdn%rdra*j`YPHTK~w3YQOiIQ5Tn|eKlBK} zDDaTM;aHlT(WoRA>{xm4s;{LTjYJsVV&`B1IRJxlrY0PwTbzcz|I$-=+Zb{DC;pV zUwIpg*4Q@2=9)pl8Jf7=&Kcb)tVCjilrzO`h|aOg(xL<^zQFrwBpNhz0!E7)!NyCQ z8E?PSE_hA9)kX$ex%NS=J!?Ua*zXWKmC2FWivSj)hFyX;G-namb{OlnXxh7HO$Zmn p5q#^hFLp>4*pJ6FQobBtEm}TIb=1=|?bFOJe3#Alck*0(lzXf|EZ_hD literal 0 HcmV?d00001 diff --git a/.git-crypt/keys/PROD/0/E596ED2AB82FBF820CC8EE869442AE57E34F8756.gpg b/.git-crypt/keys/PROD/0/E596ED2AB82FBF820CC8EE869442AE57E34F8756.gpg new file mode 100644 index 0000000000000000000000000000000000000000..5379e8a81d218f149685129cdbf778dc4439f3cb GIT binary patch literal 603 zcmV-h0;K(g0gMBIk~X=zsi5}H@%_;!n4Q6 z%#C$m=>9q@wBe*4$ff4MjhVHDFk-Iba;%)`-%UCf52w|+R1UH09C2+E_7^yu6GR~Z z8763#V=HVwT7S8*Mk24LUNJsZ*v%fh^7L*4If5*^&i(N;jK3@1@e`OcG3uEDQL&!h z(vCu05&|jVY+m(if7#Wyf$t$^FIF!n@EA@3GMQ=Ex>F&!hL5ttE2!k}2J$l(rj1bd z-kN?$Ox@q8^Xt}LfnAdbM}rv9jUxD89dLNUwY8M*t%+Z}?81EsD@K|w6@B8Z`|BErE!_Izf1R&4xC}AHO27?LmmGEFV<;9p`80s{tw9U+|NiwdP{fzpk;s@JQfO) zBsV|OxDk2@YAp{xIPs1fHTd6PVfQZzPmai;wb@y@b^ zVss1iJ|7pBRGb?L0GY|Arb6BP1l$ST*2v(on*PpgkF<&>(HIa=Zbpy}KOi?+j62ZR pWpcZWfg3Kn11@|0s$(`%c4Plb2UDBzs5w=d@oZe6enbz|!ZLjHCvX4& literal 0 HcmV?d00001 diff --git a/.git-crypt/keys/STAGE/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg b/.git-crypt/keys/STAGE/0/E53A45CD0CD193F8D668809BB994EBF9E04B9ADC.gpg new file mode 100644 index 0000000000000000000000000000000000000000..68c89bc64120a6961f9c6e035bfec81ea4290fa2 GIT binary patch literal 606 zcmV-k0-^nd0gMBfDd@=Dfl)I73;$}LvtyKNUnC7&r*Y-FAk#R9CsF&842&#~{kDh> zNHT%|H#nHXH}Cc_jQOa!aa`?GKES~G>u`>kA@w$%fjrE(uK@cQSLkK&4$+353dP7w z3hla152$hvTia+hu_j_I_MKSASx!?NmSC#siFXCcFcyBlA1}Hip@P3xjdIirs2U2Z z!vp$sZqq%p#$&2Cz+a+o2t6X|aQ? z81=vlaa*1#a9Ilkvb55`3;{=4)sRkA z^{#`Yn~wXsvQUCO+EjO-wEl^YU2uN+BxRz2Qry>I6@%peG)mGR1_14Xu{r3Zl}F$R z!2moz^CM^;1z!LrrX0UA5zdh7wmi65iUpSFo#6`I?nGWbE(L{Niul z0f&B%GCW6GxJeKVq~ni}kdo5&z#&z{6|0FbwT)G1ImEAX8Qb)DCXm$CHx@?5aijX2 zng1XBN5Y~nvMuNVGA_+86KZYug;JZqF5m%pG9ErEuB(~PN44$^L;gdWRXsQ5{&Cg6 zI(SKp{vt5Tty7$dEB*=7O(E*vbv8^3W$0Ikm;Xv-SV90e-9^koUtA-{f}3@$yTB2^ zE?4ZrIw?B%i6SKYBC0oG%2PijIA0h%> zeCMtV1Q+8HuN?Hf<^(S!7Em{iKKyL|7clEcoS9XC<0Ad0yxS+7{;4Ff)&J|SVR5tNE#^d-ld_zf) zeXL^-st|~H7N)$5eKDsKl8?D2Z_bX!xM8D9PhVKQ5nkruL&Z#TAc$O)I`q_zF|{m8 sD7gNhX6rND#(9&>>(8dPTGb2c{6|KMX+sa7;C Date: Wed, 11 Jan 2023 09:08:34 -0500 Subject: [PATCH 26/66] refactor: TTL-725 change the confirmation message of customer module (#967) * refactor: TTL-725 change the confirmation message of customer module * test: TTL-725 change the confirmation message of customer module test * test: TTL-725 change the confirmation message of customer module test --- .../components/customer-list/customer-list.component.spec.ts | 2 +- .../components/customer-list/customer-list.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts index c51cdef9..e022345b 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts @@ -84,7 +84,7 @@ describe('CustomerTableListComponent', () => { it('Onclick Edit, if there are changes, the modal must be presented ', () => { component.hasChange = true; - const expectMessage = 'You have changes in a client, do you want to discard them?'; + const expectMessage = 'You have unsaved changes, do you want to discard them?'; component.editCustomer('1'); diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts index 438e2fed..ab51d045 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts @@ -123,7 +123,7 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { editCustomer(customerId: string) { this.idToEdit = customerId; if (this.hasChange) { - this.message = 'You have changes in a client, do you want to discard them?'; + this.message = 'You have unsaved changes, do you want to discard them?'; this.showModal = true; } else { this.showCustomerForm = true; From 05d4d26d5130fa305435de49c80d8636296f1961 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Tue, 17 Jan 2023 16:36:31 -0300 Subject: [PATCH 27/66] added santiago key (#971) --- .../053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg | Bin 0 -> 597 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/default/0/053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg diff --git a/.git-crypt/keys/default/0/053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg b/.git-crypt/keys/default/0/053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg new file mode 100644 index 0000000000000000000000000000000000000000..ca829714775e245f0ba57acdae7e5566bd505236 GIT binary patch literal 597 zcmV-b0;>Im0gMAU01Y1H@i}h+3;?Bsc1ze+t)>-ys1F|zFtOMHG#sVP^l z43pPPhC`qE@2k1&OymTxN3}&VP!rVeJjJnn=fNz;9am`9nN(R>eGLM~9!ZZcP>4bu zp2M*)7%+x*aI*zg38@>G)ROs{QF``d@cSwf2Pdk-akM(R<}WxUzdGRhZs3Io<@)z< z^UzfdXxgSaH__(_6gw^@^L^39{ck?u4&PHp7@7jESE2ZVrzJxGwv0*4Q-HpJWrtyugNy)dPwkfu?X-mI~o?|?=lE#G*1=sdL3?}c3er= z2e=<1zzi8yEw&QXKJyh;@G~x)oXQwwVw$Ube)Y%e_o#s2xXgN@z)I4<0|B_B75hbR z^w2D7z>*q@jr}j29Vt(rv@#mZD#uZTrj5?~no`UR#TXY416!@s#VWpZf3@4BxCHh_ zA!euqTaykv9utO*lo(~XsgXR};L&UP;;(L?;SPF5Go|-Pg5Tdh1>G+9J>icI?tZVc zxUGIUrVKRgZ_}wivG_J%O!CRnk_v$^3e~<+t!cw3GttrB{Ec@NiXMgYIg~E&a$mHi j%k+eLwn>S+*&TQ)uZhpX*WyIuA)^4lKR7-IZ?IdS@#G^V literal 0 HcmV?d00001 From 487febc43e95a133ef26467c44bd327f03c2397f Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Mon, 23 Jan 2023 11:01:06 -0500 Subject: [PATCH 28/66] feat: TTL-815-test-calendar-view-feature-toggle (#972) Co-authored-by: Nicole Garcia --- .../pages/time-entries.component.html | 2 +- .../pages/time-entries.component.spec.ts | 70 ------------------- .../pages/time-entries.component.ts | 3 - 3 files changed, 1 insertion(+), 74 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 ce4b96f7..bb63b19e 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.html +++ b/src/app/modules/time-entries/pages/time-entries.component.html @@ -3,7 +3,7 @@ - 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 0533ccd1..854efe5c 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 @@ -510,31 +510,6 @@ describe('TimeEntriesComponent', () => { expect(cookieService.get).toHaveBeenCalledWith(sentParameter); }); - it('set false in isFeatureToggleCalendarActive when cookie does not exist', () => { - spyOn(cookieService, 'get'); - - component.ngOnInit(); - - expect(component.isFeatureToggleCalendarActive).toBeFalse(); - }); - - it('set true in isFeatureToggleCalendarActive when cookiesService.get() return true', () => { - const cookieResponseValue = 'true'; - spyOn(cookieService, 'get').and.returnValue(cookieResponseValue); - - component.ngOnInit(); - - expect(component.isFeatureToggleCalendarActive).toBeTrue(); - }); - - it('set false in isFeatureToggleCalendarActive when cookiesService.get() return false', () => { - const cookieResponseValue = 'false'; - spyOn(cookieService, 'get').and.returnValue(cookieResponseValue); - - component.ngOnInit(); - - expect(component.isFeatureToggleCalendarActive).toBeFalse(); - }); it('set true in displayGridView when its initial value is false and call onDisplayModeChange', () => { const expectedValue = true; @@ -644,51 +619,6 @@ describe('TimeEntriesComponent', () => { expect(component.calendarView).toBe(CalendarView.Month); }); - it('not view button onDisplayModeChange when isFeatureToggleCalendarActive is false', () => { - component.isFeatureToggleCalendarActive = false; - - fixture.detectChanges(); - - const HTMLTimeEntriesDebugElement: DebugElement = fixture.debugElement; - const HTMLTimeEntriesElement: HTMLElement = HTMLTimeEntriesDebugElement.nativeElement; - const HTMLTimeEntriesButton = HTMLTimeEntriesElement.querySelector('.btn.btn-primary.float-right'); - expect(HTMLTimeEntriesButton).toBeNull(); - }); - - it('view list button when displayGridView is true and isFeatureToggleCalendarActive is true', () => { - const expectedIconInsideButton = '.fa-list'; - const unexpectedIconInsideButton = '.fa-th'; - component.isFeatureToggleCalendarActive = true; - component.displayGridView = true; - - fixture.detectChanges(); - - const HTMLTimeEntriesDebugElement: DebugElement = fixture.debugElement; - const HTMLTimeEntriesElement: HTMLElement = HTMLTimeEntriesDebugElement.nativeElement; - const HTMLTimeEntriesButton = HTMLTimeEntriesElement.querySelector('.btn.btn-primary.float-right'); - const HTMLExpectedChildButton = HTMLTimeEntriesButton.querySelector(expectedIconInsideButton); - const HTMLUnexpectedChildButton = HTMLTimeEntriesButton.querySelector(unexpectedIconInsideButton); - expect(HTMLExpectedChildButton).not.toBeNull(); - expect(HTMLUnexpectedChildButton).toBeNull(); - }); - - it('view calendar button when displayGridView is false and isFeatureToggleCalendarActive is true', () => { - const expectedIconInsideButton = '.fa-th'; - const unexpectedIconInsideButton = '.fa-list'; - component.isFeatureToggleCalendarActive = true; - component.displayGridView = false; - - fixture.detectChanges(); - - const HTMLTimeEntriesDebugElement: DebugElement = fixture.debugElement; - const HTMLTimeEntriesElement: HTMLElement = HTMLTimeEntriesDebugElement.nativeElement; - const HTMLTimeEntriesButton = HTMLTimeEntriesElement.querySelector('.btn.btn-primary.float-right'); - const HTMLExpectedChildButton = HTMLTimeEntriesButton.querySelector(expectedIconInsideButton); - const HTMLUnexpectedChildButton = HTMLTimeEntriesButton.querySelector(unexpectedIconInsideButton); - expect(HTMLExpectedChildButton).not.toBeNull(); - expect(HTMLUnexpectedChildButton).toBeNull(); - }); - it('view calendarView when displayGridView is true', () => { const expectedView = '#gridView'; component.displayGridView = true; 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 1fbada9d..99a3845b 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.ts @@ -14,7 +14,6 @@ import { EntryState } from '../../time-clock/store/entry.reducer'; import { EntryActionTypes } from './../../time-clock/store/entry.actions'; import { getActiveTimeEntry, getTimeEntriesDataSource } from './../../time-clock/store/entry.selectors'; import { CookieService } from 'ngx-cookie-service'; -import { FeatureToggle } from './../../../../environments/enum'; import { CalendarView } from 'angular-calendar'; import { ParseDateTimeOffset } from '../../shared/formatters/parse-date-time-offset/parse-date-time-offset'; @@ -43,7 +42,6 @@ export class TimeEntriesComponent implements OnInit, OnDestroy, AfterViewInit { wasEditingExistingTimeEntry = false; canMarkEntryAsWIP = true; timeEntriesDataSource$: Observable>; - isFeatureToggleCalendarActive: boolean; displayGridView: boolean; selectedDate: moment.Moment; selectedYearAsText: string; @@ -68,7 +66,6 @@ export class TimeEntriesComponent implements OnInit, OnDestroy, AfterViewInit { ngOnInit(): void { this.loadActiveEntry(); - this.isFeatureToggleCalendarActive = (this.cookiesService.get(FeatureToggle.TIME_TRACKER_CALENDAR) === 'true'); this.entriesSubscription = this.actionsSubject$.pipe( filter((action: any) => ( action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS || From 99a9ecb1fe619fcb8cecb024f0920d50b34975c9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 23 Jan 2023 16:03:00 +0000 Subject: [PATCH 29/66] chore(release): 2.2.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 067a1018..1b463e0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "2.1.2", + "version": "2.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 35a39487..d342dbcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "2.1.2", + "version": "2.2.0", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From b95ae127c12f8993621ce11c4a32e9eaffef125a Mon Sep 17 00:00:00 2001 From: Andres Cabrera <85083117+andresacg30@users.noreply.github.com> Date: Mon, 6 Feb 2023 13:58:14 -0400 Subject: [PATCH 30/66] [TTL-746] Deactivate Legacy Pipelines (#976) * change not used workflows' triggers to manual * add deprecated word to deactivated pipelines --------- Co-authored-by: Andres Cabrera --- .github/workflows/CD-time-tracker-ui.yml | 8 ++------ .github/workflows/CI-mutation-tests.yml | 6 ++---- .github/workflows/CI-time-tracker-ui.yml | 8 ++------ 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/.github/workflows/CD-time-tracker-ui.yml b/.github/workflows/CD-time-tracker-ui.yml index f4c22fa2..1bbc5a01 100644 --- a/.github/workflows/CD-time-tracker-ui.yml +++ b/.github/workflows/CD-time-tracker-ui.yml @@ -1,13 +1,9 @@ # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy # More GitHub Actions for Azure: https://github.com/Azure/actions -name: CD process to deploy to App-Service service +name: (DEPRECATED) CD process to deploy to App-Service service -on: - # Trigger the workflow on pull request but only for the master branch - push: - branches: - - master +on: workflow_dispatch # deactivate workflow and run it manually only jobs: build-and-deploy: diff --git a/.github/workflows/CI-mutation-tests.yml b/.github/workflows/CI-mutation-tests.yml index c69a8834..036bd2b8 100644 --- a/.github/workflows/CI-mutation-tests.yml +++ b/.github/workflows/CI-mutation-tests.yml @@ -1,8 +1,6 @@ -name: Running mutation tests +name: (DEPRECATED) Running mutation tests -on: - schedule: - - cron: '0 9 * * 1' +on: workflow_dispatch # deactivate workflow and run it manually only jobs: configuring-stryker: diff --git a/.github/workflows/CI-time-tracker-ui.yml b/.github/workflows/CI-time-tracker-ui.yml index 6c971a73..00cd4e30 100644 --- a/.github/workflows/CI-time-tracker-ui.yml +++ b/.github/workflows/CI-time-tracker-ui.yml @@ -1,10 +1,6 @@ -name: CI process for time-tracker app +name: (DEPRECATED) CI process for time-tracker app -on: - pull_request: - types: [opened, edited, reopened, synchronize] - branches: - - master +on: workflow_dispatch # deactivate workflow and run it manually only jobs: # security-audit: From 14b1b7212fbcf03f60af4774f0561b128336ad2c Mon Sep 17 00:00:00 2001 From: mmaquina Date: Mon, 6 Feb 2023 15:02:59 -0300 Subject: [PATCH 31/66] Added new credentials for azure login (#973) * Added new credentials for azure login * update prod credentials * change workflow to avoid run duplication --- .github/workflows/time-tracker-ui-cd-prod.yml | 2 +- .github/workflows/time-tracker-ui-cd-stage.yml | 2 +- .github/workflows/time-tracker-ui-ci.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/time-tracker-ui-cd-prod.yml b/.github/workflows/time-tracker-ui-cd-prod.yml index 26c54d0c..cf531e0a 100644 --- a/.github/workflows/time-tracker-ui-cd-prod.yml +++ b/.github/workflows/time-tracker-ui-cd-prod.yml @@ -28,7 +28,7 @@ jobs: - name: Login to azure uses: Azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + creds: '{"clientId":"${{ secrets.TF_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.TF_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.TF_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TF_ARM_TENANT_ID }}"}' - name: Unlock PROD secrets uses: sliteteam/github-action-git-crypt-unlock@1.2.0 diff --git a/.github/workflows/time-tracker-ui-cd-stage.yml b/.github/workflows/time-tracker-ui-cd-stage.yml index a57cfec0..276907f3 100644 --- a/.github/workflows/time-tracker-ui-cd-stage.yml +++ b/.github/workflows/time-tracker-ui-cd-stage.yml @@ -28,7 +28,7 @@ jobs: - name: Login to azure uses: Azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + creds: '{"clientId":"${{ secrets.TF_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.TF_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.TF_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TF_ARM_TENANT_ID }}"}' - name: Unlock STAGE secrets uses: sliteteam/github-action-git-crypt-unlock@1.2.0 diff --git a/.github/workflows/time-tracker-ui-ci.yml b/.github/workflows/time-tracker-ui-ci.yml index d7992606..74d867c4 100644 --- a/.github/workflows/time-tracker-ui-ci.yml +++ b/.github/workflows/time-tracker-ui-ci.yml @@ -3,11 +3,11 @@ name: time-tracker-ui-ci on: push: branches: - - "**" + - "master" pull_request: branches: - - "**" + - "master" jobs: ci: From 0b1482caa17961c12f1bad620416dbd149e55cf2 Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Wed, 15 Feb 2023 10:12:53 -0500 Subject: [PATCH 32/66] fix: TTL-795-fix-customers-table-remove-duplicated-subscription (#977) Co-authored-by: Nicole Garcia --- .../components/customer-list/customer-list.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html index f1bae3ec..33e9226b 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html @@ -15,7 +15,7 @@ - + {{ customer.id }} {{ customer.name }} From efded51cff199cceabc7ee666d0ebe20ff277658 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 15 Feb 2023 15:15:34 +0000 Subject: [PATCH 33/66] chore(release): 2.2.1 [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 1b463e0e..ce9e01bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "2.2.0", + "version": "2.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d342dbcd..920e6eec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "2.2.0", + "version": "2.2.1", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From d956d9e4539b95de3443278e644d13d53d103442 Mon Sep 17 00:00:00 2001 From: Wolfgang Welcomez Date: Wed, 1 Mar 2023 13:21:30 -0500 Subject: [PATCH 34/66] Feat: Devops 202 Migrate remaining tf state time tracker ui (#978) --- .github/workflows/time-tracker-ui-cd-prod.yml | 4 ++- .../workflows/time-tracker-ui-cd-stage.yml | 2 ++ .github/workflows/time-tracker-ui-ci.yml | 3 +++ infrastructure/main.tf | 27 +++++++++++-------- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/time-tracker-ui-cd-prod.yml b/.github/workflows/time-tracker-ui-cd-prod.yml index cf531e0a..f2ae4674 100644 --- a/.github/workflows/time-tracker-ui-cd-prod.yml +++ b/.github/workflows/time-tracker-ui-cd-prod.yml @@ -15,7 +15,9 @@ jobs: ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} - + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/time-tracker-ui-cd-stage.yml b/.github/workflows/time-tracker-ui-cd-stage.yml index 276907f3..d71f6592 100644 --- a/.github/workflows/time-tracker-ui-cd-stage.yml +++ b/.github/workflows/time-tracker-ui-cd-stage.yml @@ -15,6 +15,8 @@ jobs: ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} steps: - name: Checkout diff --git a/.github/workflows/time-tracker-ui-ci.yml b/.github/workflows/time-tracker-ui-ci.yml index 74d867c4..b5cb7cd6 100644 --- a/.github/workflows/time-tracker-ui-ci.yml +++ b/.github/workflows/time-tracker-ui-ci.yml @@ -18,6 +18,9 @@ jobs: ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + strategy: max-parallel: 5 steps: diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 4907d9b7..caa878ec 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -5,15 +5,22 @@ terraform { source = "hashicorp/azurerm" version = "~> 2.90" } + aws = { + source = "hashicorp/aws" + version = "~> 4.9.0" + } } - backend "azurerm" { - resource_group_name = "ioet-infra-tf-state" - storage_account_name = "timetrackertfstate" - container_name = "time-tracker-tf-state" - key = "time-tracker-ui.tfstate" + backend "s3" { + bucket = "time-tracker-service" + key = "ioet-time-tracker-ui/terraform.tfstate" + region = "us-east-1" + encrypt = true } +} +provider "aws" { + region = "us-east-1" } provider "azurerm" { @@ -22,13 +29,11 @@ provider "azurerm" { } data "terraform_remote_state" "service" { - backend = "azurerm" - workspace = terraform.workspace + backend = "s3" config = { - resource_group_name = "ioet-infra-tf-state" - storage_account_name = "timetrackertfstate" - container_name = "time-tracker-tf-state" - key = "this.tfstate" + bucket = "time-tracker-service" + key = "env://${local.environment}/time-tracker-service/terraform.tfstate" + region = "us-east-1" } } From a35a658291b3eb7ce45effeb78e221a121c72460 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Thu, 6 Apr 2023 15:49:34 -0300 Subject: [PATCH 35/66] Add Nicole Garcia as a collaborator for prod and stage envs (#981) --- .../F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg | Bin 0 -> 603 bytes .../F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg | Bin 0 -> 606 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/PROD/0/F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg create mode 100644 .git-crypt/keys/STAGE/0/F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg diff --git a/.git-crypt/keys/PROD/0/F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg b/.git-crypt/keys/PROD/0/F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg new file mode 100644 index 0000000000000000000000000000000000000000..1d49695534f46e30b6f3f34399a0d5ce1df6ff72 GIT binary patch literal 603 zcmV-h0;K(g0gMCUnJSaY$OW1K3;>$=>dAPhgAUPs-c4%hM>SuD>^aI5?3s%Cs8@O^ zaAsNL@(%(5BDi#1(~`!1q8cQ2;{pVW^`xd7*F{W4c#=kT*Y2MW!ULJ@Rn#P%#klg) zA&No=IWqBDzhP`16>=)B-QXvEluWDDGOV~`tRx#$nXNo*e>HbvPwfzp6q_sB@Aiy3 zk!}Fb;Z^kNimc{GZO0pSAmFa%2cTtVu*P25m8=iMXNnU8pEWorX;=;T(-s^Ssmv+- z30n-LAl+s1lSj8Ar9TpBBLDfr7^7gGA(u8&any$vu5es7+F2>`MK%h7dnCnr?qy>L z{&joKXe`6P`=@Y4fjvO?3myjWZgJTuV}pSw!m9M28Qby5i_JuMD(7-^rHji3RoveX zO#NzFk}KTFaq?N-qbh2cA8~WZ% zqlVnYdUv#zWMCch6lBQy4ZQ8##?(Q@MCTIe=n2$&vtgx2+*QC;_LDVd+3&60hD?fjAI7 p_x5*hG+kRr&prjM9*g^L%O1CJ@1sZj&Jj6Ah7IVl5h?6*J9--AD5(Gd literal 0 HcmV?d00001 diff --git a/.git-crypt/keys/STAGE/0/F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg b/.git-crypt/keys/STAGE/0/F0EBF422F70334C5FE8CAF487A1FE9C45A4A5B22.gpg new file mode 100644 index 0000000000000000000000000000000000000000..1a2cc94c78340ee62e37dc413a8db255e5b19cd7 GIT binary patch literal 606 zcmV-k0-^nd0gMCUnJSaY$OW1K3;F{(zI|8VR&UwGp|LVQl%9}BN%UyRtNuLrH7`w6O`RL@-07M`jl+Y+3)W4inlEr29jUfyH=MPmAB# z;@XU(v&p!E8&qj_yu0o>m4?grvRZ4_&@TV^;v?w^%=C3;W@WN9phw%L%B8I9^-zR- z>P+#&NaYKe8Y+bGwzIzIEcrgjz%5<^x8MSqyW_O{Qb~XX2qKJ92s?vWQGQWwK8SSB z&j{S)S|-B@wo2@zl^}JN2gYw_*KMBe@6EzJLfz+qrUtt#pyV0D6T$s2m zn#SUrhSM7iiNRna0}{VkN()^ISF&ZGgD!K~*oqJu-rM(Wm#oDML0~x^J^bn(0B^!T z$mgT4zd0HXnR6~-iI}s|Qoj%F0s|MM8qntjai&YBjDhDH;sZT|TJ>p-$-=r-7Bw0a#*%V>Kpai)^vNkH*zbMB0m33sbqovfsjsRKeId zXf=JI80$^NL-JJ)z!zXA1fT1tT3oWi_V0yZub*X^W2KPX|0IDv94{N=t|ZvxvYH7l zugLOt+MIPH^$l37+PA}x26$*3cQ{6GjczuxlFY)d+qe}^jk8BNHfgX@hBHzDIVE=2 sO4}!px5u0_eKBSn_nT#Gv)9smFU literal 0 HcmV?d00001 From ed217e5cdfb56b0b338acd8a0d89348661cbb2b5 Mon Sep 17 00:00:00 2001 From: nicolsss <56645701+nicolsss@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:58:58 -0500 Subject: [PATCH 36/66] Fast api implementation (#979) * refactor: change endpoints route * refactor: backend port * fix make publish * fix aws login * change backend link * fix findEntriesByProjectId endpoint url * fix url createEntry * triggers by: tag->prod, publish release->stage * disable triggers to azure workflow * fix dockerfiles for aws --------- Co-authored-by: Nicole Garcia Co-authored-by: mmaquina --- .gitattributes | 4 +- .github/workflows/aws-ui-cd-prod.yml | 65 +++++++++++++++++ .github/workflows/aws-ui-cd-stage.yml | 67 ++++++++++++++++++ .github/workflows/time-tracker-ui-cd-prod.yml | 4 +- .../workflows/time-tracker-ui-cd-stage.yml | 4 +- .prod.aws.env | Bin 0 -> 566 bytes .prod.env | Bin 633 -> 630 bytes .stage.aws.env | Bin 0 -> 578 bytes .stage.env | Bin 639 -> 634 bytes Makefile | 14 ++-- README.md | 2 +- infrastructure/aws_ec2.sh | 6 ++ .../components/services/project.service.ts | 2 +- .../interceptors/inject.token.interceptor.ts | 2 +- .../time-clock/services/entry.service.ts | 14 ++-- 15 files changed, 160 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/aws-ui-cd-prod.yml create mode 100644 .github/workflows/aws-ui-cd-stage.yml create mode 100644 .prod.aws.env create mode 100644 .stage.aws.env create mode 100644 infrastructure/aws_ec2.sh diff --git a/.gitattributes b/.gitattributes index 6b7b4ea1..4b6561ba 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ .dev.env filter=git-crypt diff=git-crypt .prod.env filter=git-crypt-PROD diff=git-crypt-PROD -.stage.env filter=git-crypt-STAGE diff=git-crypt-STAGE \ No newline at end of file +.stage.env filter=git-crypt-STAGE diff=git-crypt-STAGE +.stage.aws.env filter=git-crypt-STAGE diff=git-crypt-STAGE +.prod.aws.env filter=git-crypt-PROD diff=git-crypt-PROD diff --git a/.github/workflows/aws-ui-cd-prod.yml b/.github/workflows/aws-ui-cd-prod.yml new file mode 100644 index 00000000..b254dd05 --- /dev/null +++ b/.github/workflows/aws-ui-cd-prod.yml @@ -0,0 +1,65 @@ +name: time-tracker-ui-cd-prod + +on: + push: + branches: + - 'fast-api*' + +jobs: + cd: + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + + - name: Unlock PROD secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_PROD }} + + - name: Build the docker image + run: |- + docker build \ + --target production -t timetracker_ui -f Dockerfile_prod \ + . + + - name: Publish docker image to prod aws container registry + run: | + make login publish_prod image_tag=$RELEASE_VERSION + + - name: SCP files via ssh key + uses: appleboy/scp-action@master + env: + USERNAME: ${{ secrets.AWS_EC2_USER }} + HOST: ${{ secrets.PROD_UI_URL }} + KEY: ${{ secrets.PROD_AWS_PRIVATE_KEY }} + with: + source: './infrastructure/aws_ec2.sh' + target: '.' + + - name: SCP files via ssh key - .prod.aws.env + uses: appleboy/scp-action@master + env: + USERNAME: ${{ secrets.AWS_EC2_USER }} + HOST: ${{ secrets.PROD_UI_URL }} + KEY: ${{ secrets.PROD_AWS_PRIVATE_KEY }} + with: + source: '.prod.aws.env' + target: '.' + + - name: Deploy + run: | + TEMP=$(mktemp) + echo "${{ secrets.PROD_AWS_PRIVATE_KEY }}" > $TEMP + chmod 400 $TEMP + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }} "chmod +x ./infrastructure/aws_ec2.sh" + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }} "./infrastructure/aws_ec2.sh $RELEASE_VERSION" prod diff --git a/.github/workflows/aws-ui-cd-stage.yml b/.github/workflows/aws-ui-cd-stage.yml new file mode 100644 index 00000000..bd7c146f --- /dev/null +++ b/.github/workflows/aws-ui-cd-stage.yml @@ -0,0 +1,67 @@ +name: time-tracker-ui-cd-stage + +on: + release: + types: + - published + +jobs: + cd: + runs-on: ubuntu-latest + env: + TF_WORKSPACE: stage + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + + + - name: Unlock STAGE secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_STAGE }} + + - name: Build the docker image + run: |- + docker build \ + --target production -t timetracker_ui -f Dockerfile_stage \ + . + + - name: Publish docker image to stage AWS container registry + run: | + make login publish image_tag=$RELEASE_VERSION + + - name: SCP files via ssh key - script + uses: appleboy/scp-action@master + env: + USERNAME: ${{ secrets.AWS_EC2_USER }} + HOST: ${{ secrets.STAGE_UI_URL }} + KEY: ${{ secrets.STAGE_AWS_PRIVATE_KEY }} + with: + source: './infrastructure/aws_ec2.sh' + target: '.' + + - name: SCP files via ssh key - .stage.aws.env + uses: appleboy/scp-action@master + env: + USERNAME: ${{ secrets.AWS_EC2_USER }} + HOST: ${{ secrets.STAGE_UI_URL }} + KEY: ${{ secrets.STAGE_AWS_PRIVATE_KEY }} + with: + source: '.stage.aws.env' + target: '.' + + - name: Deploy + run: | + TEMP=$(mktemp) + echo "${{ secrets.STAGE_AWS_PRIVATE_KEY }}" > $TEMP + chmod 400 $TEMP + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }} "chmod +x ./infrastructure/aws_ec2.sh" + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }} "./infrastructure/aws_ec2.sh $RELEASE_VERSION" stage diff --git a/.github/workflows/time-tracker-ui-cd-prod.yml b/.github/workflows/time-tracker-ui-cd-prod.yml index f2ae4674..c34b3587 100644 --- a/.github/workflows/time-tracker-ui-cd-prod.yml +++ b/.github/workflows/time-tracker-ui-cd-prod.yml @@ -1,9 +1,7 @@ name: time-tracker-ui-cd-prod on: - release: - types: - - published + workflow_dispatch # deactivate workflow and run it manually only jobs: cd: diff --git a/.github/workflows/time-tracker-ui-cd-stage.yml b/.github/workflows/time-tracker-ui-cd-stage.yml index d71f6592..0e287b42 100644 --- a/.github/workflows/time-tracker-ui-cd-stage.yml +++ b/.github/workflows/time-tracker-ui-cd-stage.yml @@ -1,9 +1,7 @@ name: time-tracker-ui-cd-stage on: - push: - tags: - - 'v*.*.*' + workflow_dispatch # deactivate workflow and run it manually only jobs: cd: diff --git a/.prod.aws.env b/.prod.aws.env new file mode 100644 index 0000000000000000000000000000000000000000..9fae68b07c5da5909a9ebb152cb98f2a6043eccf GIT binary patch literal 566 zcmV-60?GXVM@dveQdv+`0BVG;VJst_LRoDlZRxL2_Z~3)Ab<$LV}u5KtcKu!ek6&w zlTC&A!g?A3qy%m89GnDGs|MiWq#^blVP3jkCP1L<gRJ9Z9(cmsgax7!dY@tt-I>xMPLJUs9kY z?m<%wFHdVwCIj+s_476(C`55(0c9seG6SU}wRP3gCa+iY@NM@f%z-a}a&+~_+GlV9 z3SGZTX1fYxOr|7)boeGo^tbVmrM6#;soz)J3WtPV(Fg$af@C9Bj+wBAj~-BRTC__L zHob6-hiBkn4R_g?vbNG>D`b|8Z_x*dDI~gQwb`i!YdgZ}8BTvUd<4|KC!BaME@rE8 z-1`~U^dK_?7&>BnIz)djR|h;L|FTgQRe~b#PEj85yw!?;h*<{8wU$!)SKB6`%2A?Afsj^5L1tS*hs0kBpo-|>FMh~ zPOQx?q{{gr0rGqx(zTV7-uI-KlI{+%^n@@mWVQ|#>r(Z}zPWjP?%t#-~cHoCvu=>&sIn*Y=b2ze^3PBI_NKBfImSck4_V+SwcIf5Y~2W=(qF@ ze*LF5!siQ903*?C$L&^S7Oo;}ei)6}a~=Bau)?3KU;gzYObGr;KtgOQ@0*!3nD{^| z{GRMv7MwBz5My}qD^8T(rtl%+qT7oAQGvmtm88CQ-p0C1rG+K%q4gh_WxVJoxwa`? zSQhd|Ko3|u)eE2P)CI%TG$YTMlxXbuIP78zk}to>0=#_)b44l@g)|DGWhYGr+O?h|dCsK$@ytdz zP7lf< zizb^5CifcxcvPLFdE3SIMmSEoK_f@`BK+h*2n1FDu%#1#?WT#91g^|c4-OA-DMw1_ zRsHPma$t2N_*kbIh>sJi%WPMaxxt-ttdZ6h)Vo+QPkWHAsEjlPI)FvZEUa1G(Ce9^ z#!YcS2`LF?TfQ`D7vOi$nROJ8FZL*lJxhabw9(B7$8{kphT_>*n5 z&2Sz|Z2F)7SlZ%3GJN{6lyIVS`GkV2^ImO4f8E%!m*;vYqzXtX6#A)()QB~0Nha?? zF%5K(dy^YShuWkcWBVRUs_IWGw4H&i%OoSiRneR`$88Sd2{7L;Ck8uxdKF|E^MUWc zY)018!uTD+H&11}hi~iA#wU^gyYyR$k0g^!u_oDt$6)rAr0~#h^D)GasA&aUGagkB zBKfxz*TF#;o%Y6UEwQ20Nle`SV_c-97*R%#z0`Voe7Y#S#uLr!pPCHAw!H;X^fA9r zRPKM);~bLh>*4mv(=&)?5!P+fwA?pnY4WXG@kg}|&tSOy809}Cw4E+|see+mszZ-k zL2_0d`IyFt688s8946t-R#yJRS#{%x#pRyRT=)ueT&vnj2b>QWmteO&SCmQIe@6Zu zx|OSU0B_9(`=!DTkbI0eGVNNrnvzqWQFAcuA&sT^@(m z11pvZA=JOGl)qqyo6LQEjs$s^ z-#?Gif+pn`Ojw#ik!MVYW;G)o>!4Oc`>yeF-91k^BHxcGutu1IMFu$1--WUT83%d$ z%X;!u`VhcT7vi;!bE^<_R$L+jk{C#zjgl3?(W?c_2rE2I(Kd4mgR>mvhNMPZlwVHy TbC;Pvpf4sOe^RMqjdJyfD_TIc diff --git a/.stage.aws.env b/.stage.aws.env new file mode 100644 index 0000000000000000000000000000000000000000..20e6ec6de28028db5be60de6937087cbe5c2f4a4 GIT binary patch literal 578 zcmV-I0=@kJM@dveQdv+`0AVKo&P*fDk1rQiiWO=Ak_2faQycGlT|90q^Ao*shpyL; zNt|P{CO(Lv1w|CGjFvjYR_&KsQE93?2Y^kOTgA>) zNiYblOTL_9fA4rgH}p3`l$L#&%St~RT*i4NyI4F;PHT@fulpf$^IBbdiF)jiA0Q*5 z4zO=_8X%6+d+D>Y8Pukgfs3r|ollzd4U63fcZL}*9Uj0S0sHl7zxt!W>1{$^(>)I$ zjjsxlx2$8=Mqg1y-~$z03KeBA>&%G%PvTJ733kgTL5tFft4v1uRTlghNJSr}srpuV z#w1a;LQXDd&mOG0qHgz&HcM;!BTeiywU(5;d_ z##B(40CL#yqs|dlO5^dKd{v6aNvhz?BV|_CFLc3+7bkY`%{R zWV~WH`)25}A$3yS8V^rwNJ8BYCA&OZj=KUFRo~`uB&O^#@c6+w+y<7$xf65HWCh7i QE?RH+tL&G>;}|+HujsH3Gynhq literal 0 HcmV?d00001 diff --git a/.stage.env b/.stage.env index 7e97985779683edd93f1f6db371dc95ee7477102..de69d65a04acae9222e63301792813626f933bd8 100644 GIT binary patch literal 634 zcmV-=0)_nmM@dveQdv+`0C%fFO6$>R59RTQ0Di>Gqb*+dtZR}#9wDlR%>Ykbf^aOjAkzN+4E6GLsYd zSkBC#cgW~TDpfYmV)24ZcJ14wUHcP9@mk?scwO{YMgTx>FenHEA68vUK&6D(tq9~q z;>}|}LJs!KyA4x02b>&$>OUCaKX5U*B=&8~tf1WI!Rm_B#aI~qthU~It3(e1QkFbxbj+Bm;us+JdQa+Ot$mD*b!!|(yU)A zVfe$O2%%l2DCS#NKOF+EaKtYKo|vpUh>GbykB_DFyZ?<_sT-BVy_7LRQT>Q&G~u2K z!gF14auzY+1jzOg=+S->1jY9|v||Hp;|mS|5)G)jsG{h=TEVU{K$~{n0@;m|33|czalj%^wzZn4 URURSV>$8w=xLeSS8(qH;--K#5umAu6 literal 639 zcmV-_0)YJhM@dveQdv+`0AS#hnTtw&YS9a`U7zlaLM$)a-nh4(wgba!!iLp8pOxbQ&{d!Tp*(A<`XP7!1 zm|qML1##Nh3tA{blahW+2>Tpm+x|zGVmyyS;+9I5F8{QT7V3o*Vd>+!DfEFG~Y2#szf>IBIzQ z^iq-=y=%@vTn11-#?~I>C$Q$>mdImE&WgD1n?XeuY5k4}SAVYo0IfIF7sw`rW$hE| zeXmoUXkwlrM3;B99Y_HSktrBrz|nsf#F>m3XVgzTl?B{#DXC#ntt%3txh`a3*d%yl zR?HBvIYY3?v|i(+{b4uHp!AeSMRVECvz1Uk|I37iR8V(yo>s^1N8q(F!}|XBT3(;M zN@i1~@ugnoI@{tfU*+>Qh{dI^u4Xq1tik#bZHOQha!a0 zug8vm>yGse%frK#bfZPTgv2sYb-yyA#!k%};PR)L_p1uaQBkN;OF9jH5x?t-db9SL Zz;M+b3%On@XFOGRrX<`o0U}8M image_tag= - docker tag timetracker_ui:latest $(acr).azurecr.io/timetracker_ui:$(image_tag) - docker push $(acr).azurecr.io/timetracker_ui:$(image_tag) +publish: require-image_tag-arg ## Upload a docker image to the stage AWS container registry image_tag= + docker tag timetracker_ui:latest 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/stage-ui:$(image_tag) + docker push 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/stage-ui:$(image_tag) .PHONY: build_prod build_prod: ## Create docker image with dependencies needed for production -- to test locally only @@ -80,13 +80,13 @@ remove_prod: ## Delete container timetracker_ui_prod. docker rm timetracker_ui_prod .PHONY: publish_prod -publish_prod: ## Upload a docker image to the prod azure container registry acr= image_tag= - docker tag timetracker_ui_prod:latest $(acr).azurecr.io/timetracker_ui:$(image_tag) - docker push $(acr).azurecr.io/timetracker_ui:$(image_tag) +publish_prod: require-image_tag-arg ## Upload a docker image to the prod AWS container registry image_tag= + docker tag timetracker_ui:latest 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/prod-ui:$(image_tag) + docker push 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/prod-ui:$(image_tag) .PHONY: login login: ## Login in respository of docker images - az acr login --name $(acr) + aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 568748651446.dkr.ecr.us-east-1.amazonaws.com .PHONY: release release: require-VERSION-arg require-COMMENT-arg ## Creates an pushes a new tag. diff --git a/README.md b/README.md index 94e90975..71e6e31d 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ Stryker is also executed on GitHub actions with the following cron expresion: Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -## Deploy the app on Azure +## Deploy the app on Azure (deprecated) The app deployment is automatically executed after each pull request is merged in master. That's wht it is necessary that each pull request meets at least 80% of test coverage. diff --git a/infrastructure/aws_ec2.sh b/infrastructure/aws_ec2.sh new file mode 100644 index 00000000..2b9961ed --- /dev/null +++ b/infrastructure/aws_ec2.sh @@ -0,0 +1,6 @@ +#!/usr/bin/sh +echo "Deploying $1..." +docker ps -aq | xargs docker stop| xargs docker rm --force --volumes +docker system prune -af +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 568748651446.dkr.ecr.us-east-1.amazonaws.com +docker run -d --name timetracker_ui --env-file .$2.aws.env -p 80:80 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/$2-ui:$1 diff --git a/src/app/modules/customer-management/components/projects/components/services/project.service.ts b/src/app/modules/customer-management/components/projects/components/services/project.service.ts index c5b431b1..9ebdc6ea 100644 --- a/src/app/modules/customer-management/components/projects/components/services/project.service.ts +++ b/src/app/modules/customer-management/components/projects/components/services/project.service.ts @@ -25,7 +25,7 @@ export class ProjectService { } getRecentProjects(): Observable { - return this.http.get(`${this.url}/recent`); + return this.http.get(`${this.url}/recent/`); } createProject(projectData): Observable { diff --git a/src/app/modules/shared/interceptors/inject.token.interceptor.ts b/src/app/modules/shared/interceptors/inject.token.interceptor.ts index 58752c9a..cad849a0 100644 --- a/src/app/modules/shared/interceptors/inject.token.interceptor.ts +++ b/src/app/modules/shared/interceptors/inject.token.interceptor.ts @@ -26,7 +26,7 @@ export class InjectTokenInterceptor implements HttpInterceptor { const token = this.isProduction ? this.azureAdB2CService.getBearerToken() : this.loginService.getBearerToken(); const requestWithHeaders = request.clone( { - headers: request.headers.set('Authorization', + headers: request.headers.set('token', 'Bearer ' + token) }); return next.handle(requestWithHeaders) diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index e8b9a1b6..939f94c7 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -23,16 +23,16 @@ export class EntryService { urlInProductionLegacy = environment.production === EnvironmentType.TT_PROD_LEGACY; loadActiveEntry(): Observable { - return this.http.get(`${this.baseUrl}/running`); + return this.http.get(`${this.baseUrl}/running/`); } loadEntries(date): Observable { const timezoneOffset = new Date().getTimezoneOffset(); - return this.http.get(`${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); + return this.http.get(`${this.baseUrl}/?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); } createEntry(entryData): Observable { - return this.http.post(this.baseUrl, entryData); + return this.http.post(`${this.baseUrl}/`, entryData); } updateEntry(entryData): Observable { @@ -47,7 +47,7 @@ export class EntryService { stopEntryRunning(idEntry: string): Observable { return (this.urlInProductionLegacy ? - this.http.post(`${this.baseUrl}/${idEntry}/stop`, null) : this.http.put(`${this.baseUrl}/stop`, null) ); + this.http.post(`${this.baseUrl}/${idEntry}/stop/`, null) : this.http.put(`${this.baseUrl}/stop/`, null) ); } restartEntry(idEntry: string): Observable { @@ -57,20 +57,20 @@ export class EntryService { summary(): Observable { const timeOffset = new Date().getTimezoneOffset(); - const summaryUrl = `${this.baseUrl}/summary?time_offset=${timeOffset}`; + const summaryUrl = `${this.baseUrl}/summary/?time_offset=${timeOffset}`; return this.http.get(summaryUrl); } findEntriesByProjectId(projectId: string): Observable { const startDate = this.getDateLastMonth(); const endDate = this.getCurrentDate(); - const findEntriesByProjectURL = `${this.baseUrl}?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`; + const findEntriesByProjectURL = `${this.baseUrl}/?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`; return this.http.get(findEntriesByProjectURL); } loadEntriesByTimeRange(range: TimeEntriesTimeRange, userId: string): Observable { const MAX_NUMBER_OF_ENTRIES_FOR_REPORTS = 9999; - const loadEntriesByTimeRangeURL = this.urlInProductionLegacy ? this.baseUrl : this.baseUrl + '/report'; + const loadEntriesByTimeRangeURL = this.urlInProductionLegacy ? this.baseUrl : this.baseUrl + '/report/'; return this.http.get(loadEntriesByTimeRangeURL, { params: { From fde4209c7e49b3dcffe65507aa29850fe70f25f1 Mon Sep 17 00:00:00 2001 From: Abigail Cabascango <51092317+abigailscl@users.noreply.github.com> Date: Thu, 13 Apr 2023 09:01:23 -0500 Subject: [PATCH 37/66] feat: add PR template (#970) --- .github/pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..c08c8675 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## Description + +Add a description of the: feature, bug fix,code change that improves performance, that affect the build system or external dependencies, on documentation, refactor or test. + +## Files changed + +- Add a list of files that have been changed + +## Task board + +- [Ticket name](Ticket url) + +## Acceptance criteria + +Add the acceptance criteria for this task. \ No newline at end of file From 542fdc432f4be66c93f50d69d44f7ec88ec7d00e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 13 Apr 2023 14:03:11 +0000 Subject: [PATCH 38/66] chore(release): 2.3.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 ce9e01bd..e5ee23f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 920e6eec..85b09b82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "2.2.1", + "version": "2.3.0", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From 9888ebbd1bd622b9436a72beea0733d63eb6402f Mon Sep 17 00:00:00 2001 From: mmaquina Date: Thu, 13 Apr 2023 16:43:59 -0300 Subject: [PATCH 39/66] fix: tests (#982) * fix: tests --- .../components/services/project.service.spec.ts | 2 +- .../components/store/project.effects.spec.ts | 4 ++-- .../inject.token.interceptor.spec.ts | 15 --------------- .../time-clock/services/entry.service.spec.ts | 16 ++++++++-------- .../pages/time-entries.component.spec.ts | 9 --------- 5 files changed, 11 insertions(+), 35 deletions(-) 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 83cb3947..368b7444 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 @@ -74,7 +74,7 @@ describe('ProjectService', () => { service.getRecentProjects().subscribe((projectsInResponse) => { expect(projectsInResponse.length).toBe(projectsFoundSize); }); - const getProjectsRequest = httpMock.expectOne(`${service.url}/recent`); + const getProjectsRequest = httpMock.expectOne(`${service.url}/recent/`); expect(getProjectsRequest.request.method).toBe('GET'); getProjectsRequest.flush(projectsList); }); diff --git a/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts b/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts index dae783cb..585fd69e 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts @@ -8,7 +8,7 @@ import { ProjectActionTypes } from './project.actions'; import { ProjectEffects } from './project.effects'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ToastrModule, ToastrService } from 'ngx-toastr'; -import { INFO_SAVED_SUCCESSFULLY, INFO_DELETE_SUCCESSFULLY } from '../../../../../shared/messages'; +import { INFO_SAVED_SUCCESSFULLY, PROJECT_DEACTIVATED_SUCCESSFULLY } from '../../../../../shared/messages'; describe('ProjectEffects', () => { let actions$: Observable; @@ -128,7 +128,7 @@ describe('ProjectEffects', () => { spyOn(service, 'deleteProject').and.returnValue(of({})); effects.deleteProject$.subscribe((action) => { - expect(toastrService.success).toHaveBeenCalledWith(INFO_DELETE_SUCCESSFULLY); + expect(toastrService.success).toHaveBeenCalledWith(PROJECT_DEACTIVATED_SUCCESSFULLY); expect(action.type).toEqual(ProjectActionTypes.DELETE_PROJECT_SUCCESS); }); }); diff --git a/src/app/modules/shared/interceptors/inject.token.interceptor.spec.ts b/src/app/modules/shared/interceptors/inject.token.interceptor.spec.ts index 11cf812c..0f02c1b7 100644 --- a/src/app/modules/shared/interceptors/inject.token.interceptor.spec.ts +++ b/src/app/modules/shared/interceptors/inject.token.interceptor.spec.ts @@ -32,19 +32,4 @@ describe('InjectTokenInterceptor test', () => { expect(handler.handle).toHaveBeenCalledWith(request); }); - it('if request.url is part of time-tracker-api, then Authorization header is injected', () => { - const interceptor = new InjectTokenInterceptor(azureAdB2CService, loginService); - interceptor.isProduction = true; - const request = new HttpRequest('GET', environment.timeTrackerApiUrl); - spyOn(handler, 'handle'); - const requestWithHeaders = request.clone( - { - headers: request.headers.set('Authorization', 'Bearer XYZ') - }); - - interceptor.intercept(request, handler); - - expect(handler.handle).toHaveBeenCalledWith(requestWithHeaders); - }); - }); 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 52789881..16166b9b 100644 --- a/src/app/modules/time-clock/services/entry.service.spec.ts +++ b/src/app/modules/time-clock/services/entry.service.spec.ts @@ -17,7 +17,7 @@ describe('EntryService', () => { service = TestBed.inject(EntryService); httpMock = TestBed.inject(HttpTestingController); service.baseUrl = 'time-entries'; - reportsUrl = service.urlInProductionLegacy ? service.baseUrl : service.baseUrl + '/report'; + reportsUrl = service.urlInProductionLegacy ? service.baseUrl : service.baseUrl + '/report/'; }); it('services are ready to be used', inject( @@ -36,7 +36,7 @@ describe('EntryService', () => { expect(response.length).toBe(1); }); - const createEntryRequest = httpMock.expectOne(service.baseUrl); + const createEntryRequest = httpMock.expectOne(`${service.baseUrl}/`); expect(createEntryRequest.request.method).toBe('POST'); createEntryRequest.flush(entry); }); @@ -44,14 +44,14 @@ describe('EntryService', () => { it('loads an activeEntry with /running', () => { service.loadActiveEntry().subscribe(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/running`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/running/`); expect(loadEntryRequest.request.method).toBe('GET'); }); it('loads summary with get /summary?time_offset=', () => { service.summary().subscribe(); const timeOffset = new Date().getTimezoneOffset(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary?time_offset=${timeOffset}`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary/?time_offset=${timeOffset}`); expect(loadEntryRequest.request.method).toBe('GET'); }); @@ -62,7 +62,7 @@ describe('EntryService', () => { const timezoneOffset = new Date().getTimezoneOffset(); service.loadEntries({ year, month }).subscribe(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}?month=${month}&year=${year}&timezone_offset=${timezoneOffset}`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/?month=${month}&year=${year}&timezone_offset=${timezoneOffset}`); expect(loadEntryRequest.request.method).toBe('GET'); }); @@ -89,7 +89,7 @@ describe('EntryService', () => { service.urlInProductionLegacy = true; service.stopEntryRunning('id').subscribe(); - const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/id/stop`); + const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/id/stop/`); expect(updateEntryRequest.request.method).toBe('POST'); }); @@ -97,7 +97,7 @@ describe('EntryService', () => { service.urlInProductionLegacy = false; service.stopEntryRunning('id').subscribe(); - const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/stop`); + const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/stop/`); expect(updateEntryRequest.request.method).toBe('PUT'); }); @@ -153,7 +153,7 @@ describe('EntryService', () => { service.findEntriesByProjectId(projectId).subscribe(); - const restartEntryRequest = httpMock.expectOne( `${service.baseUrl}?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`); + const restartEntryRequest = httpMock.expectOne( `${service.baseUrl}/?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`); expect(restartEntryRequest.request.method).toBe('GET'); }); 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 854efe5c..e6fa633b 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 @@ -501,15 +501,6 @@ describe('TimeEntriesComponent', () => { expect(component.doSave).toHaveBeenCalledTimes(0); }); - it('call cookieService.get() when call ngOnInit', () => { - spyOn(cookieService, 'get'); - const sentParameter = FeatureToggle.TIME_TRACKER_CALENDAR; - - component.ngOnInit(); - - expect(cookieService.get).toHaveBeenCalledWith(sentParameter); - }); - it('set true in displayGridView when its initial value is false and call onDisplayModeChange', () => { const expectedValue = true; From ede550751614fb9419841696c1a06de3deab8b8f Mon Sep 17 00:00:00 2001 From: mmaquina Date: Fri, 14 Apr 2023 01:13:33 -0300 Subject: [PATCH 40/66] fix: workflow-triggers (#983) --- .github/workflows/aws-ui-cd-prod.yml | 6 +++--- .github/workflows/aws-ui-cd-stage.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/aws-ui-cd-prod.yml b/.github/workflows/aws-ui-cd-prod.yml index b254dd05..ef041760 100644 --- a/.github/workflows/aws-ui-cd-prod.yml +++ b/.github/workflows/aws-ui-cd-prod.yml @@ -1,9 +1,9 @@ name: time-tracker-ui-cd-prod on: - push: - branches: - - 'fast-api*' + release: + types: + - published jobs: cd: diff --git a/.github/workflows/aws-ui-cd-stage.yml b/.github/workflows/aws-ui-cd-stage.yml index bd7c146f..e5806a81 100644 --- a/.github/workflows/aws-ui-cd-stage.yml +++ b/.github/workflows/aws-ui-cd-stage.yml @@ -1,9 +1,9 @@ name: time-tracker-ui-cd-stage on: - release: - types: - - published + push: + tags: + - 'v*.*.*' jobs: cd: From f4bfd853d03d9cf780241f1e90689694b186650c Mon Sep 17 00:00:00 2001 From: mmaquina Date: Mon, 24 Apr 2023 13:37:31 -0300 Subject: [PATCH 41/66] Fix workflow (#984) * delete actions, scp files to ec2 * fix triggers --------- Co-authored-by: Nicole Garcia --- .github/workflows/aws-ui-cd-prod.yml | 22 ++-------------------- .github/workflows/aws-ui-cd-stage.yml | 22 ++-------------------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/.github/workflows/aws-ui-cd-prod.yml b/.github/workflows/aws-ui-cd-prod.yml index ef041760..c7610e61 100644 --- a/.github/workflows/aws-ui-cd-prod.yml +++ b/.github/workflows/aws-ui-cd-prod.yml @@ -36,30 +36,12 @@ jobs: run: | make login publish_prod image_tag=$RELEASE_VERSION - - name: SCP files via ssh key - uses: appleboy/scp-action@master - env: - USERNAME: ${{ secrets.AWS_EC2_USER }} - HOST: ${{ secrets.PROD_UI_URL }} - KEY: ${{ secrets.PROD_AWS_PRIVATE_KEY }} - with: - source: './infrastructure/aws_ec2.sh' - target: '.' - - - name: SCP files via ssh key - .prod.aws.env - uses: appleboy/scp-action@master - env: - USERNAME: ${{ secrets.AWS_EC2_USER }} - HOST: ${{ secrets.PROD_UI_URL }} - KEY: ${{ secrets.PROD_AWS_PRIVATE_KEY }} - with: - source: '.prod.aws.env' - target: '.' - - name: Deploy run: | TEMP=$(mktemp) echo "${{ secrets.PROD_AWS_PRIVATE_KEY }}" > $TEMP chmod 400 $TEMP + scp -o 'StrictHostKeyChecking no' -i $TEMP ./.prod.aws.env ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }}:. + scp -o 'StrictHostKeyChecking no' -i $TEMP ./infrastructure/aws_ec2.sh ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }}:./infrastructure/aws_ec2.sh ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }} "chmod +x ./infrastructure/aws_ec2.sh" ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }} "./infrastructure/aws_ec2.sh $RELEASE_VERSION" prod diff --git a/.github/workflows/aws-ui-cd-stage.yml b/.github/workflows/aws-ui-cd-stage.yml index e5806a81..b17def91 100644 --- a/.github/workflows/aws-ui-cd-stage.yml +++ b/.github/workflows/aws-ui-cd-stage.yml @@ -38,30 +38,12 @@ jobs: run: | make login publish image_tag=$RELEASE_VERSION - - name: SCP files via ssh key - script - uses: appleboy/scp-action@master - env: - USERNAME: ${{ secrets.AWS_EC2_USER }} - HOST: ${{ secrets.STAGE_UI_URL }} - KEY: ${{ secrets.STAGE_AWS_PRIVATE_KEY }} - with: - source: './infrastructure/aws_ec2.sh' - target: '.' - - - name: SCP files via ssh key - .stage.aws.env - uses: appleboy/scp-action@master - env: - USERNAME: ${{ secrets.AWS_EC2_USER }} - HOST: ${{ secrets.STAGE_UI_URL }} - KEY: ${{ secrets.STAGE_AWS_PRIVATE_KEY }} - with: - source: '.stage.aws.env' - target: '.' - - name: Deploy run: | TEMP=$(mktemp) echo "${{ secrets.STAGE_AWS_PRIVATE_KEY }}" > $TEMP chmod 400 $TEMP + scp -o 'StrictHostKeyChecking no' -i $TEMP ./.stage.aws.env ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }}:. + scp -o 'StrictHostKeyChecking no' -i $TEMP ./infrastructure/aws_ec2.sh ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }}:./infrastructure/aws_ec2.sh ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }} "chmod +x ./infrastructure/aws_ec2.sh" ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }} "./infrastructure/aws_ec2.sh $RELEASE_VERSION" stage From 6e1fa092368039f9e102c231eb424abe864ee477 Mon Sep 17 00:00:00 2001 From: mmaquina Date: Thu, 11 May 2023 22:19:43 -0300 Subject: [PATCH 42/66] Ttl 784 change time clock page name (#986) * refactor: TTL-784 change