diff --git a/packages/libs/multi-blast/src/lib/components/BlastWorkspaceResult.tsx b/packages/libs/multi-blast/src/lib/components/BlastWorkspaceResult.tsx
index 51ff0903de..fd363a609f 100644
--- a/packages/libs/multi-blast/src/lib/components/BlastWorkspaceResult.tsx
+++ b/packages/libs/multi-blast/src/lib/components/BlastWorkspaceResult.tsx
@@ -9,8 +9,8 @@ import {
} from '@veupathdb/wdk-client/lib/Components';
import WorkspaceNavigation from '@veupathdb/wdk-client/lib/Components/Workspace/WorkspaceNavigation';
import { NotFoundController } from '@veupathdb/wdk-client/lib/Controllers';
-import { usePromise } from '@veupathdb/wdk-client/lib/Hooks/PromiseHook';
import { useSetDocumentTitle } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
+import { Task } from '@veupathdb/wdk-client/lib/Utils/Task';
import { useBlastApi } from '../hooks/api';
import {
@@ -24,13 +24,9 @@ import {
ApiResultError,
ApiResultSuccess,
ErrorDetails,
- IoBlastFormat,
- LongJobResponse,
- LongReportResponse,
+ multiQueryReportJson,
MultiQueryReportJson,
- Target,
} from '../utils/ServiceTypes';
-import { BlastApi } from '../utils/api';
import { fetchOrganismToFilenameMaps } from '../utils/organisms';
import { reportToParamValues } from '../utils/params';
import { TargetMetadataByDataType } from '../utils/targetTypes';
@@ -40,6 +36,13 @@ import { BlastRequestError } from './BlastRequestError';
import { ResultContainer } from './ResultContainer';
import './BlastWorkspaceResult.scss';
+import { BlastReportClient } from '../utils/api/BlastReportClient';
+import { IOBlastOutFormat } from '../utils/api/report/blast/blast-config-format';
+import { IOReportJobDetails } from '../utils/api/report/types/ep-job-by-id';
+import { BlastQueryClient } from '../utils/api/BlastQueryClient';
+import { IOQueryJobDetails } from '../utils/api/query/types/ep-jobs-by-id';
+import { IOJobTarget } from '../utils/api/query/types/common';
+import { ioTransformer } from '@veupathdb/http-utils';
interface Props {
jobId: string;
@@ -54,109 +57,165 @@ export function BlastWorkspaceResult(props: Props) {
const blastApi = useBlastApi();
- const queryResult = usePromise(
- () => blastApi.fetchQuery(props.jobId),
- [blastApi, props.jobId]
- );
+ const [queryResultState, setQueryResultState] = useState<
+ ApiResult
+ >();
- const jobResult = usePromise(
- () => makeJobPollingPromise(blastApi, props.jobId),
+ useEffect(
+ () =>
+ Task.fromPromise(() => blastApi.queryAPI.fetchJobQuery(props.jobId)).run(
+ setQueryResultState
+ ),
[blastApi, props.jobId]
);
- const reportResult = usePromise(
- async () =>
- jobResult.value?.status !== 'job-completed'
- ? undefined
- : makeReportPollingPromise(blastApi, props.jobId, 'single-file-json'),
- [blastApi, jobResult.value?.status]
- );
+ const [jobResultState, setJobResultState] = useState({
+ status: 'job-pending',
+ jobId: props.jobId,
+ });
- const multiQueryReportResult = usePromise(
- async () =>
- reportResult.value?.status !== 'report-completed'
- ? undefined
- : blastApi.fetchSingleFileJsonReport(
- reportResult.value.report.reportID
- ),
- [blastApi, reportResult.value]
- );
+ useEffect(() => {
+ if (
+ jobResultState.status !== 'job-pending' &&
+ jobResultState.jobId === props.jobId
+ ) {
+ return;
+ }
- const individualQueriesResult = usePromise(async () => {
- if (jobResult.value?.status !== 'job-completed') {
- return undefined;
+ return Task.fromPromise(() =>
+ makeQueryJobPollingPromise(blastApi.queryAPI, props.jobId)
+ ).run(setJobResultState);
+ }, [blastApi, props.jobId, jobResultState]);
+
+ const [
+ reportResultState,
+ setReportResultState,
+ ] = useState({
+ status: 'report-pending',
+ jobId: props.jobId,
+ });
+
+ useEffect(() => {
+ if (
+ jobResultState.status !== 'job-completed' ||
+ (reportResultState.status !== 'report-pending' &&
+ reportResultState.jobId === props.jobId)
+ ) {
+ return;
}
- const childJobIds = jobResult.value.job?.childJobs?.map(({ id }) => id);
-
- const subJobIds =
- childJobIds == null || childJobIds.length === 0
- ? [jobResult.value.job.id]
- : childJobIds;
-
- const queryResults = await Promise.all(
- subJobIds.map((id) =>
- blastApi.fetchQuery(id).then((queryResult) =>
- queryResult.status === 'error'
- ? queryResult
- : {
- status: 'ok',
- value: {
- jobId: id,
- query: queryResult.value,
- },
- }
- )
+ return Task.fromPromise(() =>
+ makeReportPollingPromise(
+ blastApi.reportAPI,
+ props.jobId,
+ 'single-file-blast-json'
)
- );
-
- const invalidQueryResult = queryResults.find(
- ({ status }) => status === 'error'
- );
-
- return invalidQueryResult != null
- ? (invalidQueryResult as ApiResultError)
- : ({
- status: 'ok',
- value: (
- queryResults as {
- status: 'ok';
- value: IndividualQuery;
- }[]
- ).map((queryResult) => queryResult.value),
- } as ApiResultSuccess);
- }, [jobResult.value]);
-
- return jobResult.value != null &&
- jobResult.value.status === 'request-error' ? (
-
- ) : jobResult.value != null && jobResult.value.status === 'error' ? (
-
- ) : jobResult.value != null && jobResult.value.status === 'queueing-error' ? (
-
- ) : queryResult.value != null && queryResult.value.status === 'error' ? (
-
- ) : reportResult.value != null &&
- reportResult.value.status === 'request-error' ? (
-
- ) : reportResult.value != null &&
- reportResult.value.status === 'queueing-error' ? (
+ ).run(setReportResultState);
+ }, [blastApi, props.jobId, jobResultState, reportResultState]);
+
+ const [multiQueryReportState, setMultiQueryReportState] = useState<
+ ApiResult
+ >();
+
+ useEffect(
+ () =>
+ Task.fromPromise(async () =>
+ reportResultState?.status !== 'report-completed'
+ ? undefined
+ : blastApi.reportAPI.fetchJobFileAs(
+ reportResultState.report.reportJobID,
+ 'report.json',
+ (res) =>
+ ioTransformer(multiQueryReportJson)(JSON.parse(res as any))
+ )
+ ).run(setMultiQueryReportState),
+ [blastApi, reportResultState]
+ );
+
+ const [
+ individualQueriesResultState,
+ setIndividualQueriesResultState,
+ ] = useState>();
+
+ useEffect(
+ () =>
+ Task.fromPromise(async () => {
+ if (jobResultState.status !== 'job-completed') {
+ return undefined;
+ }
+
+ const childJobIds = jobResultState.job?.subJobs;
+ const subJobIds =
+ childJobIds == null || childJobIds.length === 0
+ ? [jobResultState.job.queryJobID]
+ : childJobIds;
+
+ const queryResults = await Promise.all(
+ subJobIds.map((id) =>
+ blastApi.queryAPI.fetchJobQuery(id).then((queryResult) =>
+ queryResult.status === 'error'
+ ? queryResult
+ : {
+ status: 'ok',
+ value: {
+ jobId: id,
+ query: queryResult.value,
+ },
+ }
+ )
+ )
+ );
+
+ const invalidQueryResult = queryResults.find(
+ ({ status }) => status === 'error'
+ );
+
+ return invalidQueryResult != null
+ ? (invalidQueryResult as ApiResultError)
+ : ({
+ status: 'ok',
+ value: (queryResults as {
+ status: 'ok';
+ value: IndividualQuery;
+ }[]).map((queryResult) => queryResult.value),
+ } as ApiResultSuccess);
+ }).run(setIndividualQueriesResultState),
+ [blastApi, jobResultState]
+ );
+
+ return jobResultState.status === 'request-error' ? (
+
+ ) : jobResultState.status === 'queueing-error' ? (
+
+ {jobResultState.errorMessage ?? 'We were unable to queue your job.'}
+
+ }
+ />
+ ) : queryResultState != null && queryResultState.status === 'error' ? (
+
+ ) : reportResultState != null &&
+ reportResultState.status === 'request-error' ? (
+
+ ) : reportResultState != null &&
+ reportResultState.status === 'queueing-error' ? (
- ) : individualQueriesResult.value != null &&
- individualQueriesResult.value.status === 'error' ? (
-
- ) : queryResult.value == null ||
- jobResult.value == null ||
- reportResult.value == null ||
- individualQueriesResult.value == null ? (
+ ) : individualQueriesResultState != null &&
+ individualQueriesResultState.status === 'error' ? (
+
+ ) : queryResultState == null ||
+ jobResultState.status === 'job-pending' ||
+ reportResultState.status === 'report-pending' ||
+ individualQueriesResultState == null ? (
) : (
);
}
@@ -186,37 +245,14 @@ function LoadingBlastResult(props: Props) {
);
}
-function BlastRerunError(props: Props) {
- return (
-
-
BLAST Job - error
-
- Job Id: {props.jobId}
-
-
-
- Status: error
-
-
- We were unable to rerun your BLAST job due to a server error.{' '}
-
- Contact us
- {' '}
- for more information.
-
-
-
- );
-}
-
-export interface MultiQueryReportResult {
- value?: ApiResult;
- loading: boolean;
-}
+export type MultiQueryReportResult = ApiResult<
+ MultiQueryReportJson,
+ ErrorDetails
+>;
interface CompleteBlastResultProps extends Props {
individualQueries: IndividualQuery[];
- jobDetails: LongJobResponse;
+ jobDetails: IOQueryJobDetails;
multiQueryReportResult?: MultiQueryReportResult;
query: string;
}
@@ -228,10 +264,11 @@ function CompleteBlastResult(props: CompleteBlastResultProps) {
const queryCount = props.individualQueries.length;
- const targets = props.jobDetails.targets;
+ const targets = props.jobDetails.jobConfig.targets;
- const { targetTypeTerm, wdkRecordType } =
- useTargetTypeTermAndWdkRecordType(targets);
+ const { targetTypeTerm, wdkRecordType } = useTargetTypeTermAndWdkRecordType(
+ targets
+ );
const organismToFilenameMapsResult = useBlastCompatibleWdkService(
async (wdkService) =>
@@ -279,8 +316,8 @@ function CompleteBlastResult(props: CompleteBlastResultProps) {
interface BlastSummaryProps {
filesToOrganisms: Record;
- jobDetails: LongJobResponse;
- targets: Target[];
+ jobDetails: IOQueryJobDetails;
+ targets: IOJobTarget[];
multiQueryReportResult?: MultiQueryReportResult;
query: string;
individualQueries: IndividualQuery[];
@@ -303,14 +340,16 @@ function BlastSummary({
const queryCount = individualQueries.length;
const databases = useMemo(
- () => targets.map(({ target }) => target),
+ () => targets.map(({ targetDisplayName: target }) => target),
[targets]
);
const databasesStr = useMemo(() => databases.join(', '), [databases]);
- const { hitTypeDisplayName, hitTypeDisplayNamePlural } =
- useHitTypeDisplayNames(wdkRecordType, targetTypeTerm);
+ const {
+ hitTypeDisplayName,
+ hitTypeDisplayNamePlural,
+ } = useHitTypeDisplayNames(wdkRecordType, targetTypeTerm);
const multiQueryParamValues = useMemo(
() =>
@@ -324,10 +363,12 @@ function BlastSummary({
[targets, filesToOrganisms, jobDetails, targetTypeTerm, query]
);
- const [lastSelectedIndividualResult, setLastSelectedIndividualResult] =
- useState(
- selectedResult.type === 'combined' ? 1 : selectedResult.resultIndex
- );
+ const [
+ lastSelectedIndividualResult,
+ setLastSelectedIndividualResult,
+ ] = useState(
+ selectedResult.type === 'combined' ? 1 : selectedResult.resultIndex
+ );
useEffect(() => {
if (selectedResult.type === 'individual') {
@@ -347,7 +388,7 @@ function BlastSummary({
Job Id:
- {jobDetails.id}
+ {jobDetails.queryJobID}
{multiQueryParamValues && (
)}
- {jobDetails.description != null && (
+ {jobDetails.userMeta?.summary != null && (
Description:
- {jobDetails.description}
+ {jobDetails.userMeta.summary}
)}
Program:
- {jobDetails.config.tool === 'tblastx' ||
- jobDetails.config.task == null
- ? jobDetails.config.tool
- : jobDetails.config.task}
+ {
+ /* Try and show the specific task that was selected, if possible.
+ If not possible, fall back to just showing the tool.
+ (Big "or" block as Most blastConfig types don't have a 'task'
+ property) */
+ jobDetails.blastConfig.tool === 'deltablast' ||
+ jobDetails.blastConfig.tool === 'psiblast' ||
+ jobDetails.blastConfig.tool === 'rpsblast' ||
+ jobDetails.blastConfig.tool === 'rpstblastn' ||
+ jobDetails.blastConfig.tool === 'tblastx' ||
+ jobDetails.blastConfig.task == null
+ ? jobDetails.blastConfig.tool
+ : jobDetails.blastConfig.task
+ }
Target Type:
{hitTypeDisplayName}
@@ -414,7 +465,7 @@ function BlastSummary({
{queryCount > 1 && (
> {
+): Promise {
const jobRequest = await blastApi.fetchJob(jobId);
if (jobRequest.status === 'ok') {
const job = jobRequest.value;
- if (job.status === 'completed' || job.status === 'errored') {
+ if (job.status === 'complete') {
return {
- status: job.status === 'completed' ? 'job-completed' : 'queueing-error',
+ status: 'job-completed',
+ jobId,
job,
};
}
+ if (job.status === 'failed') {
+ const queueingErrorMessageRequest = await blastApi.fetchJobStdErr(
+ job.queryJobID
+ );
+
+ return {
+ status: 'queueing-error',
+ jobId,
+ job,
+ errorMessage:
+ queueingErrorMessageRequest.status === 'ok'
+ ? queueingErrorMessageRequest.value
+ : undefined,
+ };
+ }
+
if (job.status === 'expired') {
- const apiResult = await blastApi.rerunJob(job.id);
- if (apiResult.status === 'error') {
- return apiResult;
- }
+ await blastApi.rerunJob(job.queryJobID);
}
await waitForNextPoll();
- return makeJobPollingPromise(blastApi, jobId);
+ return {
+ status: 'job-pending',
+ jobId: job.queryJobID,
+ };
} else {
return {
...jobRequest,
+ jobId,
status: 'request-error',
};
}
}
-type ReportPollingResult = ReportPollingSuccess | ReportPollingError;
+export type ReportJobPollingState =
+ | ReportJobPollingInProgress
+ | ReportJobPollingSuccess
+ | ReportJobPollingError;
+
+interface ReportPollingBase {
+ jobId: string;
+ reportId?: string;
+}
+
+interface ReportJobPollingInProgress extends ReportPollingBase {
+ status: 'report-pending';
+}
-interface ReportPollingSuccess {
+interface ReportJobPollingSuccess extends ReportPollingBase {
status: 'report-completed' | 'queueing-error';
- report: LongReportResponse;
+ report: IOReportJobDetails;
}
-interface ReportPollingError {
+interface ReportJobPollingError extends ReportPollingBase {
status: 'request-error';
details: ErrorDetails;
}
export async function makeReportPollingPromise(
- blastApi: BlastApi,
- jobId: string,
- format: IoBlastFormat,
+ blastApi: BlastReportClient,
+ queryJobID: string,
+ format: IOBlastOutFormat,
reportId?: string
-): Promise {
+): Promise {
if (reportId == null) {
- const reportRequest = await blastApi.createReport(jobId, {
- format,
+ const reportRequest = await blastApi.createJob({
+ queryJobID,
+ blastConfig: { formatType: format },
+ addToUserCollection: true,
});
if (reportRequest.status === 'ok') {
return makeReportPollingPromise(
blastApi,
- jobId,
- 'single-file-json',
- reportRequest.value.reportID
+ queryJobID,
+ format,
+ reportRequest.value.reportJobID
);
} else {
return {
...reportRequest,
+ jobId: queryJobID,
status: 'request-error',
};
}
}
- const reportRequest = await blastApi.fetchReport(reportId);
+ const reportRequest = await blastApi.fetchJob(reportId);
if (reportRequest.status === 'ok') {
const report = reportRequest.value;
- if (report.status === 'completed' || report.status === 'errored') {
+ if (report.status === 'complete' || report.status === 'failed') {
return {
status:
- report.status === 'completed' ? 'report-completed' : 'queueing-error',
+ report.status === 'complete' ? 'report-completed' : 'queueing-error',
+ jobId: queryJobID,
report,
};
}
if (report.status === 'expired') {
- await blastApi.rerunReport(report.reportID);
+ await blastApi.rerunJob(report.reportJobID);
}
await waitForNextPoll();
- return makeReportPollingPromise(
- blastApi,
- jobId,
- 'single-file-json',
- report.reportID
- );
+ return {
+ status: 'report-pending',
+ jobId: queryJobID,
+ reportId,
+ };
} else {
return {
...reportRequest,
+ jobId: queryJobID,
+ reportId,
status: 'request-error',
};
}
diff --git a/packages/libs/multi-blast/src/lib/components/ReportSelect.tsx b/packages/libs/multi-blast/src/lib/components/ReportSelect.tsx
index 908da6a19f..71731da0ed 100644
--- a/packages/libs/multi-blast/src/lib/components/ReportSelect.tsx
+++ b/packages/libs/multi-blast/src/lib/components/ReportSelect.tsx
@@ -1,12 +1,22 @@
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import Select, { ActionMeta, OptionsType, ValueType } from 'react-select';
+import { useNonNullableContext } from '@veupathdb/wdk-client/lib/Hooks/NonNullableContext';
+import { WdkDependenciesContext } from '@veupathdb/wdk-client/lib/Hooks/WdkDependenciesEffect';
+import { Task } from '@veupathdb/wdk-client/lib/Utils/Task';
+
import { Props as CombinedResultProps } from '../components/CombinedResult';
-import { useDownloadReportCallback } from '../hooks/api';
-import { IoBlastFormat } from '../utils/ServiceTypes';
+import { BlastServiceUrl, useBlastApi } from '../hooks/api';
+import { downloadJobContent } from '../utils/api';
+
+import {
+ ReportJobPollingState,
+ makeReportPollingPromise,
+} from './BlastWorkspaceResult';
import './ReportSelect.scss';
+import { IOBlastOutFormat } from '../utils/api/report/blast/blast-config-format';
interface Props {
combinedResultTableDownloadConfig?: CombinedResultProps['downloadTableOptions'];
@@ -17,7 +27,7 @@ interface Props {
interface ReportOption {
value:
| 'combined-result-table'
- | { format: IoBlastFormat; shouldZip: boolean };
+ | { format: IOBlastOutFormat; shouldZip: boolean };
label: string;
}
@@ -31,7 +41,7 @@ const baseReportOptions: ReportOption[] = [
label: 'XML',
},
{
- value: { format: 'archive-asn-1', shouldZip: false },
+ value: { format: 'asn1', shouldZip: false },
label: 'ASN.1',
},
{
@@ -47,19 +57,19 @@ const baseReportOptions: ReportOption[] = [
label: 'Hit Table (csv)',
},
{
- value: { format: 'multi-file-xml2', shouldZip: true },
+ value: { format: 'multi-file-blast-xml2', shouldZip: true },
label: 'Multiple-file XML2',
},
{
- value: { format: 'single-file-xml2', shouldZip: false },
+ value: { format: 'single-file-blast-xml2', shouldZip: false },
label: 'Single-file XML2',
},
{
- value: { format: 'multi-file-json', shouldZip: true },
+ value: { format: 'multi-file-blast-json', shouldZip: true },
label: 'Multiple-file JSON',
},
{
- value: { format: 'single-file-json', shouldZip: false },
+ value: { format: 'single-file-blast-json', shouldZip: false },
label: 'Single-file JSON',
},
];
@@ -69,8 +79,21 @@ export function ReportSelect({
jobId,
placeholder,
}: Props) {
+ const { wdkService } = useNonNullableContext(WdkDependenciesContext);
+ const blastServiceUrl = useContext(BlastServiceUrl);
+ const blastApi = useBlastApi();
+
const [selectedReportOption, setSelectedReportOption] =
useState(undefined);
+ const [reportState, setReportState] = useState({
+ status: 'report-pending',
+ jobId,
+ });
+
+ const resetSelectedReport = useCallback(() => {
+ setSelectedReportOption(undefined);
+ setReportState({ status: 'report-pending', jobId });
+ }, [jobId]);
const onChangeReport = useCallback(
(
@@ -84,42 +107,63 @@ export function ReportSelect({
[]
);
- const downloadReportCallback = useDownloadReportCallback(jobId);
+ useEffect(() => {
+ if (
+ selectedReportOption == null ||
+ selectedReportOption.value === 'combined-result-table' ||
+ (reportState.status !== 'report-pending' && reportState.jobId === jobId)
+ ) {
+ return;
+ }
+
+ const format = selectedReportOption.value.format;
+
+ return Task.fromPromise(() =>
+ makeReportPollingPromise(blastApi.reportAPI, jobId, format)
+ ).run(setReportState);
+ }, [blastApi, jobId, selectedReportOption, reportState]);
useEffect(() => {
- let canceled = false;
-
- (async () => {
- if (downloadReportCallback != null && selectedReportOption != null) {
- try {
- if (selectedReportOption.value === 'combined-result-table') {
- if (combinedResultTableDownloadConfig?.offer) {
- await combinedResultTableDownloadConfig.onClickDownloadTable();
- }
- } else {
- await downloadReportCallback(
- selectedReportOption.value.format,
- selectedReportOption.value.shouldZip
- );
- }
- } finally {
- if (!canceled) {
- setSelectedReportOption(undefined);
- }
- }
- }
- })();
+ if (
+ selectedReportOption == null ||
+ selectedReportOption.value === 'combined-result-table' ||
+ reportState.status === 'report-pending'
+ ) {
+ return;
+ }
+
+ const { shouldZip } = selectedReportOption.value;
- return () => {
- canceled = true;
- };
+ return Task.fromPromise(async () =>
+ downloadJobContent(blastApi.reportAPI, reportState, shouldZip)
+ ).run(resetSelectedReport, resetSelectedReport);
}, [
- combinedResultTableDownloadConfig,
- downloadReportCallback,
+ blastServiceUrl,
+ wdkService,
+ resetSelectedReport,
+ reportState,
jobId,
selectedReportOption,
]);
+ useEffect(() => {
+ if (
+ selectedReportOption?.value !== 'combined-result-table' ||
+ combinedResultTableDownloadConfig?.offer !== true
+ ) {
+ return;
+ }
+
+ return Task.fromPromise(async () =>
+ combinedResultTableDownloadConfig.onClickDownloadTable()
+ ).run(resetSelectedReport, resetSelectedReport);
+ }, [
+ resetSelectedReport,
+ jobId,
+ combinedResultTableDownloadConfig,
+ selectedReportOption,
+ ]);
+
const options = useMemo(
() =>
(!combinedResultTableDownloadConfig?.offer
diff --git a/packages/libs/multi-blast/src/lib/components/ResultContainer.tsx b/packages/libs/multi-blast/src/lib/components/ResultContainer.tsx
index 97198e1312..fa13dfc07c 100644
--- a/packages/libs/multi-blast/src/lib/components/ResultContainer.tsx
+++ b/packages/libs/multi-blast/src/lib/components/ResultContainer.tsx
@@ -40,9 +40,9 @@ export function ResultContainer(props: Props) {
const individualResultProps = useIndividualResultProps({
...props,
combinedResult:
- props.multiQueryReportResult?.value?.status !== 'ok'
+ props.multiQueryReportResult?.status !== 'ok'
? undefined
- : props.multiQueryReportResult.value.value,
+ : props.multiQueryReportResult.value,
});
return (
@@ -67,7 +67,6 @@ function CombinedResultContainer(
}
) {
return props.multiQueryReportResult == null ||
- props.multiQueryReportResult.value == null ||
props.projectUrls == null ||
props.organismToProject == null ? (
@@ -76,7 +75,7 @@ function CombinedResultContainer(
) : (
diff --git a/packages/libs/multi-blast/src/lib/hooks/allJobs.tsx b/packages/libs/multi-blast/src/lib/hooks/allJobs.tsx
index 7881a2be5b..4ab308de9d 100644
--- a/packages/libs/multi-blast/src/lib/hooks/allJobs.tsx
+++ b/packages/libs/multi-blast/src/lib/hooks/allJobs.tsx
@@ -11,11 +11,8 @@ import { useWdkService } from '@veupathdb/wdk-client/lib/Hooks/WdkServiceHook';
import { JobRow } from '../components/BlastWorkspaceAll';
import { ApiResult, ErrorDetails } from '../utils/ServiceTypes';
-import {
- entityStatusToReadableStatus,
- shouldIncludeInJobsTable,
-} from '../utils/allJobs';
-import { BlastApi } from '../utils/api';
+import { entityStatusToReadableStatus } from '../utils/allJobs';
+import { BlastQueryClient } from '../utils/api/BlastQueryClient';
export function useAllJobsColumns(): MesaColumn[] {
return useMemo(
@@ -29,9 +26,9 @@ export function useAllJobsColumns(): MesaColumn[] {
sortable: true,
},
{
- key: 'description',
- name: 'Description',
- renderCell: ({ row }: { row: JobRow }) => row.description ?? 'Untitled',
+ key: 'summary',
+ name: 'Summary',
+ renderCell: ({ row }: { row: JobRow }) => row.summary ?? 'Untitled',
sortable: true,
},
{
@@ -63,29 +60,27 @@ export function useAllJobsColumns(): MesaColumn[] {
}
export function useRawJobRows(
- blastApi: BlastApi
+ blastApi: BlastQueryClient
): ApiResult | undefined {
return useWdkService(async (wdkService) => {
- const jobEntities = await blastApi.fetchJobEntities();
const { projectId } = await wdkService.getConfig();
+ const jobEntities = await blastApi.listJobs(projectId);
- return jobEntities == null
- ? undefined
- : jobEntities.status === 'error'
- ? jobEntities
- : {
- status: 'ok',
- value: jobEntities.value
- .filter((jobEntity) =>
- shouldIncludeInJobsTable(jobEntity, projectId)
- )
- .map((jobEntity) => ({
- jobId: jobEntity.id,
- description: jobEntity.description ?? null,
- created: jobEntity.created,
+ if (jobEntities == null) {
+ return undefined;
+ } else {
+ return jobEntities.status === 'error'
+ ? jobEntities
+ : {
+ status: 'ok',
+ value: jobEntities.value.map((jobEntity) => ({
+ jobId: jobEntity.queryJobID,
+ summary: jobEntity.userMeta?.summary ?? null,
+ created: jobEntity.createdOn,
status: entityStatusToReadableStatus(jobEntity.status),
})),
- };
+ };
+ }
}, []);
}
diff --git a/packages/libs/multi-blast/src/lib/hooks/api.ts b/packages/libs/multi-blast/src/lib/hooks/api.ts
index 4fdc9b91c5..1b26bfcaf8 100644
--- a/packages/libs/multi-blast/src/lib/hooks/api.ts
+++ b/packages/libs/multi-blast/src/lib/hooks/api.ts
@@ -1,4 +1,4 @@
-import { createContext, useContext, useMemo } from 'react';
+import { createContext, useContext } from 'react';
import { Dispatch } from 'redux';
import { useDispatch } from 'react-redux';
@@ -8,13 +8,13 @@ import { once } from 'lodash';
import { WdkDependenciesContext } from '@veupathdb/wdk-client/lib/Hooks/WdkDependenciesEffect';
import { useNonNullableContext } from '@veupathdb/wdk-client/lib/Hooks/NonNullableContext';
-import { BlastApi, createJobContentDownloader } from '../utils/api';
import {
BlastCompatibleWdkService,
isBlastCompatibleWdkService,
} from '../utils/wdkServiceIntegration';
+import { BlastAPIClient } from '../utils/api/BlastAPIClient';
-const BlastServiceUrl = createContext('/multi-blast');
+export const BlastServiceUrl = createContext('/multi-blast');
export function useBlastApi() {
const blastServiceUrl = useContext(BlastServiceUrl);
@@ -29,7 +29,7 @@ export function useBlastApi() {
const reportError = makeErrorReporter(wdkDependencies.wdkService, dispatch);
- return BlastApi.getBlastClient(
+ return BlastAPIClient.create(
blastServiceUrl,
wdkDependencies.wdkService,
reportError
@@ -51,10 +51,3 @@ const makeErrorReporter = once(function (
// dispatch(notifyUnhandledError(error));
};
});
-
-export function useDownloadReportCallback(jobId: string) {
- const blastApi = useBlastApi();
- return useMemo(() => {
- return createJobContentDownloader(blastApi, jobId);
- }, [blastApi, jobId]);
-}
diff --git a/packages/libs/multi-blast/src/lib/hooks/blastAlgorithms.tsx b/packages/libs/multi-blast/src/lib/hooks/blastAlgorithms.tsx
index d74e69b473..34c2c192dd 100644
--- a/packages/libs/multi-blast/src/lib/hooks/blastAlgorithms.tsx
+++ b/packages/libs/multi-blast/src/lib/hooks/blastAlgorithms.tsx
@@ -27,7 +27,7 @@ export function useEnabledAlgorithms(
const algorithmTermsByDatabase = useAlgorithmTermsByDatabase();
const targetMetadataByDataType = useContext(TargetMetadataByDataType);
- const enabledAlgorithms = useMemo(() => {
+ return useMemo(() => {
if (algorithmTermsByDatabase == null) {
return undefined;
}
@@ -54,12 +54,10 @@ export function useEnabledAlgorithms(
enabledAlgorithmsForWdkRecordType,
};
}, [algorithmTermsByDatabase, targetDataType, targetMetadataByDataType]);
-
- return enabledAlgorithms;
}
function useAlgorithmTermsByDatabase() {
- const algorithmTermsByDatabase = useWdkService(async (wdkService) => {
+ return useWdkService(async (wdkService) => {
const [projectId, recordClasses] = await Promise.all([
wdkService.getConfig().then(({ projectId }) => projectId),
wdkService.getRecordClasses(),
@@ -85,7 +83,7 @@ function useAlgorithmTermsByDatabase() {
const databaseRecords = await Promise.all(recordPromises);
- const result = zip(blastOntologyDatabases, databaseRecords).reduce(
+ return zip(blastOntologyDatabases, databaseRecords).reduce(
(memo, [databaseName, record]) => ({
...memo,
[databaseName as BlastOntologyDatabase]: recordToTerms(
@@ -95,11 +93,7 @@ function useAlgorithmTermsByDatabase() {
}),
{} as Record
);
-
- return result;
}, []);
-
- return algorithmTermsByDatabase;
}
function recordToTerms(
diff --git a/packages/libs/multi-blast/src/lib/hooks/combinedResults.tsx b/packages/libs/multi-blast/src/lib/hooks/combinedResults.tsx
index f54ecf4a1d..716699be2a 100644
--- a/packages/libs/multi-blast/src/lib/hooks/combinedResults.tsx
+++ b/packages/libs/multi-blast/src/lib/hooks/combinedResults.tsx
@@ -29,7 +29,6 @@ import {
ApiResult,
ErrorDetails,
MultiQueryReportJson,
- Target,
} from '../utils/ServiceTypes';
import {
TargetMetadataByDataType,
@@ -56,6 +55,7 @@ import {
mergeIntervals,
orderHitsBySignificance,
} from '../utils/combinedResults';
+import { IOJobTarget } from '../utils/api/query/types/common';
const MAX_ROWS = 5000;
@@ -625,11 +625,12 @@ function useMesaOptions(sortedRows: Either) {
);
}
-export function useTargetTypeTermAndWdkRecordType(targets: Target[]) {
+export function useTargetTypeTermAndWdkRecordType(targets: IOJobTarget[]) {
const targetMetadataByDataType = useContext(TargetMetadataByDataType);
return useMemo(() => {
- const { organism: sampleOrganism, target: sampleDbName } = targets[0];
+ const { targetDisplayName: sampleOrganism, targetFile: sampleDbName } =
+ targets[0];
const targetDbName = sampleDbName.replace(sampleOrganism, '');
const targetTypeTerm = dbNameToTargetTypeTerm(targetDbName);
diff --git a/packages/libs/multi-blast/src/lib/hooks/individualResult.tsx b/packages/libs/multi-blast/src/lib/hooks/individualResult.tsx
index 1d2a1350d6..4eacbcfe99 100644
--- a/packages/libs/multi-blast/src/lib/hooks/individualResult.tsx
+++ b/packages/libs/multi-blast/src/lib/hooks/individualResult.tsx
@@ -22,7 +22,7 @@ import {
import { Props as ResultContainerProps } from '../components/ResultContainer';
import { IndividualQuery } from '../utils/CommonTypes';
import { MultiQueryReportJson } from '../utils/ServiceTypes';
-import { BLAST_QUERY_SEQUENCE_PARAM_NAME } from '../utils/params';
+import { ParamNames } from '../utils/params';
export type AnswerSpecResultTypeConfig =
| { status: 'loading' }
@@ -107,7 +107,7 @@ export function useIndividualResultProps({
...baseAnswerSpec.value.searchConfig,
parameters: {
...baseAnswerSpec.value.searchConfig.parameters,
- [BLAST_QUERY_SEQUENCE_PARAM_NAME]: querySequence.query,
+ [ParamNames.BlastQuerySequence]: querySequence.query,
},
},
},
@@ -276,8 +276,8 @@ function useIndividualQuerySequence(
individualQueries: IndividualQuery[],
resultIndex: number
) {
- return useMemo(
- () => individualQueries[resultIndex - 1],
- [individualQueries, resultIndex]
- );
+ return useMemo(() => individualQueries[resultIndex - 1], [
+ individualQueries,
+ resultIndex,
+ ]);
}
diff --git a/packages/libs/multi-blast/src/lib/hooks/params.ts b/packages/libs/multi-blast/src/lib/hooks/params.ts
index 2801a7a93c..2fb2655c26 100644
--- a/packages/libs/multi-blast/src/lib/hooks/params.ts
+++ b/packages/libs/multi-blast/src/lib/hooks/params.ts
@@ -18,12 +18,7 @@ import {
import { useChangeParamValue } from '@veupathdb/wdk-client/lib/Views/Question/Params/Utils';
import { Props } from '../components/BlastForm';
-import {
- ADVANCED_PARAMS_GROUP_NAME,
- BLAST_ALGORITHM_PARAM_NAME,
- BLAST_DATABASE_TYPE_PARAM_NAME,
- BLAST_QUERY_SEQUENCE_PARAM_NAME,
-} from '../utils/params';
+import { ADVANCED_PARAMS_GROUP_NAME, ParamNames } from '../utils/params';
import {
EnabledAlgorithms,
TargetMetadataByDataType,
@@ -38,10 +33,10 @@ export function useTargetParamProps(
// FIXME: Validate this
const parameter = state.question.parametersByName[
- BLAST_DATABASE_TYPE_PARAM_NAME
+ ParamNames.BlastDatabaseType
] as CheckBoxEnumParam;
- const selectedType = state.paramValues[BLAST_DATABASE_TYPE_PARAM_NAME];
+ const selectedType = state.paramValues[ParamNames.BlastDatabaseType];
const items = useMemo(
() =>
@@ -77,9 +72,9 @@ export function useAlgorithmParamProps(
) {
// FIXME: Validate this
const parameter = state.question.parametersByName[
- BLAST_ALGORITHM_PARAM_NAME
+ ParamNames.BlastAlgorithm
] as CheckBoxEnumParam;
- const algorithm = state.paramValues[BLAST_ALGORITHM_PARAM_NAME];
+ const algorithm = state.paramValues[ParamNames.BlastAlgorithm];
const enabledAlgorithmsForTargetType =
enabledAlgorithms?.enabledAlgorithmsForTargetType;
@@ -136,12 +131,12 @@ export function useSequenceParamProps(
updateParamValue: Props['eventHandlers']['updateParamValue']
) {
const parameter =
- state.question.parametersByName[BLAST_QUERY_SEQUENCE_PARAM_NAME];
+ state.question.parametersByName[ParamNames.BlastQuerySequence];
const onChange = useChangeParamValue(parameter, state, updateParamValue);
return {
- value: state.paramValues[BLAST_QUERY_SEQUENCE_PARAM_NAME],
+ value: state.paramValues[ParamNames.BlastQuerySequence],
onChange,
required: true,
cols: 80,
@@ -157,13 +152,12 @@ interface DefaultAdvancedParamsMetadata {
export function useDefaultAdvancedParams(
question: QuestionWithMappedParameters
): Record | undefined {
- const defaultAlgorithmDependentParams = useWdkService(
+ return useWdkService(
async (wdkService) => {
// FIXME: Validate this
const algorithmParameter = question.parametersByName[
- BLAST_ALGORITHM_PARAM_NAME
+ ParamNames.BlastAlgorithm
] as CheckBoxEnumParam;
-
const algorithms = algorithmParameter.vocabulary.map(([value]) => value);
return fetchDefaultAlgorithmDependentParamsOnce(
@@ -174,8 +168,6 @@ export function useDefaultAdvancedParams(
},
[question]
);
-
- return defaultAlgorithmDependentParams;
}
async function fetchDefaultAlgorithmAdvancedParams(
@@ -194,7 +186,7 @@ async function fetchDefaultAlgorithmAdvancedParams(
const dependentAdvancedParamPromises = algorithms.map((algorithm) =>
wdkService.getRefreshedDependentParams(
searchName,
- BLAST_ALGORITHM_PARAM_NAME,
+ ParamNames.BlastAlgorithm,
algorithm,
{}
)
@@ -216,17 +208,16 @@ async function fetchDefaultAlgorithmAdvancedParams(
dependentAdvancedParamsByName[advancedParam.name] ?? advancedParam
);
- const defaultAdvancedParamValues =
- defaultAdvancedParamsForAlgorithm.reduce(
- (memo, { initialDisplayValue, name }) => {
- if (initialDisplayValue != null) {
- memo[name] = initialDisplayValue;
- }
-
- return memo;
- },
- {} as ParameterValues
- );
+ const defaultAdvancedParamValues = defaultAdvancedParamsForAlgorithm.reduce(
+ (memo, { initialDisplayValue, name }) => {
+ if (initialDisplayValue != null) {
+ memo[name] = initialDisplayValue;
+ }
+
+ return memo;
+ },
+ {} as ParameterValues
+ );
return {
defaultParams: defaultAdvancedParamsForAlgorithm,
diff --git a/packages/libs/multi-blast/src/lib/utils/ServiceTypes.ts b/packages/libs/multi-blast/src/lib/utils/ServiceTypes.ts
index 07c5a1000d..e74ed58231 100644
--- a/packages/libs/multi-blast/src/lib/utils/ServiceTypes.ts
+++ b/packages/libs/multi-blast/src/lib/utils/ServiceTypes.ts
@@ -1,7 +1,6 @@
import {
TypeOf,
array,
- boolean,
intersection,
literal,
number,
@@ -10,633 +9,16 @@ import {
string,
type,
union,
+ unknown,
} from 'io-ts';
-export const ioBlastCompBasedStats = union([
- literal('none'),
- literal('comp-based-stats'),
- literal('conditional-comp-based-score-adjustment'),
- literal('unconditional-comp-based-score-adjustment'),
-]);
-
-export type IoBlastCompBasedStats = TypeOf;
-
-export const ioBlastSegMask = union([
- literal('yes'),
- literal('no'),
- type({
- window: number,
- locut: number,
- hicut: number,
- }),
-]);
-
-export type IoBlastSegMask = TypeOf;
-
-export const ioBlastStrand = union([
- literal('plus'),
- literal('minus'),
- literal('both'),
-]);
-
-export type IoBlastStrand = TypeOf;
-
-export const ioHitSorting = union([
- literal('by-eval'),
- literal('by-bit-score'),
- literal('by-total-score'),
- literal('by-percent-identity'),
- literal('by-query-coverage'),
-]);
-
-export type IoHitSorting = TypeOf;
-
-export const ioHspSorting = union([
- literal('by-hsp-evalue'),
- literal('by-hsp-score'),
- literal('by-hsp-query-start'),
- literal('by-hsp-percent-identity'),
- literal('by-hsp-subject-start'),
-]);
-
-export type IoHspSorting = TypeOf;
-
-export const ioBlastReportField = union([
- literal('bitscore'),
- literal('btop'),
- literal('evalue'),
- literal('frames'),
- literal('gapopen'),
- literal('gaps'),
- literal('length'),
- literal('mismatch'),
- literal('nident'),
- literal('pident'),
- literal('positive'),
- literal('ppos'),
- literal('qacc'),
- literal('qaccver'),
- literal('qcovhsp'),
- literal('qcovs'),
- literal('qcovus'),
- literal('qend'),
- literal('qframe'),
- literal('qgi'),
- literal('qlen'),
- literal('qseq'),
- literal('qseqid'),
- literal('qstart'),
- literal('sacc'),
- literal('saccver'),
- literal('sallacc'),
- literal('sallgi'),
- literal('sallseqid'),
- literal('salltitles'),
- literal('sblastname'),
- literal('sblastnames'),
- literal('scomname'),
- literal('scomnames'),
- literal('score'),
- literal('send'),
- literal('sframe'),
- literal('sgi'),
- literal('slen'),
- literal('sq'),
- literal('sr'),
- literal('ssciname'),
- literal('sscinames'),
- literal('sseq'),
- literal('sseqid'),
- literal('sskingdom'),
- literal('sskingdoms'),
- literal('sstart'),
- literal('sstrand'),
- literal('staxid'),
- literal('staxids'),
- literal('std'),
- literal('stitle'),
-]);
-
-export type IoBlastReportField = TypeOf;
-
-export const ioBlastFormat = union([
- literal('pairwise'),
- literal('query-anchored-with-identities'),
- literal('query-anchored-without-identities'),
- literal('flat-query-anchored-with-identities'),
- literal('flat-query-anchored-without-identities'),
- literal('xml'),
- literal('tabular'),
- literal('tabular-with-comments'),
- literal('text-asn-1'),
- literal('binary-asn-1'),
- literal('csv'),
- literal('archive-asn-1'),
- literal('seqalign-json'),
- literal('multi-file-json'),
- literal('multi-file-xml2'),
- literal('single-file-json'),
- literal('single-file-xml2'),
- literal('sam'),
- literal('organism-report'),
-]);
-
-export type IoBlastFormat = TypeOf;
-
-export const ioBlastReportFormat = partial({
- format: ioBlastFormat,
- delim: string,
- fields: array(ioBlastReportField),
-});
-
-export type IoBlastReportFormat = TypeOf;
-
-export const ioBlastLocation = type({
- start: number,
- end: number,
-});
-
-export type IoBlastLocation = TypeOf;
-
-export const ioJobStatus = union([
- literal('queued'),
- literal('in-progress'),
- literal('completed'),
- literal('errored'),
- literal('expired'),
-]);
-
-export type IoJobStatus = TypeOf;
-
-export const ioBlastPScoringMatrix = union([
- literal('BLOSUM45'),
- literal('BLOSUM50'),
- literal('BLOSUM62'),
- literal('BLOSUM80'),
- literal('BLOSUM90'),
- literal('PAM30'),
- literal('PAM70'),
- literal('PAM250'),
- literal('IDENTITY'),
-]);
-
-export type IOBlastPScoringMatrix = TypeOf;
-
-export const ioBlastXScoringMatrix = union([
- literal('BLOSUM45'),
- literal('BLOSUM50'),
- literal('BLOSUM62'),
- literal('BLOSUM80'),
- literal('BLOSUM90'),
- literal('PAM30'),
- literal('PAM70'),
- literal('PAM250'),
-]);
-
-export type IOBlastXScoringMatrix = TypeOf;
-
-export const ioTBlastNScoringMatrix = union([
- literal('BLOSUM45'),
- literal('BLOSUM50'),
- literal('BLOSUM62'),
- literal('BLOSUM80'),
- literal('BLOSUM90'),
- literal('PAM30'),
- literal('PAM70'),
- literal('PAM250'),
- literal('IDENTITY'),
-]);
-
-export type IOTBlastNScoringMatrix = TypeOf;
-
-export const ioTBlastXScoringMatrix = union([
- literal('BLOSUM45'),
- literal('BLOSUM50'),
- literal('BLOSUM62'),
- literal('BLOSUM80'),
- literal('BLOSUM90'),
- literal('PAM30'),
- literal('PAM70'),
- literal('PAM250'),
-]);
-
-export type IOTBlastXScoringMatrix = TypeOf;
-
-export const ioBlastNTask = union([
- literal('megablast'),
- literal('dc-megablast'),
- literal('blastn'),
- literal('blastn-short'),
-]);
-
-export type IoBlastNTask = TypeOf;
-
-export const ioBlastPTask = union([
- literal('blastp'),
- literal('blastp-short'),
- literal('blastp-fast'),
-]);
-
-export type IoBlastPTask = TypeOf;
-
-export const ioBlastXTask = union([literal('blastx'), literal('blastx-fast')]);
-
-export type IoBlastXTask = TypeOf;
-
-export const ioTBlastNTask = union([
- literal('tblastn'),
- literal('tblastn-fast'),
-]);
-
-export type IoTBlastNTask = TypeOf;
-
-export const ioBlastNDust = union([
- literal('yes'),
- literal('no'),
- type({
- level: number,
- window: number,
- linker: number,
- }),
-]);
-
-export type IoBlastNDust = TypeOf;
-
-export const ioBlastNDcTemplateType = union([
- literal('coding'),
- literal('optimal'),
- literal('both'),
-]);
-
-export type IoBlastNDcTemplateType = TypeOf;
-
-export const ioBlastNConfig = intersection([
- type({
- tool: literal('blastn'),
- }),
- partial({
- query: string,
- queryLoc: ioBlastLocation,
- eValue: string,
- outFormat: ioBlastReportFormat,
- numDescriptions: number,
- numAlignments: number,
- lineLength: number,
- sortHits: ioHitSorting,
- sortHSPs: ioHspSorting,
- lcaseMasking: boolean,
- qCovHSPPerc: number,
- maxHSPs: number,
- maxTargetSeqs: number,
- dbSize: number,
- searchSpace: number,
- xDropUngap: number,
- parseDefLines: boolean,
- strand: ioBlastStrand,
- task: ioBlastNTask,
- wordSize: number,
- gapOpen: number,
- gapExtend: number,
- penalty: number,
- reward: number,
- useIndex: boolean,
- indexName: string,
- dust: ioBlastNDust,
- windowMaskerTaxid: number,
- softMasking: boolean,
- taxIds: array(number),
- negativeTaxIds: array(number),
- dbSoftMask: string,
- dbHardMask: string,
- percIdentity: number,
- cullingLimit: number,
- bestHitOverhang: number,
- bestHitScoreEdge: number,
- subjectBestHit: boolean,
- templateType: ioBlastNDcTemplateType,
- templateLength: number,
- sumStats: boolean,
- xDropGap: number,
- xDropGapFinal: number,
- noGreedy: boolean,
- minRawGappedScore: number,
- ungapped: boolean,
- windowSize: number,
- offDiagonalRange: number,
- }),
-]);
-
-export type IoBlastNConfig = TypeOf;
-
-export const ioBlastPConfig = intersection([
- type({
- tool: literal('blastp'),
- }),
- partial({
- query: string,
- queryLoc: ioBlastLocation,
- eValue: string,
- outFormat: ioBlastReportFormat,
- numDescriptions: number,
- numAlignments: number,
- lineLength: number,
- sortHits: ioHitSorting,
- sortHSPs: ioHspSorting,
- lcaseMasking: boolean,
- qCovHSPPerc: number,
- maxHSPs: number,
- maxTargetSeqs: number,
- dbSize: number,
- searchSpace: number,
- xDropUngap: number,
- parseDefLines: boolean,
- task: ioBlastPTask,
- wordSize: number,
- gapOpen: number,
- gapExtend: number,
- matrix: ioBlastPScoringMatrix,
- threshold: number,
- compBasedStats: ioBlastCompBasedStats,
- seg: ioBlastSegMask,
- softMasking: boolean,
- taxIds: array(number),
- negativeTaxIds: array(number),
- dbSoftMask: string,
- dbHardMask: string,
- cullingLimit: number,
- bestHitOverhang: number,
- bestHitScoreEdge: number,
- subjectBestHit: boolean,
- xDropGap: number,
- xDropGapFinal: number,
- windowSize: number,
- ungapped: boolean,
- useSWTraceback: boolean,
- }),
-]);
-
-export type IoBlastPConfig = TypeOf;
-
-export const ioBlastXConfig = intersection([
- type({
- tool: literal('blastx'),
- queryGeneticCode: number,
- }),
- partial({
- query: string,
- queryLoc: ioBlastLocation,
- eValue: string,
- outFormat: ioBlastReportFormat,
- numDescriptions: number,
- numAlignments: number,
- lineLength: number,
- sortHits: ioHitSorting,
- sortHSPs: ioHspSorting,
- lcaseMasking: boolean,
- qCovHSPPerc: number,
- maxHSPs: number,
- maxTargetSeqs: number,
- dbSize: number,
- searchSpace: number,
- xDropUngap: number,
- parseDefLines: boolean,
- strand: ioBlastStrand,
- task: ioBlastXTask,
- wordSize: number,
- gapOpen: number,
- gapExtend: number,
- maxIntronLength: number,
- matrix: ioBlastXScoringMatrix,
- threshold: number,
- compBasedStats: ioBlastCompBasedStats,
- seg: ioBlastSegMask,
- softMasking: boolean,
- taxIds: array(number),
- negativeTaxIds: array(number),
- dbSoftMask: string,
- dbHardMask: string,
- cullingLimit: number,
- bestHitOverhang: number,
- bestHitScoreEdge: number,
- subjectBestHit: boolean,
- sumStats: boolean,
- xDropGap: number,
- xDropGapFinal: number,
- windowSize: number,
- ungapped: boolean,
- useSWTraceback: boolean,
- }),
-]);
-
-export type IoBlastXConfig = TypeOf;
-
-export const ioTBlastNConfig = intersection([
- type({
- tool: literal('tblastn'),
- }),
- partial({
- query: string,
- queryLoc: ioBlastLocation,
- eValue: string,
- outFormat: ioBlastReportFormat,
- numDescriptions: number,
- numAlignments: number,
- lineLength: number,
- sortHits: ioHitSorting,
- sortHSPs: ioHspSorting,
- lcaseMasking: boolean,
- qCovHSPPerc: number,
- maxHSPs: number,
- maxTargetSeqs: number,
- dbSize: number,
- searchSpace: number,
- xDropUngap: number,
- parseDefLines: boolean,
- task: ioTBlastNTask,
- wordSize: number,
- gapOpen: number,
- gapExtend: number,
- dbGenCode: number,
- maxIntronLength: number,
- matrix: ioTBlastNScoringMatrix,
- threshold: number,
- compBasedStats: ioBlastCompBasedStats,
- seg: ioBlastSegMask,
- softMasking: boolean,
- taxIds: array(number),
- negativeTaxIds: array(number),
- dbSoftMask: string,
- dbHardMask: string,
- cullingLimit: number,
- bestHitOverhang: number,
- bestHitScoreEdge: number,
- subjectBestHit: boolean,
- sumStats: boolean,
- xDropGap: number,
- xDropGapFinal: number,
- ungapped: boolean,
- windowSize: number,
- useSWTraceback: boolean,
- }),
-]);
-
-export type IoTBlastNConfig = TypeOf;
-
-export const ioTBlastXConfig = intersection([
- type({
- tool: literal('tblastx'),
- queryGeneticCode: number,
- }),
- partial({
- query: string,
- queryLoc: ioBlastLocation,
- eValue: string,
- outFormat: ioBlastReportFormat,
- numDescriptions: number,
- numAlignments: number,
- lineLength: number,
- sortHits: ioHitSorting,
- sortHSPs: ioHspSorting,
- lcaseMasking: boolean,
- qCovHSPPerc: number,
- maxHSPs: number,
- maxTargetSeqs: number,
- dbSize: number,
- searchSpace: number,
- xDropUngap: number,
- parseDefLines: boolean,
- strand: ioBlastStrand,
- wordSize: number,
- maxIntronLength: number,
- matrix: ioTBlastXScoringMatrix,
- threshold: number,
- dbGencode: number,
- seg: ioBlastSegMask,
- softMasking: boolean,
- taxIds: array(number),
- negativeTaxIds: array(number),
- dbSoftMask: string,
- dbHardMask: string,
- cullingLimit: number,
- bestHitOverhang: number,
- bestHitScoreEdge: number,
- subjectBestHit: boolean,
- sumStats: boolean,
- windowSize: number,
- }),
-]);
-
-export type IoTBlastXConfig = TypeOf;
-
-export const ioBlastConfig = union([
- ioBlastNConfig,
- ioBlastPConfig,
- ioBlastXConfig,
- ioTBlastNConfig,
- ioTBlastXConfig,
-]);
-
-export type IoBlastConfig = TypeOf;
-
export const target = type({
organism: string,
target: string,
});
-export type Target = TypeOf;
-
-export const shortJobResponse = intersection([
- type({
- id: string,
- status: ioJobStatus,
- created: string,
- site: string,
- targets: array(target),
- // FIXME: This field no longer appears in the response. Service bug?
- // expires: string,
- }),
- partial({
- description: string,
- // FIXME: This field is missing from "secondary" jobs. Service bug?
- isPrimary: boolean,
- childJobs: array(
- type({
- id: string,
- index: number,
- })
- ),
- parentJobs: array(
- type({
- id: string,
- index: number,
- })
- ),
- }),
-]);
-
-export type ShortJobResponse = TypeOf;
-
-export const longJobResponse = intersection([
- shortJobResponse,
- type({
- config: ioBlastConfig,
- }),
-]);
-
-export type LongJobResponse = TypeOf;
-
-export const createJobResponse = type({
- jobId: string,
-});
-
-export type CreateJobResponse = TypeOf;
-
-export const reportConfig = partial({
- format: ioBlastFormat,
- fieldDelim: string,
- fields: array(ioBlastReportField),
- numDescriptions: number,
- numAlignments: number,
- lineLength: number,
- sortHits: ioHitSorting,
- sortHSPs: ioHspSorting,
- maxTargetSeqs: number,
- parseDefLines: boolean,
-});
-
-export type ReportConfig = TypeOf;
-
-export const shortReportResponse = intersection([
- type({
- jobID: string,
- reportID: string,
- config: reportConfig,
- status: ioJobStatus,
- }),
- partial({
- description: string,
- }),
-]);
-
-export type ShortReportResponse = TypeOf;
-
-export const longReportResponse = intersection([
- shortReportResponse,
- partial({
- files: array(string),
- }),
-]);
-
-export type LongReportResponse = TypeOf;
-
-export const createReportResponse = shortReportResponse;
-
-export type CreateReportReponse = ShortJobResponse;
-
export const reportStrand = union([literal('Plus'), literal('Minus')]);
-export type ReportStrand = TypeOf;
-
export const reportDescriptionJson = type({
id: string,
accession: string,
@@ -666,8 +48,6 @@ export const reportHspJson = intersection([
}),
]);
-export type ReportHspJson = TypeOf;
-
export const reportHitJson = type({
num: number,
description: array(reportDescriptionJson),
@@ -675,20 +55,16 @@ export const reportHitJson = type({
hsps: array(reportHspJson),
});
-export type ReportHitJson = TypeOf;
-
export const reportStatJson = type({
- db_num: number,
db_len: number,
- hsp_len: number,
+ db_num: number,
eff_space: number,
+ entropy: number,
+ hsp_len: number,
kappa: number,
lambda: number,
- entropy: number,
});
-export type ReportStatJson = TypeOf;
-
export const reportSearchJson = intersection([
type({
query_id: string,
@@ -702,21 +78,16 @@ export const reportSearchJson = intersection([
}),
]);
-export type ReportSearchJson = TypeOf;
-
export const reportResultsJson = type({
search: reportSearchJson,
});
-export type ReportResultsJson = TypeOf;
-
export const reportSearchTargetJson = type({
db: string,
});
-export type ReportSearchTargetJson = TypeOf;
-
export const singleQueryReportJson = type({
+ params: unknown,
program: string,
version: string,
reference: string,
@@ -724,8 +95,6 @@ export const singleQueryReportJson = type({
results: reportResultsJson,
});
-export type SingleQueryReportJson = TypeOf;
-
export const multiQueryReportJson = type({
BlastOutput2: array(
type({
@@ -741,43 +110,31 @@ export const badRequestError = type({
message: string,
});
-export type BadRequestError = TypeOf;
-
export const tooLargeError = type({
status: literal('too-large'),
message: string,
});
-export type TooLargeError = TypeOf;
-
export const unauthorizedError = type({
status: literal('unauthorized'),
message: string,
});
-export type UnauthorizedError = TypeOf;
-
export const forbiddenError = type({
status: literal('forbidden'),
message: string,
});
-export type ForbiddenError = TypeOf;
-
export const notFoundError = type({
status: literal('not-found'),
message: string,
});
-export type NotFoundError = TypeOf;
-
export const methodNotAllowedError = type({
status: literal('bad-method'),
message: string,
});
-export type MethodNotAllowedError = TypeOf;
-
export const inputErrors = type({
general: array(string),
byKey: record(string, array(string)),
@@ -790,23 +147,17 @@ export const unprocessableEntityError = type({
errors: inputErrors,
});
-export type UnprocessableEntityError = TypeOf;
-
export const serverError = type({
status: literal('server-error'),
message: string,
requestId: string,
});
-export type ServerError = TypeOf;
-
export const unknownError = type({
status: literal('unknown'),
message: string,
});
-export type UnknownError = TypeOf;
-
export const errorDetails = union([
badRequestError,
tooLargeError,
diff --git a/packages/libs/multi-blast/src/lib/utils/allJobs.ts b/packages/libs/multi-blast/src/lib/utils/allJobs.ts
index d20638ff8c..ba0a2cc124 100644
--- a/packages/libs/multi-blast/src/lib/utils/allJobs.ts
+++ b/packages/libs/multi-blast/src/lib/utils/allJobs.ts
@@ -1,28 +1,22 @@
import { JobRow } from '../components/BlastWorkspaceAll';
-import { ShortJobResponse } from './ServiceTypes';
-
-export function shouldIncludeInJobsTable(
- jobEntity: ShortJobResponse,
- projectId: string
-) {
- return isJobPrimary(jobEntity) && isJobFromSite(jobEntity, projectId);
-}
-
-function isJobPrimary(jobEntity: ShortJobResponse) {
- return jobEntity.isPrimary;
-}
-
-function isJobFromSite(jobEntity: ShortJobResponse, projectId: string) {
- return jobEntity.site === projectId;
-}
+import { IOJobStatus } from './api/query/types/common';
export function entityStatusToReadableStatus(
- entityStatus: ShortJobResponse['status']
+ entityStatus: IOJobStatus
): JobRow['status'] {
- return entityStatus === 'completed'
- ? 'finished'
- : entityStatus === 'in-progress'
- ? 'running'
- : entityStatus;
+ switch (entityStatus) {
+ case 'queued':
+ return 'queued';
+ case 'in-progress':
+ return 'running';
+ case 'complete':
+ return 'finished';
+ case 'failed':
+ return 'errored';
+ case 'expired':
+ return 'expired';
+ default:
+ throw new Error(`Unexpected job status value: ${entityStatus}.`);
+ }
}
diff --git a/packages/libs/multi-blast/src/lib/utils/api.ts b/packages/libs/multi-blast/src/lib/utils/api.ts
index e6157df5f6..742392c0af 100644
--- a/packages/libs/multi-blast/src/lib/utils/api.ts
+++ b/packages/libs/multi-blast/src/lib/utils/api.ts
@@ -1,303 +1,56 @@
-import { isLeft, map } from 'fp-ts/Either';
-import { array, string } from 'io-ts';
-import { identity, memoize, omit } from 'lodash';
+import { ReportJobPollingState } from '../components/BlastWorkspaceResult';
+import { BlastReportClient } from './api/BlastReportClient';
-import {
- ApiRequest,
- FetchApiOptions,
- FetchClientWithCredentials,
- createJsonRequest,
- ioTransformer,
-} from '@veupathdb/http-utils';
-
-import { makeReportPollingPromise } from '../components/BlastWorkspaceResult';
-
-import {
- ApiResult,
- ErrorDetails,
- IoBlastConfig,
- IoBlastFormat,
- ReportConfig,
- createJobResponse,
- createReportResponse,
- errorDetails,
- longJobResponse,
- longReportResponse,
- multiQueryReportJson,
- shortJobResponse,
- shortReportResponse,
-} from './ServiceTypes';
-import { BlastCompatibleWdkService } from './wdkServiceIntegration';
-import { submitAsForm } from '@veupathdb/wdk-client/lib/Utils/FormSubmitter';
-
-const JOBS_PATH = '/jobs';
-const REPORTS_PATH = '/reports';
-
-export class BlastApi extends FetchClientWithCredentials {
- public static getBlastClient = memoize(
- (
- baseUrl: string,
- wdkService: BlastCompatibleWdkService,
- reportError: (error: any) => void
- ) => {
- return new BlastApi({ baseUrl }, wdkService, reportError);
- }
- );
-
- constructor(
- options: FetchApiOptions,
- protected readonly wdkService: BlastCompatibleWdkService,
- protected reportError: (error: any) => void
- ) {
- super(options, wdkService);
- }
-
- async taggedFetch(
- apiRequest: ApiRequest
- ): Promise> {
- try {
- return {
- status: 'ok',
- value: await super.fetch(apiRequest),
- };
- } catch (error: any) {
- if (
- typeof error === 'object' &&
- error != null &&
- typeof error.message === 'string'
- ) {
- try {
- const errorDetailsJson = JSON.parse(
- error.message.replace(/^[^{]*(\{.*\})[^}]*$/, '$1')
- );
-
- const decodedErrorDetails = map(transformTooLargeError)(
- errorDetails.decode(errorDetailsJson)
- );
-
- if (
- isLeft(decodedErrorDetails) ||
- (decodedErrorDetails.right.status !== 'invalid-input' &&
- decodedErrorDetails.right.status !== 'too-large')
- ) {
- this.reportError(error);
- }
-
- return isLeft(decodedErrorDetails)
- ? {
- status: 'error',
- details: {
- status: 'unknown',
- message: error.message,
- },
- }
- : {
- status: 'error',
- details: decodedErrorDetails.right,
- };
- } catch {
- this.reportError(error);
-
- return {
- status: 'error',
- details: {
- status: 'unknown',
- message: error.message,
- },
- };
- }
- } else {
- throw error;
- }
- }
- }
-
- fetchJobEntities() {
- return this.taggedFetch({
- path: JOBS_PATH,
- method: 'GET',
- transformResponse: ioTransformer(array(shortJobResponse)),
- });
- }
-
- createJob(
- site: string,
- targets: { organism: string; target: string }[],
- query: string | File,
- config: IoBlastConfig,
- maxResultSize: number = 0,
- description?: string
- ) {
- const requestProperties = {
- site,
- targets,
- config:
- query instanceof File
- ? omit(config, 'query')
- : {
- ...config,
- query,
- },
- maxResultSize,
- description,
- };
-
- if (query instanceof File) {
- const requestBody = new FormData();
-
- requestBody.append('properties', JSON.stringify(requestProperties));
-
- requestBody.append('query', query);
-
- return this.taggedFetch({
- path: JOBS_PATH,
- method: 'POST',
- body: requestBody,
- transformResponse: ioTransformer(createJobResponse),
- });
- } else {
- return this.taggedFetch(
- createJsonRequest({
- path: JOBS_PATH,
- method: 'POST',
- body: requestProperties,
- transformResponse: ioTransformer(createJobResponse),
- })
- );
- }
- }
-
- fetchJob(jobId: string) {
- return this.taggedFetch({
- path: `${JOBS_PATH}/${jobId}`,
- method: 'GET',
- transformResponse: ioTransformer(longJobResponse),
- });
- }
-
- rerunJob(jobId: string) {
- return this.taggedFetch({
- path: `${JOBS_PATH}/${jobId}`,
- method: 'POST',
- transformResponse: identity,
- });
+// FIXME: Update FetchClientWithCredentials to accommodate responses
+// with "attachment" Content-Disposition
+export async function downloadJobContent(
+ reportAPI: BlastReportClient,
+ reportResponse: ReportJobPollingState,
+ shouldZip: boolean
+): Promise {
+ if (reportResponse.status === 'report-pending') {
+ throw new Error('Tried to download a report which has not yet finished.');
}
- fetchReportEntities() {
- return this.taggedFetch({
- path: REPORTS_PATH,
- method: 'GET',
- transformResponse: ioTransformer(array(shortReportResponse)),
- });
+ if (reportResponse.status === 'queueing-error') {
+ throw new Error('We were unable to queue your report.');
}
- createReport(jobId: string, reportConfig: ReportConfig) {
- return this.taggedFetch(
- createJsonRequest({
- path: REPORTS_PATH,
- method: 'POST',
- body: {
- jobID: jobId,
- ...reportConfig,
- },
- transformResponse: ioTransformer(createReportResponse),
- })
+ if (reportResponse.status === 'request-error') {
+ throw new Error(
+ `An error occurred while trying to create your report: ${JSON.stringify(
+ reportResponse.details
+ )}`
);
}
- fetchReport(reportId: string) {
- return this.taggedFetch({
- path: `${REPORTS_PATH}/${reportId}`,
- method: 'GET',
- transformResponse: ioTransformer(longReportResponse),
- });
- }
-
- rerunReport(reportId: string) {
- return this.taggedFetch({
- path: `${REPORTS_PATH}/${reportId}`,
- method: 'POST',
- transformResponse: identity,
- });
- }
-
- fetchSingleFileJsonReport(
- reportId: string,
- maxSize: number = 10 * 10 ** 6 // 10 MB
- ) {
- return this.taggedFetch({
- path: `${REPORTS_PATH}/${reportId}/files/report.json?download=false`,
- headers: {
- 'Content-Max-Length': `${maxSize}`,
- },
- method: 'GET',
- transformResponse: ioTransformer(multiQueryReportJson),
- });
- }
+ const reportJobID = reportResponse.report.reportJobID;
+ const filesResponse = await reportAPI.listJobFiles(
+ reportResponse.report.reportJobID
+ );
- fetchQuery(jobId: string) {
- return this.taggedFetch({
- path: `${JOBS_PATH}/${jobId}/query?download=false`,
- method: 'GET',
- transformResponse: ioTransformer(string),
- });
+ if (filesResponse.status === 'error') {
+ throw new Error(
+ 'An error occurred while attempting to fetch the results of your report: ' +
+ JSON.stringify(filesResponse.details)
+ );
}
- async downloadJobContent(
- jobId: string,
- format: IoBlastFormat,
- shouldZip: boolean
- ) {
- const reportResponse = await makeReportPollingPromise(this, jobId, format);
+ const nonZippedReportFiles = filesResponse.value
+ .filter((entry) => !entry.name.endsWith('.zip'))
+ .map((entry) => entry.name);
- if (reportResponse.status === 'queueing-error') {
- throw new Error('We were unable to queue your report.');
- }
+ const reportFile =
+ shouldZip || nonZippedReportFiles[0] == null
+ ? 'report.zip'
+ : nonZippedReportFiles[0];
- if (reportResponse.status === 'request-error') {
- throw new Error(
- `An error occurred while trying to create your report: ${JSON.stringify(
- reportResponse.details
- )}`
- );
- }
-
- const { reportID, files = [] } = reportResponse.report;
-
- const nonZippedReportFiles = files.filter(
- (file) => file !== 'meta.json' && !file.endsWith('.zip')
- );
-
- const reportFile =
- shouldZip || nonZippedReportFiles[0] == null
- ? 'report.zip'
- : nonZippedReportFiles[0];
+ const downloadResponse = await reportAPI.downloadJobFile(
+ reportJobID,
+ reportFile
+ );
- submitAsForm({
- action: `${
- this.baseUrl
- }/reports/${reportID}/files/${reportFile}?${await this.findAuthorizationQueryString()}`,
- method: 'GET',
- });
+ if (downloadResponse.status !== 'ok') {
+ throw new Error('An error occurred while trying to download your report.');
}
}
-
-function transformTooLargeError(errorDetails: ErrorDetails): ErrorDetails {
- return errorDetails.status === 'too-large' ||
- (errorDetails.status === 'bad-request' &&
- errorDetails.message ===
- 'Requested report is larger than the specified max content size.')
- ? { ...errorDetails, status: 'too-large' }
- : errorDetails;
-}
-
-// FIXME: Update createRequestHandler to accommodate responses
-// with "attachment" Content-Disposition
-export function createJobContentDownloader(blastApi: BlastApi, jobId: string) {
- return async function downloadJobContent(
- format: IoBlastFormat,
- shouldZip: boolean
- ) {
- blastApi.downloadJobContent(jobId, format, shouldZip);
- };
-}
diff --git a/packages/libs/multi-blast/src/lib/utils/api/BlastAPIClient.ts b/packages/libs/multi-blast/src/lib/utils/api/BlastAPIClient.ts
new file mode 100644
index 0000000000..ce7eece4b1
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/BlastAPIClient.ts
@@ -0,0 +1,30 @@
+import { memoize } from 'lodash';
+import { BlastCompatibleWdkService } from '../wdkServiceIntegration';
+import { FetchApiOptions } from '@veupathdb/http-utils';
+import { BlastQueryClient } from './BlastQueryClient';
+import { BlastReportClient } from './BlastReportClient';
+
+export class BlastAPIClient {
+ public static create = memoize(
+ (
+ baseUrl: string,
+ wdkService: BlastCompatibleWdkService,
+ reportError: (error: any) => void
+ ) => {
+ return new BlastAPIClient({ baseUrl }, wdkService, reportError);
+ }
+ );
+
+ public readonly queryAPI: BlastQueryClient;
+
+ public readonly reportAPI: BlastReportClient;
+
+ constructor(
+ options: FetchApiOptions,
+ wdkService: BlastCompatibleWdkService,
+ reportError: (error: any) => void
+ ) {
+ this.queryAPI = new BlastQueryClient(options, wdkService, reportError);
+ this.reportAPI = new BlastReportClient(options, wdkService, reportError);
+ }
+}
diff --git a/packages/libs/multi-blast/src/lib/utils/api/BlastQueryClient.ts b/packages/libs/multi-blast/src/lib/utils/api/BlastQueryClient.ts
new file mode 100644
index 0000000000..03b490f262
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/BlastQueryClient.ts
@@ -0,0 +1,241 @@
+import {
+ createJsonRequest,
+ FetchApiOptions,
+ ioTransformer,
+} from '@veupathdb/http-utils';
+import { BlastCompatibleWdkService } from '../wdkServiceIntegration';
+import { BlastSubClient } from './BlastSubClient';
+import { array, string } from 'io-ts';
+import { identity } from 'lodash';
+import {
+ IOQueryJobCreateRequest,
+ ioQueryJobCreateResponse,
+ ioQueryJobListEntry,
+} from './query/types/ep-jobs';
+import {
+ ioQueryJobDetails,
+ IOQueryJobPatchRequest,
+} from './query/types/ep-jobs-by-id';
+import { ioBulkStatusResponse } from './query/types/ep-statuses';
+import { ioBlastTargetIndex } from './query/types/ep-targets';
+import { IOGuestJobTransferRequest } from './query/types/ep-link-guest';
+
+// // //
+//
+// Query Service Paths
+//
+// // //
+
+const QUERY_SVC_PATH_BASE = '/query';
+const QUERY_SVC_JOBS_PATH = `${QUERY_SVC_PATH_BASE}/jobs`;
+const QUERY_SVC_STATUSES_PATH = `${QUERY_SVC_PATH_BASE}/statuses`;
+const QUERY_SVC_TARGETS_PATH = `${QUERY_SVC_PATH_BASE}/targets`;
+const QUERY_SVC_LINK_GUEST_PATH = `${QUERY_SVC_PATH_BASE}/link-guest`;
+
+const newQueryJobsPath = (site?: string) =>
+ site === undefined
+ ? QUERY_SVC_JOBS_PATH
+ : `${QUERY_SVC_JOBS_PATH}?site=${site}`;
+
+const newQueryJobPath = (jobID: string, saveJob?: boolean) =>
+ saveJob === undefined
+ ? `${QUERY_SVC_JOBS_PATH}/${jobID}`
+ : `${QUERY_SVC_JOBS_PATH}/${jobID}?save_job=${saveJob}`;
+
+const newQueryJobErrorPath = (jobID: string, download: boolean = false) =>
+ `${QUERY_SVC_JOBS_PATH}/${jobID}/stderr?download=${download}`;
+
+const newQueryJobQueryPath = (jobID: string, download: boolean) =>
+ `${QUERY_SVC_JOBS_PATH}/${jobID}/query?download=${download}`;
+
+const newQueryJobResultPath = (jobID: string, download: boolean = false) =>
+ `${QUERY_SVC_JOBS_PATH}/${jobID}/result?download=${download}`;
+
+// // //
+//
+// Service Client Implementation
+//
+// // //
+
+/**
+ * Multi-Blast Query Service REST API Client
+ *
+ * Provides functions for interacting with the Multi-Blast query service API.
+ */
+export class BlastQueryClient extends BlastSubClient {
+ constructor(
+ options: FetchApiOptions,
+ wdkService: BlastCompatibleWdkService,
+ reportError: (error: any) => void
+ ) {
+ super(options, wdkService, reportError);
+ }
+
+ // /jobs
+
+ /**
+ * Creates a new Multi-Blast query job.
+ *
+ * @param request Details about the job to create.
+ *
+ * @param query Query text or file.
+ *
+ * @return An API response of either an error or the job creation result.
+ */
+ createJob(request: IOQueryJobCreateRequest, query: string | File) {
+ const reqBody = new FormData();
+ reqBody.append('config', JSON.stringify(request));
+ reqBody.append('query', query);
+
+ return this.taggedFetch({
+ path: newQueryJobsPath(),
+ method: 'POST',
+ body: reqBody,
+ transformResponse: ioTransformer(ioQueryJobCreateResponse),
+ });
+ }
+
+ /**
+ * Lists the jobs linked to the current client user.
+ *
+ * Optionally the list of jobs may be filtered by the target site.
+ *
+ * @param site Optional site name to filter the results by.
+ *
+ * @return An API response of either an error or the job list result.
+ */
+ listJobs(site?: string) {
+ return this.taggedFetch({
+ path: newQueryJobsPath(site),
+ method: 'GET',
+ transformResponse: ioTransformer(array(ioQueryJobListEntry)),
+ });
+ }
+
+ // /jobs/{job-id}
+
+ fetchJob(jobID: string, saveJob: boolean = true) {
+ return this.taggedFetch({
+ path: newQueryJobPath(jobID, saveJob),
+ method: 'GET',
+ transformResponse: ioTransformer(ioQueryJobDetails),
+ });
+ }
+
+ rerunJob(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobPath(jobID),
+ method: 'POST',
+ transformResponse: identity,
+ });
+ }
+
+ deleteJob(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobPath(jobID),
+ method: 'DELETE',
+ transformResponse: identity,
+ });
+ }
+
+ updateJob(jobID: string, request: IOQueryJobPatchRequest) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: newQueryJobPath(jobID),
+ method: 'PATCH',
+ body: request,
+ transformResponse: identity,
+ })
+ );
+ }
+
+ // /jobs/{job-id}/query
+
+ fetchJobQuery(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobQueryPath(jobID, false),
+ method: 'GET',
+ transformResponse: ioTransformer(string),
+ });
+ }
+
+ downloadJobQuery(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobQueryPath(jobID, true),
+ method: 'GET',
+ transformResponse: identity,
+ });
+ }
+
+ // /jobs/{job-id}/stderr
+
+ fetchJobStdErr(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobErrorPath(jobID, false),
+ method: 'GET',
+ transformResponse: ioTransformer(string),
+ });
+ }
+
+ downloadJobStdErr(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobErrorPath(jobID, true),
+ method: 'GET',
+ transformResponse: identity,
+ });
+ }
+
+ // /jobs/{job-id}/result
+
+ fetchJobResult(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobResultPath(jobID, false),
+ method: 'GET',
+ transformResponse: ioTransformer(string),
+ });
+ }
+
+ downloadJobResult(jobID: string) {
+ return this.taggedFetch({
+ path: newQueryJobResultPath(jobID, true),
+ method: 'GET',
+ transformResponse: identity,
+ });
+ }
+
+ // /statuses
+
+ fetchJobStatuses(jobIDs: string[]) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: QUERY_SVC_STATUSES_PATH,
+ method: 'POST',
+ body: JSON.stringify(jobIDs),
+ transformResponse: ioTransformer(ioBulkStatusResponse),
+ })
+ );
+ }
+
+ // /targets
+
+ fetchBlastableTargets() {
+ return this.taggedFetch({
+ path: QUERY_SVC_TARGETS_PATH,
+ method: 'GET',
+ transformResponse: ioTransformer(ioBlastTargetIndex),
+ });
+ }
+
+ // /link-guest
+
+ linkGuest(request: IOGuestJobTransferRequest) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: QUERY_SVC_LINK_GUEST_PATH,
+ method: 'POST',
+ body: JSON.stringify(request),
+ transformResponse: identity,
+ })
+ );
+ }
+}
diff --git a/packages/libs/multi-blast/src/lib/utils/api/BlastReportClient.ts b/packages/libs/multi-blast/src/lib/utils/api/BlastReportClient.ts
new file mode 100644
index 0000000000..cd29bfedf3
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/BlastReportClient.ts
@@ -0,0 +1,208 @@
+import {
+ createJsonRequest,
+ FetchApiOptions,
+ ioTransformer,
+} from '@veupathdb/http-utils';
+import { BlastCompatibleWdkService } from '../wdkServiceIntegration';
+import { BlastSubClient } from './BlastSubClient';
+import {
+ IOReportJobCreateRequest,
+ ioReportJobCreateResponse,
+ ioReportJobListEntry,
+} from './report/types/ep-jobs';
+import { array, string } from 'io-ts';
+import { identity } from 'lodash';
+import {
+ ioReportJobDetails,
+ IOReportJobPatchRequest,
+} from './report/types/ep-job-by-id';
+import { ioFileEntry } from './report/types/ep-job-file-list';
+import { ioBulkStatusResponse } from './report/types/ep-statuses';
+import { IOGuestJobTransferRequest } from './report/types/ep-link-guest';
+
+// // //
+//
+// Report Service Paths
+//
+// // //
+
+const REPORT_SVC_PATH_BASE = '/report';
+const REPORT_SVC_JOBS_PATH = `${REPORT_SVC_PATH_BASE}/jobs`;
+const REPORT_SVC_STATUSES_PATH = `${REPORT_SVC_PATH_BASE}/statuses`;
+const REPORT_SVC_LINK_GUEST_PATH = `${REPORT_SVC_PATH_BASE}/link-guest`;
+
+const newReportJobsPath = (queryJobID?: string) =>
+ queryJobID === undefined
+ ? REPORT_SVC_JOBS_PATH
+ : `${REPORT_SVC_JOBS_PATH}?query_job_id=${queryJobID}`;
+
+const newReportJobPath = (jobID: string, saveJob?: boolean) =>
+ saveJob === undefined
+ ? `${REPORT_SVC_JOBS_PATH}/${jobID}`
+ : `${REPORT_SVC_JOBS_PATH}/${jobID}?save_job=${saveJob}`;
+
+const newReportJobErrorPath = (jobID: string, download: boolean) =>
+ `${REPORT_SVC_JOBS_PATH}/${jobID}/stderr?download=${download}`;
+
+const newReportJobFileListPath = (jobID: string) =>
+ `${REPORT_SVC_JOBS_PATH}/${jobID}/files`;
+
+const newReportJobFilePath = (
+ jobID: string,
+ fileName: string,
+ download: boolean
+) => `${REPORT_SVC_JOBS_PATH}/${jobID}/files/${fileName}?download=${download}`;
+
+// // //
+//
+// Service Client Implementation
+//
+// // //
+
+export class BlastReportClient extends BlastSubClient {
+ constructor(
+ options: FetchApiOptions,
+ wdkService: BlastCompatibleWdkService,
+ reportError: (error: any) => void
+ ) {
+ super(options, wdkService, reportError);
+ }
+
+ // /jobs
+
+ createJob(request: IOReportJobCreateRequest) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: newReportJobsPath(),
+ method: 'POST',
+ body: request,
+ transformResponse: ioTransformer(ioReportJobCreateResponse),
+ })
+ );
+ }
+
+ listJobs(queryJobID?: string) {
+ return this.taggedFetch({
+ path: newReportJobsPath(queryJobID),
+ method: 'GET',
+ transformResponse: ioTransformer(array(ioReportJobListEntry)),
+ });
+ }
+
+ // /jobs/{job-id}
+
+ deleteJob(jobID: string) {
+ return this.taggedFetch({
+ path: newReportJobPath(jobID),
+ method: 'DELETE',
+ transformResponse: identity,
+ });
+ }
+
+ fetchJob(jobID: string, saveJob: boolean = true) {
+ return this.taggedFetch({
+ path: newReportJobPath(jobID, saveJob),
+ method: 'GET',
+ transformResponse: ioTransformer(ioReportJobDetails),
+ });
+ }
+
+ rerunJob(jobID: string) {
+ return this.taggedFetch({
+ path: newReportJobPath(jobID),
+ method: 'POST',
+ transformResponse: identity,
+ });
+ }
+
+ updateJob(jobID: string, request: IOReportJobPatchRequest) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: newReportJobPath(jobID),
+ method: 'PATCH',
+ body: request,
+ transformResponse: identity,
+ })
+ );
+ }
+
+ // /jobs/{job-id}/files
+
+ listJobFiles(jobID: string) {
+ return this.taggedFetch({
+ path: newReportJobFileListPath(jobID),
+ method: 'GET',
+ transformResponse: ioTransformer(array(ioFileEntry)),
+ });
+ }
+
+ // /jobs/{job-id}/files/{filename}
+
+ fetchJobFile(jobID: string, fileName: string) {
+ return this.fetchJobFileAs(jobID, fileName, identity);
+ }
+
+ fetchJobFileAs(
+ jobID: string,
+ fileName: string,
+ transform: (res: unknown) => Promise
+ ) {
+ return this.taggedFetch({
+ path: newReportJobFilePath(jobID, fileName, false),
+ method: 'GET',
+ transformResponse: transform,
+ });
+ }
+
+ downloadJobFile(jobID: string, fileName: string) {
+ return this.taggedFetch({
+ path: newReportJobFilePath(jobID, fileName, true),
+ method: 'GET',
+ transformResponse: identity,
+ });
+ }
+
+ // /jobs/{job-id}/stderr
+
+ fetchJobStdErr(jobID: string) {
+ return this.taggedFetch({
+ path: newReportJobErrorPath(jobID, false),
+ method: 'GET',
+ transformResponse: ioTransformer(string),
+ });
+ }
+
+ downloadJobStdErr(jobID: string) {
+ return this.taggedFetch({
+ path: newReportJobErrorPath(jobID, true),
+ method: 'GET',
+ transformResponse: identity,
+ });
+ }
+
+ // /statuses
+
+ fetchJobStatuses(jobIDs: string[]) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: REPORT_SVC_STATUSES_PATH,
+ method: 'POST',
+ body: jobIDs,
+ transformResponse: ioTransformer(ioBulkStatusResponse),
+ })
+ );
+ }
+
+ // /link-guest
+
+ linkGuest(request: IOGuestJobTransferRequest) {
+ return this.taggedFetch(
+ createJsonRequest({
+ path: REPORT_SVC_LINK_GUEST_PATH,
+ method: 'POST',
+ body: request,
+ transformResponse: identity,
+ })
+ );
+ }
+}
diff --git a/packages/libs/multi-blast/src/lib/utils/api/BlastSubClient.ts b/packages/libs/multi-blast/src/lib/utils/api/BlastSubClient.ts
new file mode 100644
index 0000000000..f60239f416
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/BlastSubClient.ts
@@ -0,0 +1,87 @@
+import {
+ ApiRequest,
+ FetchApiOptions,
+ FetchClientWithCredentials,
+} from '@veupathdb/http-utils';
+import { BlastCompatibleWdkService } from '../wdkServiceIntegration';
+import { ApiResult, errorDetails, ErrorDetails } from '../ServiceTypes';
+import { isLeft, map } from 'fp-ts/Either';
+
+export class BlastSubClient extends FetchClientWithCredentials {
+ constructor(
+ options: FetchApiOptions,
+ protected readonly wdkService: BlastCompatibleWdkService,
+ protected reportError: (error: any) => void
+ ) {
+ super(options, wdkService);
+ }
+
+ async taggedFetch(
+ apiRequest: ApiRequest
+ ): Promise> {
+ try {
+ return {
+ status: 'ok',
+ value: await super.fetch(apiRequest),
+ };
+ } catch (error: any) {
+ if (
+ typeof error === 'object' &&
+ error != null &&
+ typeof error.message === 'string'
+ ) {
+ try {
+ const errorDetailsJson = JSON.parse(
+ error.message.replace(/^[^{]*(\{.*\})[^}]*$/, '$1')
+ );
+
+ const decodedErrorDetails = map(transformTooLargeError)(
+ errorDetails.decode(errorDetailsJson)
+ );
+
+ if (
+ isLeft(decodedErrorDetails) ||
+ (decodedErrorDetails.right.status !== 'invalid-input' &&
+ decodedErrorDetails.right.status !== 'too-large')
+ ) {
+ this.reportError(error);
+ }
+
+ return isLeft(decodedErrorDetails)
+ ? {
+ status: 'error',
+ details: {
+ status: 'unknown',
+ message: error.message,
+ },
+ }
+ : {
+ status: 'error',
+ details: decodedErrorDetails.right,
+ };
+ } catch {
+ this.reportError(error);
+
+ return {
+ status: 'error',
+ details: {
+ status: 'unknown',
+ message: error.message,
+ },
+ };
+ }
+ } else {
+ throw error;
+ }
+ }
+ }
+}
+
+function transformTooLargeError(errorDetails: ErrorDetails): ErrorDetails {
+ return errorDetails.status === 'too-large' ||
+ (errorDetails.status === 'bad-request' &&
+ errorDetails.message ===
+ 'Requested report is larger than the specified max content size.')
+ ? { ...errorDetails, status: 'too-large' }
+ : errorDetails;
+}
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-all.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-all.ts
new file mode 100644
index 0000000000..a6b8cc38a9
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-all.ts
@@ -0,0 +1,24 @@
+import { TypeOf, union } from 'io-ts';
+import { ioBlastNConfig } from './blast-config-n';
+import { ioBlastPConfig } from './blast-config-p';
+import { ioPSIBlastConfig } from './blast-config-psi';
+import { ioRPSBlastConfig } from './blast-config-rps';
+import { ioRPSTBlastNConfig } from './blast-config-rpstn';
+import { ioTBlastNConfig } from './blast-config-tn';
+import { ioTBlastXConfig } from './blast-config-tx';
+import { ioBlastXConfig } from './blast-config-x';
+import { ioDeltaBlastConfig } from './blast-config-delta';
+
+export const ioBlastConfig = union([
+ ioBlastNConfig,
+ ioBlastPConfig,
+ ioBlastXConfig,
+ ioDeltaBlastConfig,
+ ioPSIBlastConfig,
+ ioRPSBlastConfig,
+ ioRPSTBlastNConfig,
+ ioTBlastNConfig,
+ ioTBlastXConfig,
+]);
+
+export type IOBlastConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-common.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-common.ts
new file mode 100644
index 0000000000..e2342c38cb
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-common.ts
@@ -0,0 +1,58 @@
+import { boolean, keyof, number, partial, string, type, TypeOf } from 'io-ts';
+
+// // //
+//
+// Common Blast Types
+//
+// Types that are common across multiple blast configuration specifications.
+//
+// // //
+
+export const ioBlastLocation = type({
+ start: number,
+ stop: number,
+});
+
+export type IOBlastLocation = TypeOf;
+
+//
+
+export const ioBlastSeg = partial({
+ enabled: boolean,
+ window: number,
+ locut: number,
+ hicut: number,
+});
+
+export type IOBlastSeg = TypeOf;
+
+//
+
+export const ioBlastStrand = keyof({
+ plus: null,
+ minus: null,
+ both: null,
+});
+
+export type IOBlastStrand = TypeOf;
+
+//
+
+export const ioBlastQueryConfig = partial({
+ queryLocation: ioBlastLocation,
+ eValue: string,
+ softMasking: boolean,
+ lowercaseMasking: boolean,
+ queryCoverageHSPPercent: number,
+ maxHSPs: number,
+ maxTargetSequences: number,
+ dbSize: number,
+ searchSpace: number,
+ xDropoffUngapped: number,
+ windowSize: number,
+ parseDefLines: boolean,
+});
+
+export type IOBlastQueryConfig = TypeOf;
+
+//
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-delta.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-delta.ts
new file mode 100644
index 0000000000..fd0cf8a603
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-delta.ts
@@ -0,0 +1,70 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ type,
+ TypeOf,
+} from 'io-ts';
+import { ioBlastQueryConfig, ioBlastSeg } from './blast-common';
+
+// // //
+//
+// DeltaBlast Configuration Types
+//
+// // //
+
+export const ioDeltaBlastMatrix = keyof({
+ BLOSUM45: null,
+ BLOSUM50: null,
+ BLOSUM62: null,
+ BLOSUM80: null,
+ BLOSUM90: null,
+ PAM30: null,
+ PAM70: null,
+ PAM250: null,
+});
+
+export type IODeltaBlastMatrix = TypeOf;
+
+//
+
+export const ioDeltaBlastCompBasedStats = keyof({
+ none: null,
+ 'comp-based-stats': null,
+});
+
+export type IODeltaBlastCompBasedStats = TypeOf<
+ typeof ioDeltaBlastCompBasedStats
+>;
+
+//
+
+export const ioDeltaBlastConfig = intersection([
+ type({
+ tool: literal('deltablast'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ wordSize: number,
+ gapOpen: number,
+ gapExtend: number,
+ matrix: ioDeltaBlastMatrix,
+ threshold: number,
+ compBasedStats: ioDeltaBlastCompBasedStats,
+ seg: ioBlastSeg,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ gapTrigger: number,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IODeltaBlastConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-n.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-n.ts
new file mode 100644
index 0000000000..2a38ce940f
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-n.ts
@@ -0,0 +1,100 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ string,
+ type,
+ TypeOf,
+ union,
+} from 'io-ts';
+
+import { ioBlastQueryConfig, ioBlastStrand } from './blast-common';
+
+// // //
+//
+// BlastN Configuration Types
+//
+// // //
+
+export const ioBlastNTask = keyof({
+ blastn: null,
+ 'blastn-short': null,
+ 'dc-megablast': null,
+ megablast: null,
+ rmblastn: null,
+});
+
+export type IOBlastNTask = TypeOf;
+
+//
+
+export const ioBlastNDust = partial({
+ enabled: boolean,
+ level: number,
+ window: number,
+ linker: number,
+});
+
+export type IOBlastNDust = TypeOf;
+
+//
+
+export const ioBlastNTemplateType = keyof({
+ coding: null,
+ 'coding-and-optimal': null,
+ optimal: null,
+});
+
+export type IOBlastNTemplateType = TypeOf;
+
+//
+
+export const ioBlastNTemplateLength = union([
+ literal(16),
+ literal(18),
+ literal(21),
+]);
+
+export type IOBlastNTemplateLength = TypeOf;
+
+//
+
+export const ioBlastNConfig = intersection([
+ type({
+ tool: literal('blastn'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ strand: ioBlastStrand,
+ task: ioBlastNTask,
+ wordSize: number,
+ gapOpen: number,
+ gapExtend: number,
+ penalty: number,
+ reward: number,
+ useIndex: boolean,
+ indexName: string,
+ dust: ioBlastNDust,
+ dbSoftMask: string,
+ dbHardMask: string,
+ percentIdentity: number,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ templateType: ioBlastNTemplateType,
+ templateLength: ioBlastNTemplateLength,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ nonGreedy: boolean,
+ minRawGappedScore: number,
+ ungappedOnly: boolean,
+ offDiagonalRange: number,
+ }),
+]);
+
+export type IOBlastNConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-p.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-p.ts
new file mode 100644
index 0000000000..1ebfaedc62
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-p.ts
@@ -0,0 +1,85 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ string,
+ type,
+ TypeOf,
+} from 'io-ts';
+
+import { ioBlastQueryConfig, ioBlastSeg } from './blast-common';
+
+// // //
+//
+// BlastP Configuration Types
+//
+// // //
+
+export const ioBlastPTask = keyof({
+ blastp: null,
+ 'blastp-fast': null,
+ 'blastp-short': null,
+});
+
+export type IOBlastPTask = TypeOf;
+
+//
+
+export const ioBlastPMatrix = keyof({
+ BLOSUM45: null,
+ BLOSUM50: null,
+ BLOSUM62: null,
+ BLOSUM80: null,
+ BLOSUM90: null,
+ PAM30: null,
+ PAM70: null,
+ PAM250: null,
+ IDENTITY: null,
+});
+
+export type IOBlastPMatrix = TypeOf;
+
+//
+
+export const ioBlastPCompBasedStats = keyof({
+ none: null,
+ 'comp-based-stats': null,
+ 'comp-based-score-adjustment-conditional': null,
+ 'comp-based-score-adjustment-unconditional': null,
+});
+
+export type IOBlastPCompBasedStats = TypeOf;
+
+//
+
+export const ioBlastPConfig = intersection([
+ type({
+ tool: literal('blastp'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ task: ioBlastPTask,
+ wordSize: number,
+ gapOpen: number,
+ gapExtend: number,
+ matrix: ioBlastPMatrix,
+ threshold: number,
+ compBasedStats: ioBlastPCompBasedStats,
+ seg: ioBlastSeg,
+ dbSoftMask: string,
+ dbHardMask: string,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ ungappedOnly: boolean,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IOBlastPConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-psi.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-psi.ts
new file mode 100644
index 0000000000..1bb9221650
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-psi.ts
@@ -0,0 +1,70 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ type,
+ TypeOf,
+} from 'io-ts';
+import { ioBlastQueryConfig, ioBlastSeg } from './blast-common';
+
+// // //
+//
+// PSIBlast Configuration Types
+//
+// // //
+
+export const ioPSIBlastMatrix = keyof({
+ BLOSUM45: null,
+ BLOSUM50: null,
+ BLOSUM62: null,
+ BLOSUM80: null,
+ BLOSUM90: null,
+ PAM30: null,
+ PAM70: null,
+ PAM250: null,
+});
+
+export type IOPSIBlastMatrix = TypeOf;
+
+//
+
+export const ioPSIBlastCompBasedStats = keyof({
+ none: null,
+ 'comp-based-stats': null,
+ 'comp-based-score-adjustment-conditional': null,
+ 'comp-based-score-adjustment-unconditional': null,
+});
+
+export type IOPSIBlastCompBasedStats = TypeOf;
+
+//
+
+export const ioPSIBlastConfig = intersection([
+ type({
+ tool: literal('psiblast'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ wordSize: number,
+ gapOpen: number,
+ gapExtend: number,
+ matrix: ioPSIBlastMatrix,
+ threshold: number,
+ compBasedStats: ioPSIBlastCompBasedStats,
+ seg: ioBlastSeg,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ gapTrigger: number,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IOPSIBlastConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-rps.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-rps.ts
new file mode 100644
index 0000000000..a5a731f60e
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-rps.ts
@@ -0,0 +1,47 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ type,
+ TypeOf,
+} from 'io-ts';
+import { ioBlastQueryConfig, ioBlastSeg } from './blast-common';
+
+// // //
+//
+// RPSBlast Configuration Types
+//
+// // //
+
+export const ioRPSBlastCompBasedStats = keyof({
+ simplified: null,
+ 'comp-based-stats': null,
+});
+
+export type IORPSBlastCompBasedStats = TypeOf;
+
+//
+
+export const ioRPSBlastConfig = intersection([
+ type({
+ tool: literal('rpsblast'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ compBasedStats: ioRPSBlastCompBasedStats,
+ seg: ioBlastSeg,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IORPSBlastConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-rpstn.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-rpstn.ts
new file mode 100644
index 0000000000..48c3c3ad1e
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-rpstn.ts
@@ -0,0 +1,48 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ type,
+ TypeOf,
+} from 'io-ts';
+import { ioBlastQueryConfig, ioBlastSeg, ioBlastStrand } from './blast-common';
+
+// // //
+//
+// RPSTBlastN Configuration Types
+//
+// // //
+
+export const ioRPSTBlastNCompBasedStats = keyof({
+ none: null,
+ 'comp-based-stats': null,
+});
+
+export type IORPSTBlastNCompBasedStats = TypeOf<
+ typeof ioRPSTBlastNCompBasedStats
+>;
+
+//
+
+export const ioRPSTBlastNConfig = intersection([
+ type({
+ tool: literal('rpstblastn'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ queryGenCode: number,
+ strand: ioBlastStrand,
+ compBasedStats: ioRPSTBlastNCompBasedStats,
+ seg: ioBlastSeg,
+ sumStats: number,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ ungappedOnly: boolean,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IORPSTBlastNConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-tn.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-tn.ts
new file mode 100644
index 0000000000..be6d18c828
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-tn.ts
@@ -0,0 +1,86 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ string,
+ type,
+ TypeOf,
+} from 'io-ts';
+import { ioBlastQueryConfig, ioBlastSeg } from './blast-common';
+
+// // //
+//
+// TBlastN Configuration Types
+//
+// // //
+
+export const ioTBlastNMatrix = keyof({
+ BLOSUM45: null,
+ BLOSUM50: null,
+ BLOSUM62: null,
+ BLOSUM80: null,
+ BLOSUM90: null,
+ PAM30: null,
+ PAM70: null,
+ PAM250: null,
+ IDENTITY: null,
+});
+
+export type IOTBlastNMatrix = TypeOf;
+
+//
+
+export const ioTBlastNTask = keyof({
+ tblastn: null,
+ 'tblastn-fast': null,
+});
+
+export type IOTBlastNTask = TypeOf;
+
+//
+
+export const ioTBlastNCompBasedStats = keyof({
+ none: null,
+ 'comp-based-stats': null,
+ 'comp-based-score-adjustment-conditional': null,
+ 'comp-based-score-adjustment-unconditional': null,
+});
+
+export type IOTBlastNCompBasedStats = TypeOf;
+
+//
+
+export const ioTBlastNConfig = intersection([
+ type({
+ tool: literal('tblastn'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ task: ioTBlastNTask,
+ wordSize: number,
+ gapOpen: number,
+ gapExtend: number,
+ dbGenCode: number,
+ maxIntronLength: number,
+ matrix: ioTBlastNMatrix,
+ threshold: number,
+ compBasedStats: ioTBlastNCompBasedStats,
+ seg: ioBlastSeg,
+ dbSoftMask: string,
+ dbHardMask: string,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ betsHitScoreEdge: number,
+ subjectBestHit: boolean,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ ungappedOnly: boolean,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IOTBlastNConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-tx.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-tx.ts
new file mode 100644
index 0000000000..d1e094a888
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-tx.ts
@@ -0,0 +1,61 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ string,
+ type,
+ TypeOf,
+} from 'io-ts';
+import { ioBlastQueryConfig, ioBlastSeg, ioBlastStrand } from './blast-common';
+
+// // //
+//
+// TBlastX Configuration Types
+//
+// // //
+
+export const ioTBlastXMatrix = keyof({
+ BLOSUM45: null,
+ BLOSUM50: null,
+ BLOSUM62: null,
+ BLOSUM80: null,
+ BLOSUM90: null,
+ PAM30: null,
+ PAM70: null,
+ PAM250: null,
+});
+
+export type IOTBlastXMatrix = TypeOf;
+
+//
+
+export const ioTBlastXConfig = intersection([
+ type({
+ tool: literal('tblastx'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ strand: ioBlastStrand,
+ queryGenCode: number,
+ wordSize: number,
+ maxIntronLength: number,
+ matrix: ioTBlastXMatrix,
+ threshold: number,
+ dbGenCode: number,
+ seg: ioBlastSeg,
+ dbSoftMask: string,
+ dbHardMask: string,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ }),
+]);
+
+export type IOTBlastXConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-x.ts b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-x.ts
new file mode 100644
index 0000000000..d8f5adb318
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/blast/blast-config-x.ts
@@ -0,0 +1,87 @@
+import {
+ boolean,
+ intersection,
+ keyof,
+ literal,
+ number,
+ partial,
+ string,
+ type,
+ TypeOf,
+} from 'io-ts';
+
+import { ioBlastQueryConfig, ioBlastSeg, ioBlastStrand } from './blast-common';
+
+// // //
+//
+// BlastX Configuration Types
+//
+// // //
+
+export const ioBlastXTask = keyof({
+ blastx: null,
+ 'blastx-fast': null,
+});
+
+export type IOBlastXTask = TypeOf;
+
+//
+
+export const ioBlastXMatrix = keyof({
+ BLOSUM45: null,
+ BLOSUM50: null,
+ BLOSUM62: null,
+ BLOSUM80: null,
+ BLOSUM90: null,
+ PAM30: null,
+ PAM70: null,
+ PAM250: null,
+});
+
+export type IOBlastXMatrix = TypeOf;
+
+//
+
+export const ioBlastXCompBasedStats = keyof({
+ none: null,
+ 'comp-based-stats': null,
+ 'comp-based-score-adjustment-conditional': null,
+ 'comp-based-score-adjustment-unconditional': null,
+});
+
+export type IOBlastXCompBasedStats = TypeOf;
+
+//
+
+export const ioBlastXConfig = intersection([
+ type({
+ tool: literal('blastx'),
+ }),
+ ioBlastQueryConfig,
+ partial({
+ strand: ioBlastStrand,
+ queryGenCode: number,
+ task: ioBlastXTask,
+ wordSize: number,
+ gapOpen: number,
+ gapExtend: number,
+ maxIntronLength: number,
+ matrix: ioBlastXMatrix,
+ threshold: number,
+ compBasedStats: ioBlastXCompBasedStats,
+ seg: ioBlastSeg,
+ dbSoftMask: string,
+ dbHardMask: string,
+ cullingLimit: number,
+ bestHitOverhang: number,
+ bestHitScoreEdge: number,
+ subjectBestHit: boolean,
+ sumStats: boolean,
+ xDropoffPrelimGapped: number,
+ xDropoffFinalGapped: number,
+ ungappedOnly: boolean,
+ useSWTraceback: boolean,
+ }),
+]);
+
+export type IOBlastXConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/types/common.ts b/packages/libs/multi-blast/src/lib/utils/api/query/types/common.ts
new file mode 100644
index 0000000000..96d19c54fa
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/types/common.ts
@@ -0,0 +1,44 @@
+import {
+ array,
+ boolean,
+ intersection,
+ keyof,
+ partial,
+ string,
+ type,
+ TypeOf,
+} from 'io-ts';
+
+export const ioJobStatus = keyof({
+ queued: null,
+ 'in-progress': null,
+ complete: null,
+ failed: null,
+ expired: null,
+});
+
+export const ioJobTarget = type({
+ targetDisplayName: string,
+ targetFile: string,
+});
+
+export const ioQueryJobUserMeta = partial({
+ summary: string,
+ description: string,
+});
+
+export const ioQueryJobConfig = intersection([
+ type({
+ site: string,
+ targets: array(ioJobTarget),
+ }),
+ partial({
+ query: string,
+ addToUserCollection: boolean,
+ }),
+]);
+
+export type IOJobStatus = TypeOf;
+export type IOJobTarget = TypeOf;
+export type IOQueryJobUserMeta = TypeOf;
+export type IOQueryJobConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-jobs-by-id.ts b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-jobs-by-id.ts
new file mode 100644
index 0000000000..4fbb15e1b2
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-jobs-by-id.ts
@@ -0,0 +1,22 @@
+import { array, intersection, partial, string, type, TypeOf } from 'io-ts';
+import { ioJobStatus, ioQueryJobConfig, ioQueryJobUserMeta } from './common';
+import { ioBlastConfig } from '../blast/blast-all';
+
+export const ioQueryJobDetails = intersection([
+ type({
+ queryJobID: string,
+ status: ioJobStatus,
+ jobConfig: ioQueryJobConfig,
+ blastConfig: ioBlastConfig,
+ createdOn: string,
+ }),
+ partial({
+ userMeta: ioQueryJobUserMeta,
+ subJobs: array(string),
+ }),
+]);
+
+export const ioQueryJobPatchRequest = partial({ userMeta: ioQueryJobUserMeta });
+
+export type IOQueryJobDetails = TypeOf;
+export type IOQueryJobPatchRequest = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-jobs.ts b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-jobs.ts
new file mode 100644
index 0000000000..1c75c346db
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-jobs.ts
@@ -0,0 +1,34 @@
+import { array, intersection, partial, string, type, TypeOf } from 'io-ts';
+import { ioBlastConfig } from '../blast/blast-all';
+import { ioJobStatus, ioQueryJobConfig, ioQueryJobUserMeta } from './common';
+
+export const ioQueryJobListEntry = intersection([
+ type({
+ queryJobID: string,
+ status: ioJobStatus,
+ site: string,
+ createdOn: string,
+ }),
+ partial({
+ userMeta: ioQueryJobUserMeta,
+ }),
+]);
+
+export const ioQueryJobListResponse = array(ioQueryJobListEntry);
+
+export const ioQueryJobCreateRequest = intersection([
+ type({
+ jobConfig: ioQueryJobConfig,
+ blastConfig: ioBlastConfig,
+ }),
+ partial({
+ userMeta: ioQueryJobUserMeta,
+ }),
+]);
+
+export const ioQueryJobCreateResponse = type({ queryJobID: string });
+
+export type IOQueryJobListEntry = TypeOf;
+export type IOQueryJobListResponse = TypeOf;
+export type IOQueryJobCreateRequest = TypeOf;
+export type IOQueryJobCreateResponse = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-link-guest.ts b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-link-guest.ts
new file mode 100644
index 0000000000..b8f157215b
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-link-guest.ts
@@ -0,0 +1,7 @@
+import { number, type, TypeOf } from 'io-ts';
+
+export const ioGuestJobTransferRequest = type({ guestID: number });
+
+export type IOGuestJobTransferRequest = TypeOf<
+ typeof ioGuestJobTransferRequest
+>;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-statuses.ts b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-statuses.ts
new file mode 100644
index 0000000000..8892e65676
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-statuses.ts
@@ -0,0 +1,6 @@
+import { record, string, TypeOf } from 'io-ts';
+import { ioJobStatus } from './common';
+
+export const ioBulkStatusResponse = record(string, ioJobStatus);
+
+export type IOBulkStatusResponse = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-targets.ts b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-targets.ts
new file mode 100644
index 0000000000..75c7e4a947
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/query/types/ep-targets.ts
@@ -0,0 +1,14 @@
+import { array, partial, record, string, TypeOf } from 'io-ts';
+
+export const ioBlastableTarget = partial({
+ naTargets: array(string),
+ aaTargets: array(string),
+});
+
+export const ioBlastTargetMap = record(string, ioBlastableTarget);
+
+export const ioBlastTargetIndex = record(string, ioBlastTargetMap);
+
+export type IOBlastableTarget = TypeOf;
+export type IOBlastTargetMap = TypeOf;
+export type IOBlastTargetIndex = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/blast/blast-config-format.ts b/packages/libs/multi-blast/src/lib/utils/api/report/blast/blast-config-format.ts
new file mode 100644
index 0000000000..2edef09dcf
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/blast/blast-config-format.ts
@@ -0,0 +1,114 @@
+import { array, boolean, number, partial, TypeOf, keyof } from 'io-ts';
+
+export const ioBlastHSPSorting = keyof({
+ 'by-hsp-evalue': null,
+ 'by-hsp-score': null,
+ 'by-hsp-query-start': null,
+ 'by-hsp-percent-identity': null,
+ 'by-hsp-subject-start': null,
+});
+
+export const ioBlastHitSorting = keyof({
+ 'by-evalue': null,
+ 'by-bit-score': null,
+ 'by-total-score': null,
+ 'by-percent-identity': null,
+ 'by-query-coverage': null,
+});
+
+export const ioBlastOutField = keyof({
+ qseqid: null,
+ qgi: null,
+ qacc: null,
+ qaccver: null,
+ qlen: null,
+ sseqid: null,
+ sallseqid: null,
+ sgi: null,
+ sallgi: null,
+ sacc: null,
+ saccver: null,
+ sallacc: null,
+ slen: null,
+ qstart: null,
+ qend: null,
+ sstart: null,
+ send: null,
+ qseq: null,
+ sseq: null,
+ evalue: null,
+ bitscore: null,
+ score: null,
+ length: null,
+ pident: null,
+ nident: null,
+ mismatch: null,
+ positive: null,
+ gapopen: null,
+ gaps: null,
+ ppos: null,
+ frames: null,
+ qframe: null,
+ sframe: null,
+ btop: null,
+ staxid: null,
+ ssciname: null,
+ scomname: null,
+ sblastname: null,
+ sskingdom: null,
+ staxids: null,
+ sscinames: null,
+ scomnames: null,
+ sblastnames: null,
+ sskingdoms: null,
+ stitle: null,
+ salltitles: null,
+ sstrand: null,
+ qcovs: null,
+ qcovhsp: null,
+ qcovus: null,
+ SQ: null,
+ SR: null,
+ std: null,
+});
+
+export const ioBlastOutFormat = keyof({
+ pairwise: null,
+ 'query-anchored-with-identities': null,
+ 'query-anchored-no-identities': null,
+ 'flat-query-anchored-with-identities': null,
+ 'flat-query-anchored-no-identities': null,
+ xml: null,
+ tabular: null,
+ 'tabular-with-comments': null,
+ 'seqalign-text': null,
+ 'seqalign-binary': null,
+ csv: null,
+ asn1: null,
+ 'seqalign-json': null,
+ 'multi-file-blast-json': null,
+ 'multi-file-blast-xml2': null,
+ 'single-file-blast-json': null,
+ 'single-file-blast-xml2': null,
+ sam: null,
+ 'organism-report': null,
+});
+
+export const ioBlastFormatConfig = partial({
+ formatType: ioBlastOutFormat,
+ formatFields: array(ioBlastOutField),
+ showGIs: boolean,
+ numDescriptions: number,
+ numAlignments: number,
+ lineLength: number,
+ sortHits: ioBlastHitSorting,
+ sortHSPs: ioBlastHSPSorting,
+ maxTargetSequences: number,
+ parseDefLines: boolean,
+});
+
+export type IOBlastHSPSorting = TypeOf;
+export type IOBlastHitSorting = TypeOf;
+export type IOBlastOutField = TypeOf;
+export type IOBlastOutFormat = TypeOf;
+export type IOBlastFormatConfig = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/types/common.ts b/packages/libs/multi-blast/src/lib/utils/api/report/types/common.ts
new file mode 100644
index 0000000000..6ff5a74e02
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/types/common.ts
@@ -0,0 +1,17 @@
+import { keyof, partial, string, TypeOf } from 'io-ts';
+
+export const ioJobStatus = keyof({
+ queued: null,
+ 'in-progress': null,
+ complete: null,
+ failed: null,
+ expired: null,
+});
+
+export const ioReportJobUserMeta = partial({
+ summary: string,
+ description: string,
+});
+
+export type IOJobStatus = TypeOf;
+export type IOReportJobUserMeta = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-job-by-id.ts b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-job-by-id.ts
new file mode 100644
index 0000000000..8b5bf9d8c6
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-job-by-id.ts
@@ -0,0 +1,22 @@
+import { intersection, partial, string, type, TypeOf } from 'io-ts';
+import { ioJobStatus, ioReportJobUserMeta } from './common';
+import { ioBlastFormatConfig } from '../blast/blast-config-format';
+
+export const ioReportJobPatchRequest = partial({
+ userMeta: ioReportJobUserMeta,
+});
+
+export const ioReportJobDetails = intersection([
+ type({
+ reportJobID: string,
+ queryJobID: string,
+ status: ioJobStatus,
+ blastConfig: ioBlastFormatConfig,
+ }),
+ partial({
+ userMeta: ioReportJobUserMeta,
+ }),
+]);
+
+export type IOReportJobPatchRequest = TypeOf;
+export type IOReportJobDetails = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-job-file-list.ts b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-job-file-list.ts
new file mode 100644
index 0000000000..fa593fc188
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-job-file-list.ts
@@ -0,0 +1,8 @@
+import { number, string, type, TypeOf } from 'io-ts';
+
+export const ioFileEntry = type({
+ name: string,
+ size: number,
+});
+
+export type IOFileEntry = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-jobs.ts b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-jobs.ts
new file mode 100644
index 0000000000..4194c06d9b
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-jobs.ts
@@ -0,0 +1,33 @@
+import { boolean, intersection, partial, string, type, TypeOf } from 'io-ts';
+import { ioBlastFormatConfig } from '../blast/blast-config-format';
+import { ioJobStatus, ioReportJobUserMeta } from './common';
+
+export const ioReportJobCreateRequest = intersection([
+ type({
+ queryJobID: string,
+ }),
+ partial({
+ blastConfig: ioBlastFormatConfig,
+ addToUserCollection: boolean,
+ userMeta: ioReportJobUserMeta,
+ }),
+]);
+
+export const ioReportJobListEntry = intersection([
+ type({
+ reportJobID: string,
+ queryJobID: string,
+ status: ioJobStatus,
+ }),
+ partial({
+ userMeta: ioReportJobUserMeta,
+ }),
+]);
+
+export const ioReportJobCreateResponse = type({ reportJobID: string });
+
+export type IOReportJobCreateRequest = TypeOf;
+export type IOReportJobListEntry = TypeOf;
+export type IOReportJobCreateResponse = TypeOf<
+ typeof ioReportJobCreateResponse
+>;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-link-guest.ts b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-link-guest.ts
new file mode 100644
index 0000000000..b8f157215b
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-link-guest.ts
@@ -0,0 +1,7 @@
+import { number, type, TypeOf } from 'io-ts';
+
+export const ioGuestJobTransferRequest = type({ guestID: number });
+
+export type IOGuestJobTransferRequest = TypeOf<
+ typeof ioGuestJobTransferRequest
+>;
diff --git a/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-statuses.ts b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-statuses.ts
new file mode 100644
index 0000000000..8892e65676
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/api/report/types/ep-statuses.ts
@@ -0,0 +1,6 @@
+import { record, string, TypeOf } from 'io-ts';
+import { ioJobStatus } from './common';
+
+export const ioBulkStatusResponse = record(string, ioJobStatus);
+
+export type IOBulkStatusResponse = TypeOf;
diff --git a/packages/libs/multi-blast/src/lib/utils/params-from-query-config.ts b/packages/libs/multi-blast/src/lib/utils/params-from-query-config.ts
new file mode 100644
index 0000000000..91b8719d9c
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/params-from-query-config.ts
@@ -0,0 +1,102 @@
+import { IOBlastConfig } from './api/query/blast/blast-all';
+import { ParameterValues } from '@veupathdb/wdk-client/lib/Utils/WdkModel';
+import { IOBlastNDust } from './api/query/blast/blast-config-n';
+import { IOBlastSeg } from './api/query/blast/blast-common';
+import { ParamNames } from './params';
+
+export function blastConfigToParamValues(
+ blastConfig: IOBlastConfig
+): ParameterValues {
+ const params: ParameterValues = {
+ [ParamNames.BlastAlgorithm]: blastConfig.tool,
+ };
+
+ optSetParam(params, ParamNames.ExpectValue, blastConfig.eValue);
+ optSetParam(
+ params,
+ ParamNames.NumQueryResults,
+ blastConfig.maxTargetSequences
+ );
+ optSetParam(params, ParamNames.MaxMatchesQueryRange, blastConfig.maxHSPs);
+ optSetParam(params, ParamNames.SoftMask, blastConfig.softMasking);
+ optSetParam(params, ParamNames.LowercaseMask, blastConfig.lowercaseMasking);
+
+ switch (blastConfig.tool) {
+ case 'blastn':
+ optSetParam(params, ParamNames.WordSize, blastConfig.wordSize);
+ params[ParamNames.FilterLowComplexity] = dustToParamValue(
+ blastConfig.dust
+ );
+ if (blastConfig.gapOpen != null && blastConfig.gapExtend != null) {
+ params[
+ ParamNames.GapCosts
+ ] = `${blastConfig.gapOpen},${blastConfig.gapExtend}`;
+ }
+ if (blastConfig.reward != null && blastConfig.penalty != null) {
+ params[
+ ParamNames.MatchMismatch
+ ] = `${blastConfig.reward},${blastConfig.penalty}`;
+ }
+ break;
+ case 'blastp':
+ case 'blastx':
+ case 'deltablast':
+ case 'psiblast':
+ case 'tblastn':
+ optSetParam(params, ParamNames.WordSize, blastConfig.wordSize);
+ optSetParam(params, ParamNames.ScoringMatrix, blastConfig.matrix);
+ params[ParamNames.CompAdjust] = blastConfig.compBasedStats as string;
+ params[ParamNames.FilterLowComplexity] = segToParamValue(blastConfig.seg);
+ if (blastConfig.gapOpen != null && blastConfig.gapExtend != null) {
+ params[
+ ParamNames.GapCosts
+ ] = `${blastConfig.gapOpen},${blastConfig.gapExtend}`;
+ }
+ break;
+ case 'rpsblast':
+ case 'rpstblastn':
+ params[ParamNames.CompAdjust] = blastConfig.compBasedStats as string;
+ params[ParamNames.FilterLowComplexity] = segToParamValue(blastConfig.seg);
+ break;
+ case 'tblastx':
+ optSetParam(params, ParamNames.WordSize, blastConfig.wordSize);
+ optSetParam(params, ParamNames.ScoringMatrix, blastConfig.matrix);
+ params[ParamNames.FilterLowComplexity] = segToParamValue(blastConfig.seg);
+ break;
+ }
+
+ return params;
+}
+
+/**
+ * Optionally sets a param if the target value is not `null` and not
+ * `undefined`.
+ *
+ * @param params Parameter map into which the value may be set.
+ *
+ * @param key Key under which the value may be set in the parameter map.
+ *
+ * @param value Value which may be set if it is not `null` and is not
+ * `undefined`.
+ */
+function optSetParam(
+ params: ParameterValues,
+ key: string,
+ value: unknown | null | undefined
+) {
+ if (value !== null && value !== undefined) {
+ params[key] = String(value);
+ }
+}
+
+function dustToParamValue(dust: IOBlastNDust | null | undefined): string {
+ return dust === null || dust === undefined || !dust.enabled
+ ? 'no filter'
+ : 'dust';
+}
+
+function segToParamValue(seg: IOBlastSeg | null | undefined): string {
+ return seg === null || seg === undefined || !seg.enabled
+ ? 'no filter'
+ : 'seg';
+}
diff --git a/packages/libs/multi-blast/src/lib/utils/params-to-query-config.ts b/packages/libs/multi-blast/src/lib/utils/params-to-query-config.ts
new file mode 100644
index 0000000000..7e2882594a
--- /dev/null
+++ b/packages/libs/multi-blast/src/lib/utils/params-to-query-config.ts
@@ -0,0 +1,274 @@
+import { ParameterValues } from '@veupathdb/wdk-client/lib/Utils/WdkModel';
+import { ParamNames } from './params';
+import { IOBlastQueryConfig, IOBlastSeg } from './api/query/blast/blast-common';
+import { IOBlastNConfig, IOBlastNDust } from './api/query/blast/blast-config-n';
+import {
+ IOBlastPCompBasedStats,
+ IOBlastPConfig,
+ IOBlastPMatrix,
+} from './api/query/blast/blast-config-p';
+import {
+ IOBlastXCompBasedStats,
+ IOBlastXConfig,
+ IOBlastXMatrix,
+} from './api/query/blast/blast-config-x';
+import {
+ IOTBlastNCompBasedStats,
+ IOTBlastNConfig,
+ IOTBlastNMatrix,
+} from './api/query/blast/blast-config-tn';
+import {
+ IOTBlastXConfig,
+ IOTBlastXMatrix,
+} from './api/query/blast/blast-config-tx';
+import {
+ IOPSIBlastCompBasedStats,
+ IOPSIBlastConfig,
+ IOPSIBlastMatrix,
+} from './api/query/blast/blast-config-psi';
+import {
+ IORPSBlastCompBasedStats,
+ IORPSBlastConfig,
+} from './api/query/blast/blast-config-rps';
+import {
+ IORPSTBlastNCompBasedStats,
+ IORPSTBlastNConfig,
+} from './api/query/blast/blast-config-rpstn';
+import {
+ IODeltaBlastCompBasedStats,
+ IODeltaBlastConfig,
+ IODeltaBlastMatrix,
+} from './api/query/blast/blast-config-delta';
+import { IOBlastConfig } from './api/query/blast/blast-all';
+
+const LOW_COMPLEXITY_NONE_VALUE = 'no filter';
+
+/**
+ * This function transforms the parameter values of a multi-blast WDK question
+ * into a valid job configuration for the multi-blast service.
+ *
+ * NOTE: This logic is mirrored in
+ *
+ * EbrcWebSvcCommon/WSFPlugin/src/main/java/org/apidb/apicomplexa/wsfplugin/blast/MultiblastServiceParams.java
+ *
+ * The two must be kept in sync so unexpected results are not shown in the
+ * multi-blast UI and so users get the same result when they export to WDK.
+ */
+export function paramValuesToBlastQueryConfig(
+ paramValues: ParameterValues
+): IOBlastConfig {
+ const { [ParamNames.BlastAlgorithm]: tool } = paramValues;
+
+ const config: IOBlastQueryConfig = {
+ eValue: paramValues[ParamNames.ExpectValue],
+ softMasking: booleanOrUndefined(paramValues[ParamNames.SoftMask]),
+ lowercaseMasking: booleanOrUndefined(paramValues[ParamNames.LowercaseMask]),
+ maxTargetSequences: zeroToUndefined(
+ numberOrUndefined(paramValues[ParamNames.NumQueryResults])
+ ),
+ maxHSPs: zeroToUndefined(
+ numberOrUndefined(paramValues[ParamNames.MaxMatchesQueryRange])
+ ),
+ };
+
+ switch (tool) {
+ case 'blastn':
+ return paramsToBlastNConfig(config, paramValues);
+ case 'blastp':
+ return paramsToBlastPConfig(config, paramValues);
+ case 'blastx':
+ return paramsToBlastXConfig(config, paramValues);
+ case 'tblastn':
+ return paramsToTBlastNConfig(config, paramValues);
+ case 'tblastx':
+ return paramsToTBlastXConfig(config, paramValues);
+ case 'deltablast':
+ return paramsToDeltaBlastConfig(config, paramValues);
+ case 'psiblast':
+ return paramsToPSIBlastConfig(config, paramValues);
+ case 'rpsblast':
+ return paramsToRPSBlastConfig(config, paramValues);
+ case 'rpstblastn':
+ return paramsToRPSTBlastNConfig(config, paramValues);
+ default:
+ throw new Error(`The selected BLAST tool '${tool}' is not supported.`);
+ }
+}
+
+function paramsToBlastNConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IOBlastNConfig {
+ return {
+ tool: 'blastn',
+ task: 'blastn',
+ ...base,
+ ...parseGapCosts(params),
+ ...mismatch(params[ParamNames.MatchMismatch]),
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ dust: parseDust(params),
+ };
+}
+
+function paramsToBlastPConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IOBlastPConfig {
+ return {
+ tool: 'blastp',
+ task: 'blastp',
+ ...base,
+ ...parseGapCosts(params),
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ matrix: params[ParamNames.ScoringMatrix] as IOBlastPMatrix,
+ compBasedStats: params[ParamNames.CompAdjust] as IOBlastPCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToBlastXConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IOBlastXConfig {
+ return {
+ tool: 'blastx',
+ task: 'blastx',
+ ...base,
+ ...parseGapCosts(params),
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ matrix: params[ParamNames.ScoringMatrix] as IOBlastXMatrix,
+ compBasedStats: params[ParamNames.CompAdjust] as IOBlastXCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToTBlastNConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IOTBlastNConfig {
+ return {
+ tool: 'tblastn',
+ task: 'tblastn',
+ ...base,
+ ...parseGapCosts(params),
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ matrix: params[ParamNames.ScoringMatrix] as IOTBlastNMatrix,
+ compBasedStats: params[ParamNames.CompAdjust] as IOTBlastNCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToTBlastXConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IOTBlastXConfig {
+ return {
+ tool: 'tblastx',
+ ...base,
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ matrix: params[ParamNames.ScoringMatrix] as IOTBlastXMatrix,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToPSIBlastConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IOPSIBlastConfig {
+ return {
+ tool: 'psiblast',
+ ...base,
+ ...parseGapCosts(params),
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ matrix: params[ParamNames.ScoringMatrix] as IOPSIBlastMatrix,
+ compBasedStats: params[ParamNames.CompAdjust] as IOPSIBlastCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToRPSBlastConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IORPSBlastConfig {
+ return {
+ tool: 'rpsblast',
+ ...base,
+ compBasedStats: params[ParamNames.CompAdjust] as IORPSBlastCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToRPSTBlastNConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IORPSTBlastNConfig {
+ return {
+ tool: 'rpstblastn',
+ ...base,
+ compBasedStats: params[ParamNames.CompAdjust] as IORPSTBlastNCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function paramsToDeltaBlastConfig(
+ base: IOBlastQueryConfig,
+ params: ParameterValues
+): IODeltaBlastConfig {
+ return {
+ tool: 'deltablast',
+ ...base,
+ ...parseGapCosts(params),
+ wordSize: numberOrUndefined(params[ParamNames.WordSize]),
+ matrix: params[ParamNames.ScoringMatrix] as IODeltaBlastMatrix,
+ compBasedStats: params[ParamNames.CompAdjust] as IODeltaBlastCompBasedStats,
+ seg: parseSeg(params),
+ };
+}
+
+function parseGapCosts(
+ params: ParameterValues
+): { gapOpen: number; gapExtend: number } | {} {
+ const value = params[ParamNames.GapCosts];
+
+ if (value === undefined) return {};
+
+ const [gapOpen, gapExtend] = value.split(',').map(Number);
+
+ return { gapOpen, gapExtend };
+}
+
+function parseSeg(params: ParameterValues): IOBlastSeg {
+ return {
+ enabled:
+ params[ParamNames.FilterLowComplexity] !== LOW_COMPLEXITY_NONE_VALUE,
+ };
+}
+
+function parseDust(params: ParameterValues): IOBlastNDust {
+ return {
+ enabled:
+ params[ParamNames.FilterLowComplexity] !== LOW_COMPLEXITY_NONE_VALUE,
+ };
+}
+
+function mismatch(
+ value: string | undefined
+): { reward: number; penalty: number } | {} {
+ if (value === undefined) return {};
+
+ const [reward, penalty] = value.split(',').map(Number);
+
+ return { reward, penalty };
+}
+
+function zeroToUndefined(value: number | undefined): number | undefined {
+ return value === undefined || value === 0 ? undefined : value;
+}
+
+function booleanOrUndefined(value: string | undefined) {
+ return value === undefined ? undefined : value === 'true';
+}
+
+function numberOrUndefined(value: string | undefined) {
+ return value === undefined ? undefined : Number(value);
+}
diff --git a/packages/libs/multi-blast/src/lib/utils/params.ts b/packages/libs/multi-blast/src/lib/utils/params.ts
index c53db2ec44..e4395cc7da 100644
--- a/packages/libs/multi-blast/src/lib/utils/params.ts
+++ b/packages/libs/multi-blast/src/lib/utils/params.ts
@@ -14,48 +14,41 @@ import {
SHOW_ONLY_PREFERRED_ORGANISMS_PROPERTY,
} from '@veupathdb/preferred-organisms/lib/components/OrganismParam';
-import {
- IoBlastCompBasedStats,
- IoBlastConfig,
- IoBlastNDust,
- IOBlastPScoringMatrix,
- IoBlastSegMask,
- IOBlastXScoringMatrix,
- IOTBlastNScoringMatrix,
- IOTBlastXScoringMatrix,
- LongJobResponse,
- Target,
-} from './ServiceTypes';
-
-export const BLAST_DATABASE_ORGANISM_PARAM_NAME = 'BlastDatabaseOrganism';
-export const BLAST_DATABASE_TYPE_PARAM_NAME = 'MultiBlastDatabaseType';
-export const BLAST_QUERY_SEQUENCE_PARAM_NAME = 'BlastQuerySequence';
-export const BLAST_ALGORITHM_PARAM_NAME = 'BlastAlgorithm';
-export const JOB_DESCRIPTION_PARAM_NAME = 'BlastJobDescription';
-
-// General config for all BLAST applications
-export const EXPECTATION_VALUE_PARAM_NAME = 'ExpectationValue';
-export const NUM_QUERY_RESULTS_PARAM_NAME = 'NumQueryResults';
-export const MAX_MATCHES_QUERY_RANGE_PARAM_NAME = 'MaxMatchesQueryRange';
-
-// General config specific to each BLAST application
-export const WORD_SIZE_PARAM_NAME = 'WordSize';
-export const SCORING_MATRIX_PARAM_NAME = 'ScoringMatrix';
-export const COMP_ADJUST_PARAM_NAME = 'CompAdjust';
-
-// Filter and masking config
-export const FILTER_LOW_COMPLEX_PARAM_NAME = 'FilterLowComplex';
-export const SOFT_MASK_PARAM_NAME = 'SoftMask';
-export const LOWER_CASE_MASK_PARAM_NAME = 'LowerCaseMask';
-
-// Scoring config
-export const GAP_COSTS_PARAM_NAME = 'GapCosts';
-export const MATCH_MISMATCH_SCORE = 'MatchMismatchScore';
+import { IOQueryJobDetails } from './api/query/types/ep-jobs-by-id';
+import { IOJobTarget } from './api/query/types/common';
+import { blastConfigToParamValues } from './params-from-query-config';
export const ADVANCED_PARAMS_GROUP_NAME = 'advancedParams';
export const OMIT_PARAM_TERM = 'none';
+export const ParamNames = {
+ BlastDatabaseOrganism: 'BlastDatabaseOrganism',
+ BlastDatabaseType: 'MultiBlastDatabaseType',
+ BlastQuerySequence: 'BlastQuerySequence',
+ BlastAlgorithm: 'BlastAlgorithm',
+ JobDescription: 'BlastJobDescription',
+
+ // General config for all BLAST applications
+ ExpectValue: 'ExpectationValue',
+ NumQueryResults: 'NumQueryResults',
+ MaxMatchesQueryRange: 'MaxMatchesQueryRange',
+
+ // General config specific to each BLAST application
+ WordSize: 'WordSize',
+ ScoringMatrix: 'ScoringMatrix',
+ CompAdjust: 'CompAdjust',
+
+ // Filter and masking config
+ FilterLowComplexity: 'FilterLowComplex',
+ SoftMask: 'SoftMask',
+ LowercaseMask: 'LowerCaseMask',
+
+ // Scoring config
+ GapCosts: 'GapCosts',
+ MatchMismatch: 'MatchMismatchScore',
+} as const;
+
export function isOmittedParam(param?: Parameter) {
return param == null || !isEnumParam(param) || param.displayType === 'treeBox'
? false
@@ -63,249 +56,25 @@ export function isOmittedParam(param?: Parameter) {
param.vocabulary[0][0] === OMIT_PARAM_TERM;
}
-/**
- * This function transforms the parameter values of a multi-blast WDK question
- * into a valid job configuration for the multi-blast service.
- *
- * NOTE: This logic is mirrored in
- *
- * EbrcWebSvcCommon/WSFPlugin/src/main/java/org/apidb/apicomplexa/wsfplugin/blast/MultiblastServiceParams.java
- *
- * The two must be kept in sync so unexpected results are not shown in the
- * multi-blast UI and so users get the same result when they export to WDK.
- *
- * @author jtlong
- */
-export function paramValuesToBlastConfig(
- paramValues: ParameterValues
-): IoBlastConfig {
- const {
- [BLAST_QUERY_SEQUENCE_PARAM_NAME]: query,
- [BLAST_ALGORITHM_PARAM_NAME]: selectedTool,
- [EXPECTATION_VALUE_PARAM_NAME]: eValue,
- [NUM_QUERY_RESULTS_PARAM_NAME]: numQueryResultsStr,
- [MAX_MATCHES_QUERY_RANGE_PARAM_NAME]: maxMatchesStr,
- [WORD_SIZE_PARAM_NAME]: wordSizeStr,
- [SCORING_MATRIX_PARAM_NAME]: scoringMatrixStr,
- [COMP_ADJUST_PARAM_NAME]: compBasedStats,
- [FILTER_LOW_COMPLEX_PARAM_NAME]: filterLowComplexityRegionsStr,
- [SOFT_MASK_PARAM_NAME]: softMaskStr,
- [LOWER_CASE_MASK_PARAM_NAME]: lowerCaseMaskStr,
- [GAP_COSTS_PARAM_NAME]: gapCostsStr,
- [MATCH_MISMATCH_SCORE]: matchMismatchStr,
- } = paramValues;
-
- const maxHSPsConfig =
- Number(maxMatchesStr) >= 1 ? { maxHSPs: Number(maxMatchesStr) } : {};
-
- const baseConfig = {
- query,
- eValue,
- maxTargetSeqs: Number(numQueryResultsStr),
- wordSize: Number(wordSizeStr),
- softMasking: stringToBoolean(softMaskStr),
- lcaseMasking: stringToBoolean(lowerCaseMaskStr),
- outFormat: {
- format: 'single-file-json',
- },
- ...maxHSPsConfig,
- } as const;
-
- const filterLowComplexityRegions =
- filterLowComplexityRegionsStr !== 'no filter';
-
- const dustConfig: { dust: IoBlastNDust } = {
- dust: filterLowComplexityRegions ? 'yes' : 'no',
- };
-
- const segConfig: { seg: IoBlastSegMask } = {
- seg: filterLowComplexityRegions ? 'yes' : 'no',
- };
-
- const [reward, penalty] = (matchMismatchStr ?? '').split(',').map(Number);
- const [gapOpen, gapExtend] = (gapCostsStr ?? '').split(',').map(Number);
-
- if (selectedTool === 'blastn') {
- return {
- tool: selectedTool,
- task: selectedTool,
- ...baseConfig,
- ...dustConfig,
- reward,
- penalty,
- gapOpen,
- gapExtend,
- };
- }
-
- if (selectedTool === 'blastp') {
- return {
- tool: selectedTool,
- task: selectedTool,
- ...baseConfig,
- ...segConfig,
- gapOpen,
- gapExtend,
- matrix: scoringMatrixStr as IOBlastPScoringMatrix,
- compBasedStats: compBasedStats as IoBlastCompBasedStats,
- };
- }
-
- if (selectedTool === 'blastx') {
- return {
- tool: selectedTool,
- queryGeneticCode: 1,
- task: selectedTool,
- ...baseConfig,
- ...segConfig,
- gapOpen,
- gapExtend,
- matrix: scoringMatrixStr as IOBlastXScoringMatrix,
- compBasedStats: compBasedStats as IoBlastCompBasedStats,
- };
- }
-
- if (selectedTool === 'tblastn') {
- return {
- tool: selectedTool,
- task: selectedTool,
- ...baseConfig,
- ...segConfig,
- gapOpen,
- gapExtend,
- matrix: scoringMatrixStr as IOTBlastNScoringMatrix,
- compBasedStats: compBasedStats as IoBlastCompBasedStats,
- };
- }
-
- if (selectedTool === 'tblastx') {
- return {
- tool: selectedTool,
- queryGeneticCode: 1,
- ...baseConfig,
- ...segConfig,
- matrix: scoringMatrixStr as IOTBlastXScoringMatrix,
- };
- }
-
- throw new Error(`The BLAST tool '${selectedTool}' is not supported.`);
-}
-
-export function blastConfigToParamValues(
- blastConfig: IoBlastConfig
-): ParameterValues {
- const parameterValues: ParameterValues = {
- [BLAST_ALGORITHM_PARAM_NAME]: blastConfig.tool,
- };
-
- if (blastConfig.eValue != null) {
- parameterValues[EXPECTATION_VALUE_PARAM_NAME] = blastConfig.eValue;
- }
-
- if (
- blastConfig.maxTargetSeqs != null ||
- blastConfig.numDescriptions != null ||
- blastConfig.numAlignments != null
- ) {
- const resultSetBound = Math.min(
- blastConfig.maxTargetSeqs ?? Infinity,
- Math.max(
- blastConfig.numDescriptions ?? Infinity,
- blastConfig.numAlignments ?? Infinity
- )
- );
-
- parameterValues[NUM_QUERY_RESULTS_PARAM_NAME] = String(resultSetBound);
- }
-
- if (blastConfig.maxHSPs != null) {
- parameterValues[MAX_MATCHES_QUERY_RANGE_PARAM_NAME] = String(
- blastConfig.maxHSPs
- );
- }
-
- if (blastConfig.wordSize != null) {
- parameterValues[WORD_SIZE_PARAM_NAME] = String(blastConfig.wordSize);
- }
-
- if (blastConfig.tool !== 'blastn' && blastConfig.matrix != null) {
- parameterValues[SCORING_MATRIX_PARAM_NAME] = blastConfig.matrix;
- }
-
- if (
- blastConfig.tool !== 'blastn' &&
- blastConfig.tool !== 'tblastx' &&
- blastConfig.compBasedStats != null
- ) {
- parameterValues[COMP_ADJUST_PARAM_NAME] = blastConfig.compBasedStats;
- }
-
- if (blastConfig.tool === 'blastn') {
- parameterValues[FILTER_LOW_COMPLEX_PARAM_NAME] =
- blastConfig.dust === 'yes' || typeof blastConfig.dust === 'object'
- ? 'dust'
- : 'no filter';
- }
-
- if (blastConfig.tool !== 'blastn') {
- parameterValues[FILTER_LOW_COMPLEX_PARAM_NAME] =
- blastConfig.seg === 'yes' || typeof blastConfig.seg === 'object'
- ? 'seg'
- : 'no filter';
- }
-
- if (blastConfig.softMasking != null) {
- parameterValues[SOFT_MASK_PARAM_NAME] = String(blastConfig.softMasking);
- }
-
- if (blastConfig.lcaseMasking != null) {
- parameterValues[LOWER_CASE_MASK_PARAM_NAME] = String(
- blastConfig.lcaseMasking
- );
- }
-
- if (
- blastConfig.tool !== 'tblastx' &&
- blastConfig.gapOpen != null &&
- blastConfig.gapExtend != null
- ) {
- parameterValues[
- GAP_COSTS_PARAM_NAME
- ] = `${blastConfig.gapOpen},${blastConfig.gapExtend}`;
- }
-
- if (
- blastConfig.tool === 'blastn' &&
- blastConfig.reward != null &&
- blastConfig.penalty != null
- ) {
- parameterValues[
- MATCH_MISMATCH_SCORE
- ] = `${blastConfig.reward},${blastConfig.penalty}`;
- }
-
- return parameterValues;
-}
-
export function targetsToOrganismParamValue(
- targets: Target[],
+ targets: IOJobTarget[],
dirsToOrganisms: Record
) {
const selectedOrganisms = targets
- .map((target) => dirsToOrganisms[target.organism])
+ .map((target) => dirsToOrganisms[target.targetDisplayName])
.filter((organism): organism is string => organism != null);
return toMultiValueString(selectedOrganisms);
}
export function reportToParamValues(
- jobDetails: LongJobResponse,
+ jobDetails: IOQueryJobDetails,
query: string,
targetTypeTerm: string,
- targets: Target[],
+ targets: IOJobTarget[],
dirsToOrganisms: Record
): ParameterValues {
- const configParamValues = blastConfigToParamValues(jobDetails.config);
+ const configParamValues = blastConfigToParamValues(jobDetails.blastConfig);
const organismParamValue = targetsToOrganismParamValue(
targets,
@@ -314,10 +83,10 @@ export function reportToParamValues(
return {
...configParamValues,
- [JOB_DESCRIPTION_PARAM_NAME]: jobDetails.description ?? '',
- [BLAST_QUERY_SEQUENCE_PARAM_NAME]: query,
- [BLAST_DATABASE_ORGANISM_PARAM_NAME]: organismParamValue,
- [BLAST_DATABASE_TYPE_PARAM_NAME]: targetTypeTerm,
+ [ParamNames.JobDescription]: jobDetails.userMeta?.summary ?? '',
+ [ParamNames.BlastQuerySequence]: query,
+ [ParamNames.BlastDatabaseOrganism]: organismParamValue,
+ [ParamNames.BlastDatabaseType]: targetTypeTerm,
};
}
@@ -332,10 +101,6 @@ export function organismParamValueToFilenames(
.map(([, organismFile]) => organismFile);
}
-function stringToBoolean(str: string) {
- return str === 'true';
-}
-
export function transformOrganismParameter(
parameter: Parameter,
targetRecordType: string
diff --git a/packages/sites/genomics-site/webpack.config.local.mjs b/packages/sites/genomics-site/webpack.config.local.mjs
index cc902d8c91..7e66f64bdf 100644
--- a/packages/sites/genomics-site/webpack.config.local.mjs
+++ b/packages/sites/genomics-site/webpack.config.local.mjs
@@ -20,7 +20,6 @@ export default configure({
[process.env.WDK_SERVICE_ENDPOINT]: process.env.WDK_SERVICE_URL,
[process.env.SITE_SEARCH_SERVICE_ENDPOINT]: process.env.SITE_SEARCH_SERVICE_URL,
[process.env.EDA_SERVICE_ENDPOINT]: process.env.EDA_SERVICE_URL,
- [process.env.MULTI_BLAST_ENDPOINT]: process.env.MULTI_BLAST_URL,
[process.env.USER_DATASETS_WORKSPACE_IMPORT_SERVICE_ENDPOINT]: process.env.USER_DATASETS_WORKSPACE_IMPORT_SERVICE_URL,
[process.env.DOCUMENTS_ENDPOINT]: process.env.DOCUMENTS_URL,
[process.env.ASSETS_ENDPOINT]: process.env.ASSETS_URL,