From da18ee9c37c0e514840606798055ef40758a3f68 Mon Sep 17 00:00:00 2001 From: wobravo Date: Fri, 19 Mar 2021 11:24:38 -0500 Subject: [PATCH 1/3] fix: TT-178 Make URI clickable when possible --- .../time-entries-table.component.html | 10 ++- .../time-entries-table.component.scss | 25 ++++++- .../time-entries-table.component.spec.ts | 73 +++++++++++++------ .../time-entries-table.component.ts | 39 +++++----- .../pages/time-entries.component.ts | 2 + 5 files changed, 107 insertions(+), 42 deletions(-) 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 3a6501c3c..2c45f080c 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 @@ -4,7 +4,7 @@ datatable [dtTrigger]="dtTrigger" [dtOptions]="dtOptions" - *ngIf="(reportDataSource$ | async) as dataSource"> + *ngIf="reportDataSource$ | async as dataSource"> ID @@ -41,7 +41,13 @@ {{ entry.customer_name }} {{ entry.customer_id }} {{ entry.activity_name }} - {{ entry.uri }} + + + + {{ entry.uri }} + + + {{ entry.description }} {{ entry.technologies }} diff --git a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.scss b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.scss index 1cc15655e..82fab2e2a 100644 --- a/src/app/modules/reports/components/time-entries-table/time-entries-table.component.scss +++ b/src/app/modules/reports/components/time-entries-table/time-entries-table.component.scss @@ -1,3 +1,4 @@ +@import '../../../../../styles/colors.scss'; .col{ white-space: nowrap; overflow: hidden; @@ -31,4 +32,26 @@ overflow-x: scroll; width: 100%; display: grid; -} \ No newline at end of file +} + +$url-base-color: $primary; +$url-hover-color: darken($url-base-color, 20); + +.is-url { + cursor: pointer; + color: $url-base-color; + text-decoration: underline; + + &:visited { + color: $dark; + } + + &:hover { + color: $url-hover-color; + } + + &:active { + color: $url-base-color; + } +} + 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 b58b13050..4bccd5679 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 @@ -21,7 +21,7 @@ describe('Reports Page', () => { description: 'any comment', uri: 'custom uri', project_id: '123', - project_name: 'Time-Tracker' + project_name: 'Time-Tracker', }; const state: EntryState = { @@ -33,38 +33,41 @@ describe('Reports Page', () => { timeEntriesSummary: null, timeEntriesDataSource: { data: [timeEntry], - isLoading: false + isLoading: false, }, reportDataSource: { data: [timeEntry], - isLoading: false - } + isLoading: false, + }, }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [], - declarations: [TimeEntriesTableComponent, SubstractDatePipe], - providers: [provideMockStore({ initialState: state })], - }).compileComponents(); - store = TestBed.inject(MockStore); - - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [], + declarations: [TimeEntriesTableComponent, SubstractDatePipe], + providers: [provideMockStore({ initialState: state })], + }).compileComponents(); + store = TestBed.inject(MockStore); + }) + ); - beforeEach(waitForAsync(() => { - fixture = TestBed.createComponent(TimeEntriesTableComponent); - component = fixture.componentInstance; - store.setState(state); - getReportDataSourceSelectorMock = store.overrideSelector(getReportDataSource, state.reportDataSource); - fixture.detectChanges(); - })); + beforeEach( + waitForAsync(() => { + fixture = TestBed.createComponent(TimeEntriesTableComponent); + component = fixture.componentInstance; + store.setState(state); + getReportDataSourceSelectorMock = store.overrideSelector(getReportDataSource, state.reportDataSource); + fixture.detectChanges(); + }) + ); it('component should be created', async () => { expect(component).toBeTruthy(); }); it('on success load time entries, the report should be populated', () => { - component.reportDataSource$.subscribe(ds => { + component.reportDataSource$.subscribe((ds) => { expect(ds.data).toEqual(state.reportDataSource.data); }); }); @@ -76,6 +79,34 @@ describe('Reports Page', () => { expect(component.dtTrigger.next).toHaveBeenCalled(); }); + it('when the uri starts with http or https it should return true and open the url in a new tab', () => { + const url = 'http://customuri.com'; + spyOn(component, 'isURL').and.returnValue(true); + spyOn(window, 'open'); + + expect(component.openURLInNewTab(url)).not.toEqual(''); + expect(window.open).toHaveBeenCalledWith(url, '_blank'); + }); + + it('when the uri starts without http or https it should return false and not navigate or open a new tab', () => { + const uriExpected = timeEntry.uri; + spyOn(component, 'isURL').and.returnValue(false); + + expect(component.openURLInNewTab(uriExpected)).toEqual(''); + }); + + const params = [ + {url: 'http://example.com', expected_value: true, with: 'with'}, + {url: 'https://example.com', expected_value: true, with: 'with'}, + {url: 'no-url-example', expected_value: false, with: 'without'} + ]; + params.map((param) => { + it(`when the url starts ${param.with} http or https it should return ${param.expected_value}`, () => { + + expect(component.isURL(param.url)).toEqual(param.expected_value); + }); + }); + afterEach(() => { fixture.destroy(); }); 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 cacf54688..893af0ac1 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 @@ -1,19 +1,18 @@ import { formatDate } from '@angular/common'; -import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild, NgModule } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { DataTableDirective } from 'angular-datatables'; import * as moment from 'moment'; import { Observable, Subject } from 'rxjs'; import { Entry } from 'src/app/modules/shared/models'; import { DataSource } from 'src/app/modules/shared/models/data-source.model'; - import { EntryState } from '../../../time-clock/store/entry.reducer'; import { getReportDataSource } from '../../../time-clock/store/entry.selectors'; @Component({ selector: 'app-time-entries-table', templateUrl: './time-entries-table.component.html', - styleUrls: ['./time-entries-table.component.scss'] + styleUrls: ['./time-entries-table.component.scss'], }) export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewInit { dtOptions: any = { @@ -24,7 +23,6 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn { extend: 'colvis', columns: ':not(.hidden-col)', - }, 'print', { @@ -32,30 +30,26 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn exportOptions: { format: { body: (data, row, column, node) => { - return column === 3 ? - moment.duration(data).asHours().toFixed(4).slice(0, -1) : - data; - } - } + return column === 3 ? moment.duration(data).asHours().toFixed(4).slice(0, -1) : data; + }, + }, }, text: 'Excel', - filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}` + filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}`, }, { extend: 'csv', exportOptions: { format: { body: (data, row, column, node) => { - return column === 3 ? - moment.duration(data).asHours().toFixed(4).slice(0, -1) : - data; - } - } + return column === 3 ? moment.duration(data).asHours().toFixed(4).slice(0, -1) : data; + }, + }, }, text: 'CSV', - filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}` - } - ] + filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}`, + }, + ], }; dtTrigger: Subject = new Subject(); @ViewChild(DataTableDirective, { static: false }) @@ -91,4 +85,13 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn this.dtTrigger.next(); } } + + openURLInNewTab(uri: string): WindowProxy | string { + return this.isURL(uri) ? window.open(uri, '_blank') : ''; + } + + isURL(uri: string) { + const regex = new RegExp('http*', 'g'); + return regex.test(uri) ? true : false; + } } 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 ac058c1fd..b6799b99c 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.ts @@ -160,11 +160,13 @@ export class TimeEntriesComponent implements OnInit, OnDestroy { this.selectedMonthAsText = moment().month(event.monthIndex).format('MMMM'); this.store.dispatch(new entryActions.LoadEntries(this.selectedMonth, this.selectedYear)); } + openModal(item: any) { this.idToDelete = item.id; this.message = `Are you sure you want to delete ${item.activity_name}?`; this.showModal = true; } + resetDraggablePosition(event: any): void { event.source._dragRef.reset(); } From 18e26b154bc9668c6c0044e633fc00d3ad862f8a Mon Sep 17 00:00:00 2001 From: wobravo Date: Fri, 19 Mar 2021 13:17:25 -0500 Subject: [PATCH 2/3] fix: TT-178 Make URI clickable when possible --- .../time-entries-table.component.html | 2 +- .../time-entries-table.component.spec.ts | 12 ++++++------ .../time-entries-table.component.ts | 12 ++++++++---- 3 files changed, 15 insertions(+), 11 deletions(-) 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 2c45f080c..ee771bcb7 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 @@ -4,7 +4,7 @@ datatable [dtTrigger]="dtTrigger" [dtOptions]="dtOptions" - *ngIf="reportDataSource$ | async as dataSource"> + *ngIf="(reportDataSource$ | async) as dataSource"> ID 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 4bccd5679..27cc614ea 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 @@ -89,19 +89,19 @@ describe('Reports Page', () => { }); it('when the uri starts without http or https it should return false and not navigate or open a new tab', () => { - const uriExpected = timeEntry.uri; + const url = timeEntry.uri; spyOn(component, 'isURL').and.returnValue(false); - expect(component.openURLInNewTab(uriExpected)).toEqual(''); + expect(component.openURLInNewTab(url)).toEqual(''); }); const params = [ - {url: 'http://example.com', expected_value: true, with: 'with'}, - {url: 'https://example.com', expected_value: true, with: 'with'}, - {url: 'no-url-example', expected_value: false, with: 'without'} + {url: 'http://example.com', expected_value: true}, + {url: 'https://example.com', expected_value: true}, + {url: 'no-url-example', expected_value: false} ]; params.map((param) => { - it(`when the url starts ${param.with} http or https it should return ${param.expected_value}`, () => { + it(`Given the url ${param.url}, the method isURL should return ${param.expected_value}`, () => { expect(component.isURL(param.url)).toEqual(param.expected_value); }); 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 893af0ac1..4bf5207ee 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 @@ -1,5 +1,5 @@ import { formatDate } from '@angular/common'; -import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild, NgModule } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; import { select, Store } from '@ngrx/store'; import { DataTableDirective } from 'angular-datatables'; import * as moment from 'moment'; @@ -30,7 +30,9 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn exportOptions: { format: { body: (data, row, column, node) => { - return column === 3 ? moment.duration(data).asHours().toFixed(4).slice(0, -1) : data; + return column === 3 ? + moment.duration(data).asHours().toFixed(4).slice(0, -1) : + data; }, }, }, @@ -42,7 +44,9 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn exportOptions: { format: { body: (data, row, column, node) => { - return column === 3 ? moment.duration(data).asHours().toFixed(4).slice(0, -1) : data; + return column === 3 ? + moment.duration(data).asHours().toFixed(4).slice(0, -1) : + data; }, }, }, @@ -92,6 +96,6 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn isURL(uri: string) { const regex = new RegExp('http*', 'g'); - return regex.test(uri) ? true : false; + return regex.test(uri); } } From 545bdc0ddb57c310ae6349ce815614a2a2ca7c1d Mon Sep 17 00:00:00 2001 From: wobravo Date: Fri, 19 Mar 2021 14:09:11 -0500 Subject: [PATCH 3/3] fix: TT-178 Make URI clickable when possible --- .../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 4bf5207ee..cc58021e9 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 @@ -94,7 +94,7 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn return this.isURL(uri) ? window.open(uri, '_blank') : ''; } - isURL(uri: string) { + isURL(uri: string): boolean { const regex = new RegExp('http*', 'g'); return regex.test(uri); }