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 { 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'; } } }