Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replaces overview headlines view with table view, with sorting, pagination and draggable columns #409

Merged
merged 7 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Loading