Skip to content

Commit

Permalink
Merge pull request #235 from amosproj/195-frontendbackend-module-over…
Browse files Browse the repository at this point in the history
…view-about-total-backups

195 frontendbackend module overview about total backups
  • Loading branch information
chrisklg authored Jan 12, 2025
2 parents 9662cc4 + ffaa079 commit 15356ad
Show file tree
Hide file tree
Showing 19 changed files with 328 additions and 6 deletions.
4 changes: 3 additions & 1 deletion apps/backend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BackupDataModule } from './backupData/backupData.module';
import { AlertingModule } from './alerting/alerting.module';
import { TasksModule } from './tasks/tasks.module';
import { DataStoresModule } from './dataStores/dataStores.module';
import { InformationModule } from './information/information.module';

@Module({
imports: [
Expand All @@ -24,7 +25,8 @@ import { DataStoresModule } from './dataStores/dataStores.module';
BackupDataModule,
AlertingModule,
TasksModule,
DataStoresModule
DataStoresModule,
InformationModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
19 changes: 19 additions & 0 deletions apps/backend/src/app/backupData/backupData.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { SortOrder } from '../utils/pagination/SortOrder';
import { TasksService } from '../tasks/tasks.service';
import { BackupDataFilterByTaskIdsDto } from './dto/backupDataFilterByTaskIds.dto';
import { TaskEntity } from '../tasks/entity/task.entity';
import { BackupInformationDto } from '../information/dto/backupInformation.dto';

@Injectable()
export class BackupDataService extends PaginationService {
Expand All @@ -39,6 +40,24 @@ export class BackupDataService extends PaginationService {
super();
}

async getTotalBackupInformation(): Promise<BackupInformationDto> {
// Sum the size of all backups
const totalSize = await this.backupDataRepository
.createQueryBuilder('backup')
.select('SUM(backup.sizeMB)', 'total')
.getRawOne();

//Get number of all backups
const numberOfBackups = await this.backupDataRepository
.createQueryBuilder('backup')
.select('COUNT(backup.id)', 'total')
.getRawOne();
return {
totalBackupSize: totalSize?.total ?? 0,
numberOfBackups: numberOfBackups?.total ?? 0,
};
}

/**
* Find one Backup by id.
* @param id
Expand Down
15 changes: 15 additions & 0 deletions apps/backend/src/app/information/dto/backupInformation.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';

export class BackupInformationDto {
@ApiProperty({
type: 'number',
description: 'The total size of all backups in MB.',
})
totalBackupSize!: number;

@ApiProperty({
type: 'number',
description: 'The number of backups that are stored.',
})
numberOfBackups!: number;
}
19 changes: 19 additions & 0 deletions apps/backend/src/app/information/information.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller, Get, Logger } from '@nestjs/common';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { InformationService } from './information.service';
import { BackupInformationDto } from './dto/backupInformation.dto';

@ApiTags('Informations')
@Controller('information')
export class InformationController {
readonly logger = new Logger(InformationController.name);

constructor(private readonly informationService: InformationService) {}

@Get()
@ApiOperation({ summary: 'Returns informations about metadata of our analysations' })
@ApiOkResponse()
async findAll(): Promise<BackupInformationDto> {
return this.informationService.getBackupInformation();
}
}
11 changes: 11 additions & 0 deletions apps/backend/src/app/information/information.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { InformationService } from './information.service';
import { InformationController } from './information.controller';
import { BackupDataModule } from '../backupData/backupData.module';

@Module({
providers: [InformationService],
imports: [BackupDataModule],
controllers: [InformationController],
})
export class InformationModule {}
44 changes: 44 additions & 0 deletions apps/backend/src/app/information/information.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Test, TestingModule } from '@nestjs/testing';
import { InformationService } from './information.service';
import { BackupInformationDto } from './dto/backupInformation.dto';
import { BackupDataService } from '../backupData/backupData.service';

describe('InformationService', () => {
let service: InformationService;
let backupDataService: BackupDataService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
InformationService,
{
provide: BackupDataService,
useValue: {
getTotalBackupInformation: jest.fn(),
},
},
],
}).compile();

backupDataService = module.get<BackupDataService>(BackupDataService);
service = module.get<InformationService>(InformationService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('should return backup information', async () => {
const mockBackupInformation: BackupInformationDto = {
totalBackupSize: 100,
numberOfBackups: 10,
};

jest
.spyOn(backupDataService, 'getTotalBackupInformation')
.mockResolvedValue(mockBackupInformation);

const result = await service.getBackupInformation();
expect(result).toEqual(mockBackupInformation);
});
});
12 changes: 12 additions & 0 deletions apps/backend/src/app/information/information.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
import { BackupInformationDto } from './dto/backupInformation.dto';
import { BackupDataService } from '../backupData/backupData.service';

@Injectable()
export class InformationService {
constructor(private readonly backupDataService: BackupDataService) {}

async getBackupInformation(): Promise<BackupInformationDto> {
return await this.backupDataService.getTotalBackupInformation();
}
}
5 changes: 4 additions & 1 deletion apps/frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ import { InformationPanelComponent } from './backups-overview-page/component/inf
import { BackupTableComponent } from './backup-statistics-page/component/backup-table/backup-table.component';
import { SidePanelComponent } from './shared/components/filter-side-panel/side-panel.component';
import { BackupStatisticsPageComponent } from './backup-statistics-page/component/backup-statistics-page.component';
import { FactsPanelComponent } from './backups-overview-page/component/facts-panel/facts-panel.component';
import { UserManualComponent } from './management/components/user-manual/user-manual/user-manual.component';


@NgModule({
declarations: [
AppComponent,
Expand All @@ -62,7 +64,8 @@ import { UserManualComponent } from './management/components/user-manual/user-ma
BackupTableComponent,
SidePanelComponent,
BackupStatisticsPageComponent,
UserManualComponent
FactsPanelComponent,
UserManualComponent,
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.facts-card {
display: flex;
justify-content: space-around;
padding: 20px;
background-color: #f3f8fb;
border: 1px solid #dcdcdc;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
text-align: center;
}

.facts-title {
font-size: 1.2em;
margin-bottom: 8px;
color: #6c757d;
}

.facts-value {
font-size: 2em;
font-weight: bold;
color: #343a40;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="card facts-card">
<div class="facts-item">
<div class="facts-title">Total Size of All Backups</div>
<div class="facts-value">{{ shortenBytes(((basicInformations$ | async)?.totalBackupSize ?? 0) * 1000000) }}</div>
</div>
<div class="facts-item">
<div class="facts-title">Number of Backups</div>
<div class="facts-value">{{ (basicInformations$ | async)?.numberOfBackups }}</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { of } from 'rxjs';
import { FactsPanelComponent } from './facts-panel.component';
import { BasicInformation } from '../../../shared/types/basicInformation';

const mockInformations: BasicInformation = {
numberOfBackups: 10,
totalBackupSize: 100,
};
describe('FactsPanel', () => {
let component: FactsPanelComponent;
let mockInformationService: {
getBasicInformations: Mock;
};

beforeEach(() => {
mockInformationService = {
getBasicInformations: vi.fn().mockReturnValue(of(mockInformations)),
};

component = new FactsPanelComponent(mockInformationService as any);
});

describe('basicInformations$', () => {
it('should load basic informations correctly', (done) => {
mockInformationService.getBasicInformations.mockReturnValue(
of(mockInformations)
);

component.loadBasicInformations();

component.basicInformations$.subscribe((result) => {
expect(result).toEqual(mockInformations);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, of, shareReplay, Subject, takeUntil } from 'rxjs';
import { BasicInformation } from '../../../shared/types/basicInformation';
import { InformationServiceService } from '../../../shared/services/information-service/information-service.service';
import { shortenBytes } from '../../../shared/utils/shortenBytes';

@Component({
selector: 'app-facts-panel',
templateUrl: './facts-panel.component.html',
styleUrl: './facts-panel.component.css',
})
export class FactsPanelComponent implements OnDestroy, OnInit {
private readonly destroy$ = new Subject<void>();
basicInformations$: Observable<BasicInformation> = of();

constructor(private readonly informationService: InformationServiceService) {}

ngOnInit() {
this.loadBasicInformations();
this.informationService
.getRefreshObservable()
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.loadBasicInformations();
});
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}

loadBasicInformations(): void {
this.basicInformations$ = this.informationService
.getBasicInformations()
.pipe(takeUntil(this.destroy$), shareReplay(1));
}

protected readonly shortenBytes = shortenBytes;
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ <h1 class="header">Overview</h1>
</div>
</div>
</div>
<div class="clr-col-6"></div>
<div class="clr-col-6">
<app-facts-panel></app-facts-panel>
</div>
</div>

<app-side-panel [isOpen]="filterPanel" [charts]="charts"></app-side-panel>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AnalyzerService } from './analyzer-service';
import { AlertServiceService } from '../alert-service/alert-service.service';
import { BackupService } from '../backup-service/backup-service.service';
import { DataStoresService } from '../data-stores-service/data-stores-service.service';
import { InformationServiceService } from '../information-service/information-service.service';

describe('AnalyzerService', () => {
let service: AnalyzerService;
Expand All @@ -20,6 +21,9 @@ describe('AnalyzerService', () => {
let backupServiceMock: {
refresh: ReturnType<typeof vi.fn>;
};
let informationServiceMock: {
refresh: ReturnType<typeof vi.fn>;
};
const baseUrl = 'http://test-url';

beforeEach(() => {
Expand All @@ -38,13 +42,17 @@ describe('AnalyzerService', () => {
backupServiceMock = {
refresh: vi.fn(),
};
informationServiceMock = {
refresh: vi.fn(),
};

service = new AnalyzerService(
baseUrl,
httpClientMock as unknown as HttpClient,
alertServiceMock as unknown as AlertServiceService,
dataStoresServiceMock as unknown as DataStoresService,
backupServiceMock as unknown as BackupService
backupServiceMock as unknown as BackupService,
informationServiceMock as unknown as InformationServiceService
);
});

Expand All @@ -61,6 +69,7 @@ describe('AnalyzerService', () => {
expect(alertServiceMock.refresh).toHaveBeenCalled();
expect(dataStoresServiceMock.refresh).toHaveBeenCalled();
expect(backupServiceMock.refresh).toHaveBeenCalled();
expect(informationServiceMock.refresh).toHaveBeenCalled();
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BASE_URL } from '../../types/configuration';
import { AlertServiceService } from '../alert-service/alert-service.service';
import { DataStoresService } from '../data-stores-service/data-stores-service.service';
import { BackupService } from '../backup-service/backup-service.service';
import { InformationServiceService } from '../information-service/information-service.service';

@Injectable({
providedIn: 'root',
Expand All @@ -15,7 +16,8 @@ export class AnalyzerService {
private readonly http: HttpClient,
private readonly alertService: AlertServiceService,
private readonly dataStoresService: DataStoresService,
private readonly backupService: BackupService
private readonly backupService: BackupService,
private readonly informationService: InformationServiceService
) {}

refresh(): Observable<void> {
Expand All @@ -24,6 +26,7 @@ export class AnalyzerService {
this.alertService.refresh();
this.dataStoresService.refresh();
this.backupService.refresh();
this.informationService.refresh();
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fail } from 'assert';
import { DataStoresService } from './data-stores-service.service';
import { DataStore } from '../../types/data-store';

describe('BackupService', () => {
describe('DataStoreService', () => {
let service: DataStoresService;
let httpClientMock: {
get: ReturnType<typeof vi.fn>;
Expand Down
Loading

0 comments on commit 15356ad

Please sign in to comment.