Skip to content

Commit

Permalink
Replaces overview headlines with MatTable (#409)
Browse files Browse the repository at this point in the history
* Add overview table and use in overview (with sorting, pagination and draggable columns)

* Add overview table tests

* Add overview component reload on add and edit

* Add overview tests

* Move getCount to parent class

* Add remote data test

* Fix styles
  • Loading branch information
minottic authored Oct 25, 2024
1 parent 60a7788 commit 5bfcd28
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 61 deletions.
15 changes: 12 additions & 3 deletions scilog/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@ang
import { MatSidenavModule } from '@angular/material/sidenav';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ScrollingModule as ExperimentalScrollingModule } from '@angular/cdk-experimental/scrolling';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { CdkDrag, CdkDropList, DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { HttpClientModule } from '@angular/common/http';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
Expand Down Expand Up @@ -84,6 +84,9 @@ import { AppConfigService } from "./app-config.service";
import { NavigationGuardService } from './logbook/core/navigation-guard-service';
import { TaskComponent } from './logbook/core/task/task.component';
import { ResizedDirective } from '@shared/directives/resized.directive';
import { OverviewTableComponent } from './overview/overview-table/overview-table.component';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';

const appConfigInitializerFn = (appConfig: AppConfigService) => {
return () => appConfig.loadAppConfig();
Expand Down Expand Up @@ -129,7 +132,8 @@ const appConfigInitializerFn = (appConfig: AppConfigService) => {
SearchComponent,
SearchWindowComponent,
TaskComponent,
ResizedDirective
ResizedDirective,
OverviewTableComponent
],
imports: [
BrowserModule,
Expand Down Expand Up @@ -170,7 +174,12 @@ const appConfigInitializerFn = (appConfig: AppConfigService) => {
MatTabsModule,
UiScrollModule,
MatProgressBarModule,
MatSnackBarModule
MatSnackBarModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
CdkDrag,
CdkDropList,
],
providers: [
AppConfigService,
Expand Down
11 changes: 10 additions & 1 deletion scilog/src/app/core/remote-data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,23 @@ describe('LogbookDataService', () => {
expect(spyGetSnippets.calls.mostRecent().args[0]).toEqual("logbooks/1");
});


it('should call getSnippets with list of ids', () => {
const spyGetSnippets = spyOn<LogbookDataService, any>(service, "getSnippets").and.returnValue(of([]));
service.getLogbooksInfo(["1", "2", "3"]);
expect(spyGetSnippets.calls.mostRecent().args[1]["params"].toString()).
toEqual("filter=%7B%22where%22:%7B%22id%22:%7B%22inq%22:%5B%221%22,%222%22,%223%22%5D%7D%7D%7D");
});

it('should test _prepareFilters', () => {
const filters = service['_prepareFilters']({ general: {}, filter: {}, view: {} }, 10, 20);
expect(filters).toEqual({
order: ['defaultOrder DESC'],
where: {and: [{snippetType: 'logbook', deleted: false}]},
limit: 20,
skip: 10
});
});

});

describe('SearchDataService', () => {
Expand Down
45 changes: 25 additions & 20 deletions scilog/src/app/core/remote-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ interface Count {
export class RemoteDataService {


get imagesLocation () {
return `${this.serverSettings.getServerAddress()}/images`
}

constructor(private httpClient: HttpClient,
private serverSettings: ServerSettingsService) { }

Expand Down Expand Up @@ -128,6 +132,14 @@ export class RemoteDataService {
};
}

async getCount(config: any) {
let filter = this._prepareFilters(config);
console.log(filter);
let params = new HttpParams();
params = params.set('where', JSON.stringify(filter["where"]));
return this.getSnippets<Count>('basesnippets/count', { params: params }).toPromise()
}

}

@Injectable({
Expand Down Expand Up @@ -206,16 +218,6 @@ export class LogbookItemDataService extends RemoteDataService {
return this.getSnippets<Basesnippets>('basesnippets/' + id, { headers: headers, params: params }).toPromise();
}

async getCount(config: any) {
let filter = this._prepareFilters(config);
// let whereFilter = filter["where"];
console.log(filter);
let params = new HttpParams();
params = params.set('where', JSON.stringify(filter["where"]));
// let count:Count = await this.getSnippets<Count>('basesnippets/count', {params:params}).toPromise();
return this.getSnippets<Count>('basesnippets/count', { params: params }).toPromise()
}

async getIndex(id: string, config: any) {
let filter = this._prepareFilters(config);
console.log(filter);
Expand Down Expand Up @@ -368,9 +370,20 @@ export class LogbookDataService extends RemoteDataService {
headers = headers.set('Content-Type', 'application/json; charset=utf-8');
this._searchString = this._searchString.trim();

let httpFilter: Object = this._prepareFilters(config, index, count);
let params = new HttpParams();
params = params.set('filter', JSON.stringify(httpFilter))

if (this._searchString.length == 0) {
return this.getSnippets<any[]>('basesnippets', { headers: headers, params: params }).toPromise();
} else {
return this.getSnippets<any[]>('basesnippets/search=' + this._searchString, { headers: headers, params: params }).toPromise();
}
}

protected _prepareFilters(config: WidgetItemConfig, index: number = 0, count: number = Infinity): Object {
let httpFilter: Object = {};
httpFilter["order"] = ["defaultOrder DESC"];
httpFilter["order"] = config.view.order ?? ["defaultOrder DESC"];

let whereFilter: Object[] = [];
whereFilter.push({ "snippetType": "logbook", deleted: false });
Expand All @@ -387,16 +400,8 @@ export class LogbookDataService extends RemoteDataService {
if (this._searchString.length > 0) {
httpFilter["include"] = [{ "relation": "subsnippets" }];
}
let params = new HttpParams();
params = params.set('filter', JSON.stringify(httpFilter))

if (this._searchString.length == 0) {
return this.getSnippets<any[]>('basesnippets', { headers: headers, params: params }).toPromise();
} else {
return this.getSnippets<any[]>('basesnippets/search=' + this._searchString, { headers: headers, params: params }).toPromise();
}
return httpFilter;
}

}

@Injectable({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<section class="scrollable-container mat-elevation-z8" tabindex="0">
<table mat-table [dataSource]="dataSource" matSort cdkDropList cdkDropListOrientation="horizontal"
(cdkDropListDropped)="drop($event)" (matSortChange)="onSortChange()">
<ng-container matColumnDef="name">
<th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>Title </th>
<td mat-cell *matCellDef="let row"> {{row.name}} </td>
</ng-container>

<ng-container matColumnDef="description">
<th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>Description </th>
<td mat-cell *matCellDef="let row"> {{row.description}} </td>
</ng-container>

<ng-container matColumnDef="ownerGroup">
<th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>ownerGroup </th>
<td mat-cell *matCellDef="let row"> {{row.ownerGroup}} </td>
</ng-container>

<ng-container matColumnDef="createdAt">
<th mat-header-cell cdkDrag *matHeaderCellDef mat-sort-header>Date </th>
<td mat-cell *matCellDef="let row"> {{row.createdAt | date}} </td>
</ng-container>

<ng-container matColumnDef="thumbnail">
<th mat-header-cell cdkDrag *matHeaderCellDef>Thumbnail </th>
<td mat-cell *matCellDef="let row"><img [src]="getImage(row.thumbnail)" style="height: 100%;" /> </td>
</ng-container>

<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="mat-fab-top-right"></th>
<td mat-cell *matCellDef="let row" class="mat-fab-top-right">
<button mat-icon-button aria-label="Actions" [matMenuTriggerFor]="menu"
[disabled]="isActionAllowed.tooltips.edit" matTooltip="{{ isActionAllowed.tooltips.edit }}">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<span [matTooltip]="isActionAllowed.tooltips.update">
<button mat-menu-item (click)="editLogbook(row)" [disabled]="isActionAllowed.tooltips.update">
<mat-icon>edit</mat-icon>
Edit
</button>
</span>
<span [matTooltip]="isActionAllowed.tooltips.delete">
<button mat-menu-item (click)="deleteLogbook(row.id)" [disabled]="isActionAllowed.tooltips.delete">
<mat-icon>delete</mat-icon>
Delete
</button>
</span>
</mat-menu>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row class="logbooks" *matRowDef="let row; columns: displayedColumns;" (dblclick)="openLogbook(row.id)"></tr>
</table>

</section>

<mat-paginator [length]="totalItems" [pageSize]="10" [pageSizeOptions]="[10, 20, 50, 100]" (page)="onPageChange()"
showFirstLastButtons></mat-paginator>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
table {
width: 100%;
table-layout: fixed;
}

table th,
table td {
white-space: nowrap;
overflow: scroll;
}


.logbooks:hover {
background-color: var(--main-background)!important;
-webkit-transition: background-color 100ms linear, color 100ms linear;
-ms-transition: background-color 100ms linear, color 100ms linear;
transition: background-color 100ms linear, color 100ms linear;
}

.logbooks {
height: 65px;
}

.logbooks img {
max-height: 60px;
object-fit: contain;
}

.mat-fab-top-right {
text-align: right;
width: 2px;
padding-right: 40px!important;
overflow: hidden!important;
}

.scrollable-container {
max-height: 65px * 11;
width: 100%;
overflow: auto;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { OverviewTableComponent } from './overview-table.component';
import { LogbookDataService } from 'src/app/core/remote-data.service';
import { UserPreferencesService } from 'src/app/core/user-preferences.service';
import { Logbooks } from 'src/app/core/model/logbooks';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { MatPaginatorModule } from '@angular/material/paginator';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

class UserPreferencesMock {
userInfo = { roles: ["roles"] };
}

describe('OverviewTableComponent', () => {
let component: OverviewTableComponent;
let fixture: ComponentFixture<OverviewTableComponent>;
const logbookDataSpy = jasmine.createSpyObj(
'LogbookDataService',
['getDataBuffer', 'getCount'],
{ imagesLocation: 'server/images' }
);
logbookDataSpy.getCount.and.returnValue({ count: 1 });
logbookDataSpy.getDataBuffer.and.returnValue([{ abc: 1 }]);
const paginatorSpy = jasmine.createSpyObj("MatPaginator", {}, { pageIndex: 0, pageSize: 5 });
const sortSpy = jasmine.createSpyObj("MatSort", ['sort']);


beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [OverviewTableComponent],
imports: [MatPaginatorModule, NoopAnimationsModule],
providers: [
{ provide: LogbookDataService, useValue: logbookDataSpy },
{ provide: UserPreferencesService, useClass: UserPreferencesMock },
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(OverviewTableComponent);
component = fixture.componentInstance;
component.config = { general: {}, filter: {}, view: {} };
fixture.detectChanges();
component.sort = sortSpy;
component.sort.active = 'name';
component.sort.direction = 'asc';
component.paginator = paginatorSpy;
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should test itemsCount', async () => {
await component['itemsCount']();
expect(component.totalItems).toEqual(1);
});

it('should test getLogbooks', async () => {
logbookDataSpy.getDataBuffer.calls.reset();
await component.getLogbooks();
expect(logbookDataSpy.getDataBuffer).toHaveBeenCalledOnceWith(0, 5, component.config);
expect(component.dataSource.data).toEqual([{ abc: 1 } as Logbooks]);
});

it('should test openLogbook', () => {
const routerSpy = spyOn(component['router'], 'navigateByUrl');
component.openLogbook('123');
expect(routerSpy).toHaveBeenCalledOnceWith('/logbooks/123/dashboard');
});

it('should test drop', () => {
expect(component.displayedColumns).toEqual(['name', 'description', 'ownerGroup', 'createdAt', 'thumbnail', 'actions']);
component.drop({ previousIndex: 1, currentIndex: 2 } as CdkDragDrop<string[]>);
expect(component.displayedColumns).toEqual(['name', 'ownerGroup', 'description', 'createdAt', 'thumbnail', 'actions']);
});

[undefined, '123'].forEach(t => {
it(`should test getImage ${t}`, () => {
expect(component.getImage(t)).toEqual(t ? 'server/images/123' : t);
});
});

it('should test onPageChange', () => {
const getLogbooks = spyOn(component, 'getLogbooks');
component.onPageChange();
expect(getLogbooks).toHaveBeenCalledTimes(1);
});

it('should test onSortChange', () => {
const getDatasetsSpy = spyOn(component, 'getLogbooks');
component.onSortChange();
expect(component['_config'].view.order).toEqual(['name asc']);
expect(getDatasetsSpy).toHaveBeenCalledTimes(1);
});

it('should test resetSortAndReload', async () => {
await component.resetSortAndReload();
expect(component.sort.active).toEqual('');
expect(component.sort.direction).toEqual('');
expect(component['_config']).toEqual({ general: {}, filter: {}, view: {} });
});

})
Loading

0 comments on commit 5bfcd28

Please sign in to comment.