Add delete artifact button. Refactor for updated Angular.

This commit is contained in:
Brian Hood 2024-04-29 16:05:21 -05:00
parent e4af78213d
commit a982ad6c45
5 changed files with 105 additions and 6 deletions
src/app
app.module.ts
webapp-common
experiments/dumb/experiment-artifact-item-view
settings/admin
shared/ui-components/overlay/upload-artifact-dialog

View File

@ -27,6 +27,11 @@ import {MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltipDefaultOptions} from '@angular/ma
import {UpdateNotifierComponent} from '@common/shared/ui-components/overlay/update-notifier/update-notifier.component'; import {UpdateNotifierComponent} from '@common/shared/ui-components/overlay/update-notifier/update-notifier.component';
import {ChooseColorModule} from '@common/shared/ui-components/directives/choose-color/choose-color.module'; import {ChooseColorModule} from '@common/shared/ui-components/directives/choose-color/choose-color.module';
import {SpinnerComponent} from '@common/shared/ui-components/overlay/spinner/spinner.component'; import {SpinnerComponent} from '@common/shared/ui-components/overlay/spinner/spinner.component';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { UploadArtifactDialogComponent } from '@common/shared/ui-components/overlay/upload-artifact-dialog/upload-artifact-dialog.component';
@NgModule({ @NgModule({
declarations : [AppComponent], declarations : [AppComponent],
@ -61,6 +66,10 @@ import {SpinnerComponent} from '@common/shared/ui-components/overlay/spinner/spi
UpdateNotifierComponent, UpdateNotifierComponent,
ChooseColorModule, ChooseColorModule,
SpinnerComponent, SpinnerComponent,
MatButtonModule,
MatInputModule,
MatFormFieldModule,
UploadArtifactDialogComponent
], ],
providers : [ providers : [
UserPreferences, UserPreferences,

View File

@ -18,6 +18,7 @@
</sm-labeled-row> </sm-labeled-row>
<sm-labeled-row label="FILE SIZE" data-id="fileSizeId">{{(artifact?.content_size | filesize : fileSizeConfigStorage) || ''}}</sm-labeled-row> <sm-labeled-row label="FILE SIZE" data-id="fileSizeId">{{(artifact?.content_size | filesize : fileSizeConfigStorage) || ''}}</sm-labeled-row>
<sm-labeled-row label="HASH" data-id="hashId">{{artifact?.hash}}</sm-labeled-row> <sm-labeled-row label="HASH" data-id="hashId">{{artifact?.hash}}</sm-labeled-row>
<sm-labeled-row label="MODE" data-id="modeId">{{artifact?.mode | titlecase}}</sm-labeled-row>
<sm-labeled-row *ngFor="let data of artifact?.display_data" [label]="data[0]| uppercase">{{data[1]}}</sm-labeled-row> <sm-labeled-row *ngFor="let data of artifact?.display_data" [label]="data[0]| uppercase">{{data[1]}}</sm-labeled-row>
</div> </div>
</sm-editable-section> </sm-editable-section>
@ -32,5 +33,5 @@
[formData]="artifact?.type_data?.preview" [formData]="artifact?.type_data?.preview"
></sm-scroll-textarea> ></sm-scroll-textarea>
</sm-editable-section> </sm-editable-section>
<button *ngIf="enableDeleteButton()" class="btn btn-neon" style="margin: auto;" (click)="deleteArtifact(artifact?.key, artifact?.mode)">DELETE ARTIFACT</button>

View File

@ -1,12 +1,27 @@
import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {ChangeDetectionStrategy, Component, Input, Inject} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import {Artifact} from '~/business-logic/model/tasks/artifact'; import {Artifact} from '~/business-logic/model/tasks/artifact';
import {BaseClickableArtifactComponent} from '../base-clickable-artifact.component'; import {BaseClickableArtifactComponent} from '../base-clickable-artifact.component';
import {fileSizeConfigStorage} from '@common/shared/pipes/filesize.pipe'; import {fileSizeConfigStorage} from '@common/shared/pipes/filesize.pipe';
import { ApiTasksService } from '~/business-logic/api-services/tasks.service';
import { ConfirmDialogComponent } from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component';
import { take } from 'rxjs/operators';
import { addMessage } from '@common/core/actions/layout.actions';
import { Observable } from 'rxjs/internal/Observable';
import { IExperimentInfo } from '~/features/experiments/shared/experiment-info.model';
import { selectExperimentInfoData } from '~/features/experiments/reducers';
import { ArtifactId } from '~/business-logic/model/tasks/artifactId';
import { deleteS3Sources } from '@common/shared/entity-page/entity-delete/common-delete-dialog.actions';
import { EXPERIMENTS_STATUS_LABELS } from '~/features/experiments/shared/experiments.const';
@Component({ @Component({
selector: 'sm-experiment-artifact-item-view', selector: 'sm-experiment-artifact-item-view',
templateUrl: './experiment-artifact-item-view.component.html', templateUrl: './experiment-artifact-item-view.component.html',
styleUrls: ['./experiment-artifact-item-view.component.scss'], styleUrls: ['./experiment-artifact-item-view.component.scss'],
providers: [
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: {} }
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ExperimentArtifactItemViewComponent extends BaseClickableArtifactComponent{ export class ExperimentArtifactItemViewComponent extends BaseClickableArtifactComponent{
@ -14,11 +29,28 @@ export class ExperimentArtifactItemViewComponent extends BaseClickableArtifactCo
public isLinkable: boolean; public isLinkable: boolean;
public fileSizeConfigStorage = fileSizeConfigStorage; public fileSizeConfigStorage = fileSizeConfigStorage;
public inMemorySize: boolean; public inMemorySize: boolean;
public experimentInfo$: Observable<IExperimentInfo>;
private _artifact: Artifact; private _artifact: Artifact;
private artifactId: ArtifactId;
private _experiment: any;
@Input() editable: boolean; @Input() editable: boolean;
@Input() downloading: boolean; @Input() downloading: boolean;
constructor(
@Inject(MAT_DIALOG_DATA) public data: ConfirmDialogComponent,
public dialogRef: MatDialogRef<ConfirmDialogComponent>,
private dialog: MatDialog,
private tasksApi: ApiTasksService,
){
super();
this.experimentInfo$ = this.store.select(selectExperimentInfoData);
this.artifactId = {key: '', mode: 'output'};
this.experimentInfo$.subscribe((res) => {
this._experiment = res;
});
}
@Input() set artifact(artifact: Artifact) { @Input() set artifact(artifact: Artifact) {
this._artifact = artifact; this._artifact = artifact;
if(artifact){ if(artifact){
@ -39,6 +71,14 @@ export class ExperimentArtifactItemViewComponent extends BaseClickableArtifactCo
return this._artifact; return this._artifact;
} }
getStatusLabel() {
return EXPERIMENTS_STATUS_LABELS[this._experiment?.status] || '';
}
enableDeleteButton() {
return this.getStatusLabel() === 'Draft';
}
linkClicked(event: Event) { linkClicked(event: Event) {
this.signUrl(this.artifact.uri).subscribe(signed => { this.signUrl(this.artifact.uri).subscribe(signed => {
const a = document.createElement('a'); const a = document.createElement('a');
@ -48,4 +88,34 @@ export class ExperimentArtifactItemViewComponent extends BaseClickableArtifactCo
}); });
event.preventDefault(); event.preventDefault();
} }
deleteArtifact(key, mode) {
this.artifactId = {key: key, mode: mode};
const confirmDialogRef: MatDialogRef<any, boolean> = this.dialog.open(ConfirmDialogComponent, {
data: {
title: 'Delete Artifact',
body: 'Are you sure you want to delete artifact ' + key + ' from the experiment and S3?<br /><strong>This cannot be undone.</strong>',
yes: 'Delete',
no: 'Cancel',
iconClass: 'al-icon al-ico-trash al-color blue-300',
}
});
confirmDialogRef.afterClosed().pipe(take(1)).subscribe((confirmed) => {
if (confirmed) {
this.tasksApi.tasksDeleteArtifacts({
/* eslint-disable @typescript-eslint/naming-convention */
task: this._experiment.id,
artifacts: [this.artifactId]
/* eslint-enable @typescript-eslint/naming-convention */
}, null, 'body', true).subscribe({
next: () => {
this.store.dispatch(deleteS3Sources({files: [this.artifact.uri]}))
},
error: err => this.store.dispatch(addMessage('error', `Error ${err.error?.meta?.result_msg}`)),
complete: () => this.store.dispatch(addMessage('success', 'Artifact deleted successfully.')),
});
}
});
}
} }

View File

@ -242,7 +242,7 @@ export class BaseAdminService {
Bucket: bucketKeyEndpoint.Bucket, Bucket: bucketKeyEndpoint.Bucket,
Delete: { Delete: {
Quiet: true, Quiet: true,
Objects: files.map(file => ({Key: file} as ObjectIdentifier)) Objects: files.map(() => ({Key: bucketKeyEndpoint.Key} as ObjectIdentifier))
} }
}); });
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */

View File

@ -1,6 +1,6 @@
import { Component, Inject, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { Component, Inject, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormsModule, ReactiveFormsModule, FormControl, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { addMessage } from '@common/core/actions/layout.actions'; import { addMessage } from '@common/core/actions/layout.actions';
import { UploadArtifactDialogConfig, ArtifactType } from './upload-artifact-dialog.model'; import { UploadArtifactDialogConfig, ArtifactType } from './upload-artifact-dialog.model';
@ -15,11 +15,30 @@ import { getSignedUrl } from '@common/core/actions/common-auth.actions';
import { selectExperimentExecutionInfoData } from '@common/experiments/reducers'; import { selectExperimentExecutionInfoData } from '@common/experiments/reducers';
import { ArtifactModeEnum } from '~/business-logic/model/tasks/artifactModeEnum'; import { ArtifactModeEnum } from '~/business-logic/model/tasks/artifactModeEnum';
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
import { DialogTemplateComponent } from '@common/shared/ui-components/overlay/dialog-template/dialog-template.component';
import { CommonModule } from '@angular/common';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { SafePipe } from '@common/shared/pipes/safe.pipe';
import { MatSelectModule } from '@angular/material/select';
import { MatRadioModule } from '@angular/material/radio';
@Component({ @Component({
selector: 'sm-upload-artifact-dialog', selector: 'sm-upload-artifact-dialog',
templateUrl: './upload-artifact-dialog.component.html', templateUrl: './upload-artifact-dialog.component.html',
styleUrls: ['./upload-artifact-dialog.component.scss'] styleUrls: ['./upload-artifact-dialog.component.scss'],
standalone: true,
imports: [
SafePipe,
FormsModule,
ReactiveFormsModule,
DialogTemplateComponent,
CommonModule,
MatInputModule,
MatFormFieldModule,
MatSelectModule,
MatRadioModule,
]
}) })
export class UploadArtifactDialogComponent implements OnInit, OnDestroy { export class UploadArtifactDialogComponent implements OnInit, OnDestroy {
artifactType: ArtifactType[] = [ artifactType: ArtifactType[] = [
@ -216,7 +235,7 @@ export class UploadArtifactDialogComponent implements OnInit, OnDestroy {
type: this.uploadForm.controls['artType'].value, type: this.uploadForm.controls['artType'].value,
content_size: item.size, content_size: item.size,
timestamp: ts, timestamp: ts,
hash: 'SHA256: ' + fileHash, hash: fileHash,
uri: this.uploadUrl + item.name, uri: this.uploadUrl + item.name,
mode: this.uploadForm.controls['mode'].value as ArtifactModeEnum mode: this.uploadForm.controls['mode'].value as ArtifactModeEnum
}); });