280 lines
8.6 KiB
TypeScript
280 lines
8.6 KiB
TypeScript
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { MatTableModule } from '@angular/material/table';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { MatChipsModule } from '@angular/material/chips';
|
|
import { MatCardModule } from '@angular/material/card';
|
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
import { MatDialogModule } from '@angular/material/dialog';
|
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
import { Artifact } from '../../models/artifact.interface';
|
|
import { ArtifactService } from '../../services/artifact.service';
|
|
import { NotificationService } from '../../services/notification.service';
|
|
import { TagManagerComponent } from '../tag-manager/tag-manager.component';
|
|
|
|
@Component({
|
|
selector: 'app-artifacts-table',
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
MatTableModule,
|
|
MatButtonModule,
|
|
MatIconModule,
|
|
MatChipsModule,
|
|
MatCardModule,
|
|
MatProgressSpinnerModule,
|
|
MatTooltipModule,
|
|
MatDialogModule,
|
|
MatSnackBarModule,
|
|
TagManagerComponent
|
|
],
|
|
templateUrl: './artifacts-table.component.html',
|
|
styleUrl: './artifacts-table.component.scss'
|
|
})
|
|
export class ArtifactsTableComponent implements OnInit, OnChanges {
|
|
@Input() artifacts: Artifact[] = [];
|
|
@Input() filters: any = {};
|
|
|
|
displayedColumns: string[] = [
|
|
'id',
|
|
'eventId',
|
|
'filename',
|
|
'type',
|
|
'size',
|
|
'binaries',
|
|
'testName',
|
|
'suite',
|
|
'result',
|
|
'tags',
|
|
'created',
|
|
'actions'
|
|
];
|
|
|
|
expandedBinaries: { [key: number]: boolean } = {};
|
|
expandedTags: { [key: number]: boolean } = {};
|
|
|
|
currentPage = 1;
|
|
pageSize = 25;
|
|
loading = false; // Start with false to show content immediately
|
|
|
|
selectedArtifact: Artifact | null = null;
|
|
showDetailModal = false;
|
|
|
|
filteredArtifacts: Artifact[] = [];
|
|
|
|
constructor(
|
|
private artifactService: ArtifactService,
|
|
private notificationService: NotificationService
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
console.log('ArtifactsTableComponent ngOnInit - artifacts count:', this.artifacts.length);
|
|
console.log('Initial loading state:', this.loading);
|
|
// Always load artifacts on init
|
|
this.loadArtifacts();
|
|
// Force show after a delay to debug
|
|
setTimeout(() => {
|
|
console.log('Timeout - forcing loading to false');
|
|
this.loading = false;
|
|
}, 2000);
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
console.log('ArtifactsTableComponent ngOnChanges - artifacts:', changes['artifacts']?.currentValue?.length || 0);
|
|
// Re-apply filters when artifacts or filters input changes
|
|
if (changes['artifacts'] || changes['filters']) {
|
|
this.applyFilters();
|
|
}
|
|
}
|
|
|
|
loadArtifacts(): void {
|
|
console.log('Loading artifacts...');
|
|
this.loading = true;
|
|
this.artifactService.getArtifacts(this.pageSize, (this.currentPage - 1) * this.pageSize)
|
|
.subscribe({
|
|
next: (artifacts) => {
|
|
console.log('Loaded artifacts:', artifacts.length);
|
|
this.artifacts = artifacts;
|
|
this.applyFilters();
|
|
this.loading = false;
|
|
console.log('Loading complete. loading =', this.loading);
|
|
},
|
|
error: (error) => {
|
|
console.error('Error loading artifacts:', error);
|
|
this.loading = false;
|
|
console.log('Error occurred. loading =', this.loading);
|
|
}
|
|
});
|
|
}
|
|
|
|
applyFilters(): void {
|
|
console.log('Applying filters to', this.artifacts.length, 'artifacts');
|
|
this.filteredArtifacts = this.artifacts.filter(artifact => {
|
|
if (this.filters.filename && !artifact.filename.toLowerCase().includes(this.filters.filename.toLowerCase())) {
|
|
return false;
|
|
}
|
|
if (this.filters.fileType && artifact.file_type !== this.filters.fileType) {
|
|
return false;
|
|
}
|
|
if (this.filters.testName && !artifact.test_name?.toLowerCase().includes(this.filters.testName.toLowerCase())) {
|
|
return false;
|
|
}
|
|
if (this.filters.testSuite && !artifact.test_suite?.toLowerCase().includes(this.filters.testSuite.toLowerCase())) {
|
|
return false;
|
|
}
|
|
if (this.filters.testResult && artifact.test_result !== this.filters.testResult) {
|
|
return false;
|
|
}
|
|
if (this.filters.tags && this.filters.tags.length > 0) {
|
|
const hasMatchingTag = this.filters.tags.some((tag: string) =>
|
|
artifact.tags.some(artifactTag => artifactTag.toLowerCase().includes(tag.toLowerCase()))
|
|
);
|
|
if (!hasMatchingTag) return false;
|
|
}
|
|
return true;
|
|
});
|
|
console.log('Filtered artifacts count:', this.filteredArtifacts.length);
|
|
}
|
|
|
|
toggleBinariesExpansion(artifactId: number): void {
|
|
this.expandedBinaries[artifactId] = !this.expandedBinaries[artifactId];
|
|
}
|
|
|
|
toggleTagsExpansion(artifactId: number): void {
|
|
this.expandedTags[artifactId] = !this.expandedTags[artifactId];
|
|
}
|
|
|
|
showDetail(artifact: Artifact): void {
|
|
this.selectedArtifact = artifact;
|
|
this.showDetailModal = true;
|
|
}
|
|
|
|
closeDetailModal(): void {
|
|
this.showDetailModal = false;
|
|
this.selectedArtifact = null;
|
|
}
|
|
|
|
downloadArtifact(artifact: Artifact): void {
|
|
this.artifactService.downloadArtifact(artifact.id).subscribe({
|
|
next: (blob) => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = artifact.filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
},
|
|
error: (error) => {
|
|
console.error('Error downloading artifact:', error);
|
|
this.notificationService.showError('Error downloading artifact: ' + error.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
async deleteArtifact(artifact: Artifact): Promise<void> {
|
|
const confirmed = await this.notificationService.showConfirmation(
|
|
`Are you sure you want to delete "${artifact.filename}"? This cannot be undone.`,
|
|
'Delete'
|
|
);
|
|
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
this.artifactService.deleteArtifact(artifact.id).subscribe({
|
|
next: () => {
|
|
this.notificationService.showSuccess('Artifact deleted successfully');
|
|
this.loadArtifacts();
|
|
},
|
|
error: (error) => {
|
|
console.error('Error deleting artifact:', error);
|
|
this.notificationService.showError('Error deleting artifact: ' + error.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
generateSeedData(): void {
|
|
const count = prompt('How many artifacts to generate? (1-100)', '10');
|
|
if (!count) return;
|
|
|
|
const num = parseInt(count);
|
|
if (isNaN(num) || num < 1 || num > 100) {
|
|
this.notificationService.showWarning('Please enter a number between 1 and 100');
|
|
return;
|
|
}
|
|
|
|
this.artifactService.generateSeedData(num).subscribe({
|
|
next: (result) => {
|
|
this.notificationService.showSuccess(result.message || 'Seed data generated successfully');
|
|
this.loadArtifacts();
|
|
},
|
|
error: (error) => {
|
|
console.error('Error generating seed data:', error);
|
|
this.notificationService.showError('Error generating seed data: ' + error.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
formatBytes(bytes: number): string {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
formatDate(dateString: string): string {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
getVisibleBinaries(binaries: string[] | undefined): string[] {
|
|
if (!binaries) return [];
|
|
return binaries.slice(0, 4);
|
|
}
|
|
|
|
getHiddenBinariesCount(binaries: string[] | undefined): number {
|
|
if (!binaries) return 0;
|
|
return Math.max(0, binaries.length - 4);
|
|
}
|
|
|
|
getVisibleTags(tags: string[]): string[] {
|
|
return tags.slice(0, 3);
|
|
}
|
|
|
|
getHiddenTagsCount(tags: string[]): number {
|
|
return Math.max(0, tags.length - 3);
|
|
}
|
|
|
|
previousPage(): void {
|
|
if (this.currentPage > 1) {
|
|
this.currentPage--;
|
|
this.loadArtifacts();
|
|
}
|
|
}
|
|
|
|
nextPage(): void {
|
|
this.currentPage++;
|
|
this.loadArtifacts();
|
|
}
|
|
|
|
onTagsUpdated(): void {
|
|
this.loadArtifacts();
|
|
}
|
|
|
|
getResultIcon(result: string): string {
|
|
switch (result) {
|
|
case 'pass': return 'check_circle';
|
|
case 'fail': return 'cancel';
|
|
case 'skip': return 'skip_next';
|
|
case 'error': return 'error';
|
|
default: return 'help';
|
|
}
|
|
}
|
|
}
|