Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix: TT-178 Make URI clickable when possible
  • Loading branch information
wobravo committed Mar 19, 2021
commit da18ee9c37c0e514840606798055ef40758a3f68
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
datatable
[dtTrigger]="dtTrigger"
[dtOptions]="dtOptions"
*ngIf="(reportDataSource$ | async) as dataSource">
*ngIf="reportDataSource$ | async as dataSource">
<thead class="thead-blue">
<tr class="d-flex">
<th class="hidden-col">ID</th>
Expand Down Expand Up @@ -41,7 +41,13 @@
<td class="col md-col">{{ entry.customer_name }}</td>
<td class="hidden-col">{{ entry.customer_id }}</td>
<td class="col md-col">{{ entry.activity_name }}</td>
<td class="col lg-col">{{ entry.uri }}</td>
<td class="col lg-col">
<ng-container *ngIf="entry.uri !== null">
<a [class.is-url]="isURL(entry.uri)" (click)="openURLInNewTab(entry.uri)">
{{ entry.uri }}
</a>
</ng-container>
</td>
<td class="col lg-col">{{ entry.description }}</td>
<td class="col lg-col">{{ entry.technologies }}</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import '../../../../../styles/colors.scss';
.col{
white-space: nowrap;
overflow: hidden;
Expand Down Expand Up @@ -31,4 +32,26 @@
overflow-x: scroll;
width: 100%;
display: grid;
}
}

$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;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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);
});
});
Expand All @@ -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();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -24,38 +23,33 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
{
extend: 'colvis',
columns: ':not(.hidden-col)',

},
'print',
{
extend: 'excel',
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<any> = new Subject();
@ViewChild(DataTableDirective, { static: false })
Expand Down Expand Up @@ -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;
}
}
2 changes: 2 additions & 0 deletions src/app/modules/time-entries/pages/time-entries.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down