Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions src/app/features/jobs/jobs-list/jobs-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -334,21 +334,16 @@ <h2 class="text-2xl font-semibold">All jobs</h2>
{{ job.tool || "—" }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
@if (isInProgress(job.status)) {
<span
class="inline-flex gap-2 items-center text-sm font-semibold text-gray-500"
>
<ng-icon name="heroClock" size="16"></ng-icon>
In progress...
</span>
} @else {
<span
[class]="getStatusClass(job.status)"
class="px-2 py-1 inline-flex text-xs font-semibold rounded-full"
class="px-2 py-1 inline-flex justify-center items-center text-xs font-semibold rounded-full"
>
{{ job.status }}
@if (job.status === 'In progress') {
<span
class="h-1.5 w-1.5 rounded-full mr-1.5 bg-sky-600 animate-pulse"
></span>
} {{ job.status }}
</span>
}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ job.submittedAt | date:'dd/MM/yyyy' }}
Expand Down
13 changes: 7 additions & 6 deletions src/app/features/jobs/jobs-list/jobs-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,18 +421,19 @@ describe("JobsListComponent", () => {
expect(component.getStatusClass("Completed")).toBe(
"bg-green-100 text-green-800"
);
expect(component.getStatusClass("In progress")).toBe("text-gray-700");
expect(component.getStatusClass("In progress")).toBe(
"bg-sky-100 text-sky-800"
);
expect(component.getStatusClass("In queue")).toBe(
"bg-white text-black border border-black"
"bg-gray-100 text-gray-800"
);
expect(component.getStatusClass("Failed")).toBe("bg-red-600 text-white");
expect(component.getStatusClass("Failed")).toBe("bg-red-100 text-red-800");
expect(component.getStatusClass("Stopped")).toBe(
"bg-amber-100 text-amber-700"
"bg-amber-100 text-amber-800"
);
expect(component.getStatusClass("Unknown")).toBe(
"bg-gray-100 text-gray-900"
"bg-gray-100 text-gray-800"
);
expect(component.isInProgress("In progress")).toBeTrue();
});

it("should toggle individual and all job selections", () => {
Expand Down
18 changes: 8 additions & 10 deletions src/app/features/jobs/jobs-list/jobs-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default class JobsListComponent implements OnInit, OnDestroy {
activeSort = signal<"score" | "submitted">("submitted");

// Available status options
statusOptions = ["Completed", "Failed", "Stopped", "In progress", "In queue"];
statusOptions = ["Completed", "Failed", "Stopped", "In progress", "In queue", "Pending"];

// Debounce timer for the search input
private searchDebounce?: ReturnType<typeof setTimeout>;
Expand Down Expand Up @@ -372,22 +372,20 @@ export default class JobsListComponent implements OnInit, OnDestroy {
case "Completed":
return "bg-green-100 text-green-800";
case "In progress":
return "text-gray-700";
return "bg-sky-100 text-sky-800";
case "In queue":
return "bg-white text-black border border-black";
return "bg-gray-100 text-gray-800";
case "Failed":
return "bg-red-600 text-white";
return "bg-red-100 text-red-800";
case "Stopped":
return "bg-amber-100 text-amber-700";
return "bg-amber-100 text-amber-800";
case "Pending":
return "bg-blue-600 text-white"
default:
return "bg-gray-100 text-gray-900";
return "bg-gray-100 text-gray-800";
}
}

isInProgress(status: string): boolean {
return status === "In progress";
}

/**
* Toggle selection for a single job
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ describe("SinglePredictionComponent", () => {
const datasetUploadRequest =
datasetUploadService.uploadDataset.calls.mostRecent().args[0];
const samplesheetId = datasetUploadRequest.formData["id"];
expect(samplesheetId).toMatch(/^single-prediction-[a-z0-9]{8}$/);
expect(samplesheetId).toBe("test-run");
expect(datasetUploadRequest.formData["fasta"]).toBe(
MOCK_FASTA_RESPONSE.s3Uri
);
Expand Down
21 changes: 10 additions & 11 deletions src/app/features/workflows/single-prediction/single-prediction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,17 @@ export default class SinglePredictionComponent extends WorkflowPageBase {
CCD_COMPOUNDS
).map(([code, name]) => ({ value: code, label: `${code} - ${name}` }));

private readonly samplesheetId = `single-prediction-${this.generateRandomSuffix()}`;
private nextRowId = 1;
ccdLookupState = signal<Record<number, "idle" | "valid" | "invalid">>({});
ccdLookupNames = signal<Record<number, string>>({}); // compound name resolved from the local CCD dictionary via lookupCcdCompound()
ccdLookupErrors = signal<Record<number, string>>({}); // validation error message produced by the local CCD dictionary lookup
private preparedFastaContent = signal<string | null>(null);
private preparedFastaUrl = signal<string | null>(null);
private preparedSamplesheetS3Key = signal<string | null>(null);
private preparedSamplesheetId = signal<string | null>(null);
private get samplesheetId(): string {
return this.jobName().trim();
}

readonly tools: ToolChip[] = [
{ id: "colabfold", label: "ColabFold" },
Expand Down Expand Up @@ -683,18 +686,20 @@ export default class SinglePredictionComponent extends WorkflowPageBase {
onPrepared: (fastaUrl: string, s3InputKey: string) => void
): void {
const fastaContent = this.generatedFastaContent();
const samplesheetId = this.samplesheetId;
const cachedS3InputKey = this.preparedSamplesheetS3Key();
const cachedFastaUrl = this.preparedFastaUrl();
if (
cachedS3InputKey &&
cachedFastaUrl &&
this.preparedFastaContent() === fastaContent
this.preparedFastaContent() === fastaContent &&
this.preparedSamplesheetId() === samplesheetId
) {
onPrepared(cachedFastaUrl, cachedS3InputKey);
return;
}

const fastaFile = new File([fastaContent], `${this.samplesheetId}.fasta`, {
const fastaFile = new File([fastaContent], `${samplesheetId}.fasta`, {
type: "text/plain",
});

Expand All @@ -712,7 +717,7 @@ export default class SinglePredictionComponent extends WorkflowPageBase {
}
return this.datasetUploadService
.uploadDataset({
formData: { id: this.samplesheetId, fasta: response.s3Uri },
formData: { id: samplesheetId, fasta: response.s3Uri },
})
.pipe(
map((datasetResponse) => ({
Expand All @@ -735,6 +740,7 @@ export default class SinglePredictionComponent extends WorkflowPageBase {
this.preparedFastaContent.set(fastaContent);
this.preparedFastaUrl.set(fastaUrl);
this.preparedSamplesheetS3Key.set(s3InputKey);
this.preparedSamplesheetId.set(samplesheetId);
onPrepared(fastaUrl, s3InputKey);
},
error: (error: unknown) => {
Expand All @@ -761,13 +767,6 @@ export default class SinglePredictionComponent extends WorkflowPageBase {
);
}

private generateRandomSuffix(): string {
return (
globalThis.crypto?.randomUUID?.().replace(/-/g, "") ??
Math.random().toString(36).slice(2)
).slice(0, 8);
}

private buildWorkflowPayload(): Omit<
SinglePredictionPayload,
"fastaFileUrl" | "sample_id"
Expand Down
Loading