Files
warehouse13/frontend/src/app/components/artifacts-table/artifacts-table.component.ts
2025-10-14 23:32:38 -05:00

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