From 78ec06f0a6357dda1bdc5cf12aa43d93963c9f68 Mon Sep 17 00:00:00 2001 From: Dave Falke Date: Tue, 24 Sep 2024 10:10:05 -0400 Subject: [PATCH] Add diamond support to multi-blast, and enable on ortho-site (#1197) * Attempt to get local dev working for multi-blast * Upgrade recoil to work with React 18 types * Wire multi-blast in ortho-site * Make blastOntologyDatabase optional, and add magic string to indicate all algorithm options are available. * Update search name * Handle optional blastOntologyDatabase property * Add options for custom workspace header, and remove hardcoded path. * Add new route for map-proteins * add multi-blast vars to sample file * Handle empty advanced params, allow file-only sequence param, and use file-based request if a file is provided. * Add diamond config types and mappings with wdk search parameter values * eValue should be a number for diamond * Only assign parameter values when the config value is defined * Update link in header * Update workspace heading * Make link relative * Add generic method to fetch a specific report file * Add support for customizing verbiage * Reduce margin * Add method to get download url for a file * Relativize links target can be null * target can be null * Add result view for diamond jobs * go to new form for map-proteins * fix issues with multi-blast local dev site * Make help tab configurable * Request headers and use for preview and download * Make paths relative to workspace root * Update comment * Map stitle to new columns * Add support for AbortSignal to usePromise * Handle AbortSignal --- packages/libs/multi-blast/config-overrides.js | 41 +++- packages/libs/multi-blast/package.json | 7 +- packages/libs/multi-blast/src/index.tsx | 27 ++- .../src/lib/components/BlastForm.scss | 6 +- .../src/lib/components/BlastForm.tsx | 129 +++++++---- .../src/lib/components/BlastWorkspace.tsx | 26 ++- .../src/lib/components/BlastWorkspaceHelp.tsx | 20 +- .../lib/components/BlastWorkspaceResult.scss | 9 + .../lib/components/BlastWorkspaceResult.tsx | 143 +++++++----- .../lib/components/DiamondResultContainer.tsx | 214 ++++++++++++++++++ .../lib/controllers/BlastWorkspaceRouter.tsx | 19 +- .../multi-blast/src/lib/hooks/allJobs.tsx | 6 +- .../src/lib/hooks/blastAlgorithms.tsx | 147 ++++++------ .../src/lib/hooks/combinedResults.tsx | 6 +- .../src/lib/hooks/individualResult.tsx | 6 +- .../libs/multi-blast/src/lib/hooks/params.ts | 18 +- .../multi-blast/src/lib/utils/ServiceTypes.ts | 61 ++++- .../libs/multi-blast/src/lib/utils/api.ts | 27 ++- .../libs/multi-blast/src/lib/utils/params.ts | 71 +++++- .../multi-blast/src/lib/utils/targetTypes.ts | 12 +- .../libs/preferred-organisms/package.json | 2 +- .../src/lib/utils/preferredOrganisms.ts | 2 +- .../libs/wdk-client/src/Hooks/PromiseHook.ts | 12 +- packages/sites/genomics-site/.env.sample | 3 + packages/sites/genomics-site/package.json | 2 +- .../js/client/blastRoutes.tsx | 3 +- packages/sites/ortho-site/.env.sample | 3 + packages/sites/ortho-site/package.json | 1 + .../js/client/blastRoutes.tsx | 47 ++++ .../client/components/layout/OrthoMCLPage.tsx | 4 +- .../controllers/BlastWorkspaceResult.tsx | 2 + .../controllers/BlastWorkspaceRouter.tsx | 2 + .../js/client/pluginConfig.tsx | 25 ++ .../js/client/plugins/BlastForm.tsx | 2 + .../plugins/BlastQuestionController.tsx | 2 + .../js/client/proteinMappingRoutes.tsx | 34 +++ .../wdkCustomization/js/client/routes.tsx | 5 + .../wdkCustomization/js/client/services.tsx | 4 +- .../sites/ortho-site/webpack.config.local.mjs | 1 + yarn.lock | 17 +- 40 files changed, 911 insertions(+), 257 deletions(-) create mode 100644 packages/libs/multi-blast/src/lib/components/DiamondResultContainer.tsx create mode 100644 packages/sites/ortho-site/webapp/wdkCustomization/js/client/blastRoutes.tsx create mode 100644 packages/sites/ortho-site/webapp/wdkCustomization/js/client/controllers/BlastWorkspaceResult.tsx create mode 100644 packages/sites/ortho-site/webapp/wdkCustomization/js/client/controllers/BlastWorkspaceRouter.tsx create mode 100644 packages/sites/ortho-site/webapp/wdkCustomization/js/client/plugins/BlastForm.tsx create mode 100644 packages/sites/ortho-site/webapp/wdkCustomization/js/client/plugins/BlastQuestionController.tsx create mode 100644 packages/sites/ortho-site/webapp/wdkCustomization/js/client/proteinMappingRoutes.tsx diff --git a/packages/libs/multi-blast/config-overrides.js b/packages/libs/multi-blast/config-overrides.js index 501f171fce..c061efdf7e 100644 --- a/packages/libs/multi-blast/config-overrides.js +++ b/packages/libs/multi-blast/config-overrides.js @@ -1,4 +1,4 @@ -const path = require('path'); +const webpack = require('webpack'); /* * The following gives preference to the project's root node_modules directory when loading modules and loaders. @@ -10,18 +10,37 @@ module.exports = function override(config, env) { ...config, resolve: { ...config.resolve, - modules: [ - path.join(__dirname, 'node_modules'), - ...(config.resolve.modules || ['node_modules']), - ], + fallback: { + path: 'path-browserify', + stream: 'stream-browserify', + querystring: 'querystring-es3', + assert: 'assert', + buffer: 'buffer', + fs: false, + }, }, - resolveLoader: { - ...config.resolveLoader, - modules: [ - path.join(__dirname, 'node_modules'), - ...(config.resolveLoader.modules || ['node_modules']), + plugins: [ + ...config.plugins, + new webpack.ProvidePlugin({ + // This is needed by shape2geohash, used by MapVEuMap + process: 'process/browser', + }), + ], + externals: [{ jquery: 'jQuery' }], + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + test: /\.(js|jsx|ts|tsx)$/, + exclude: /node_modules\/(?!(@veupathdb))/, + enforce: 'pre', + use: 'source-map-loader', + }, ], }, - externals: [{ jquery: 'jQuery' }], + snapshot: { + managedPaths: [], + }, }; }; diff --git a/packages/libs/multi-blast/package.json b/packages/libs/multi-blast/package.json index e105dc2848..93a00cf200 100644 --- a/packages/libs/multi-blast/package.json +++ b/packages/libs/multi-blast/package.json @@ -12,7 +12,8 @@ "src/lib" ], "sideEffects": [ - "*.scss" + "*.scss", + "src/globals.js" ], "dependencies": { "@veupathdb/coreui": "workspace:^", @@ -99,9 +100,9 @@ "react-redux": "^7.2.6", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "5.0.1", "read": "^1.0.7", - "recoil": "^0.2.0", + "recoil": "^0.7.7", "redux": "^4.0.0", "sass": "^1.56.2", "script-loader": "^0.7.2", diff --git a/packages/libs/multi-blast/src/index.tsx b/packages/libs/multi-blast/src/index.tsx index a11052b4a0..a783f493bb 100644 --- a/packages/libs/multi-blast/src/index.tsx +++ b/packages/libs/multi-blast/src/index.tsx @@ -1,15 +1,22 @@ import './globals'; + +// eslint-disable-next-line import/no-webpack-loader-syntax +import '!!script-loader!@veupathdb/wdk-client/vendored/jquery'; +// eslint-disable-next-line import/no-webpack-loader-syntax +import '!!script-loader!@veupathdb/wdk-client/vendored/jquery-migrate-1.2.1'; +// eslint-disable-next-line import/no-webpack-loader-syntax +import '!!script-loader!@veupathdb/wdk-client/vendored/jquery-ui'; +// eslint-disable-next-line import/no-webpack-loader-syntax +import '!!script-loader!@veupathdb/wdk-client/vendored/jquery.qtip.min'; + import React, { Suspense } from 'react'; import { Redirect, RouteComponentProps } from 'react-router'; import { RecoilRoot } from 'recoil'; -import { initialize } from '@veupathdb/web-common/lib/bootstrap'; -import { - BlastSummaryViewPlugin, - GenomeSummaryViewPlugin, - ResultTableSummaryViewPlugin, -} from '@veupathdb/wdk-client/lib/Plugins'; +import { initialize } from '@veupathdb/wdk-client'; +import { ResultTableSummaryViewPlugin } from '@veupathdb/wdk-client/lib/Plugins'; import { RouteEntry } from '@veupathdb/wdk-client/lib/Core/RouteEntry'; import { ClientPluginRegistryEntry } from '@veupathdb/wdk-client/lib/Utils/ClientPlugin'; +import BlastSummaryViewPlugin from '@veupathdb/blast-summary-view/lib/Controllers/BlastSummaryViewController'; import { OrganismParam, @@ -28,9 +35,6 @@ import { BlastWorkspaceRouter } from './lib/controllers/BlastWorkspaceRouter'; import { isMultiBlastQuestion } from './lib/utils/pluginConfig'; import { wrapWdkService } from './lib/utils/wdkServiceIntegration'; -import '@veupathdb/wdk-client/lib/Core/Style/index.scss'; -import '@veupathdb/web-common/lib/styles/client.scss'; - import './index.css'; initialize({ @@ -93,11 +97,6 @@ initialize({ name: '_default', component: ResultTableSummaryViewPlugin, }, - { - type: 'summaryView', - name: 'genomic-view', - component: GenomeSummaryViewPlugin, - }, { type: 'summaryView', name: 'blast-view', diff --git a/packages/libs/multi-blast/src/lib/components/BlastForm.scss b/packages/libs/multi-blast/src/lib/components/BlastForm.scss index 46dbcad544..2e2417ea27 100644 --- a/packages/libs/multi-blast/src/lib/components/BlastForm.scss +++ b/packages/libs/multi-blast/src/lib/components/BlastForm.scss @@ -1,12 +1,16 @@ .wdk-QuestionForm { &MultiBlast { + > * + * { + margin-block-start: 1em; + } + .SequenceParam { display: inline-flex; flex-direction: column; gap: 1em; &Instructions { - font-size: 1.2em; + font-size: 1.1em; } textarea { diff --git a/packages/libs/multi-blast/src/lib/components/BlastForm.tsx b/packages/libs/multi-blast/src/lib/components/BlastForm.tsx index 2536fa2a31..1f772281fc 100644 --- a/packages/libs/multi-blast/src/lib/components/BlastForm.tsx +++ b/packages/libs/multi-blast/src/lib/components/BlastForm.tsx @@ -1,5 +1,4 @@ import { - ChangeEvent, FormEvent, useCallback, useContext, @@ -23,11 +22,15 @@ import { TextArea, } from '@veupathdb/wdk-client/lib/Components'; import { WdkDependenciesContext } from '@veupathdb/wdk-client/lib/Hooks/WdkDependenciesEffect'; -import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils'; +import { + makeClassNameHelper, + safeHtml, +} from '@veupathdb/wdk-client/lib/Utils/ComponentUtils'; import { scrollIntoView } from '@veupathdb/wdk-client/lib/Utils/DomUtils'; import { Parameter, ParameterGroup, + SelectEnumParam, } from '@veupathdb/wdk-client/lib/Utils/WdkModel'; import DefaultQuestionForm, { ParameterList, @@ -38,7 +41,6 @@ import DefaultQuestionForm, { import { Plugin } from '@veupathdb/wdk-client/lib/Utils/ClientPlugin'; import { makeParamDependenciesUpdating, - useChangeParamValue, useDependentParamsAreUpdating, } from '@veupathdb/wdk-client/lib/Views/Question/Params/Utils'; @@ -74,6 +76,7 @@ import { AdvancedParamGroup } from './AdvancedParamGroup'; import { BlastFormValidationInfo } from './BlastFormValidationInfo'; import './BlastForm.scss'; +import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; export const blastFormCx = makeClassNameHelper('wdk-QuestionForm'); @@ -86,6 +89,10 @@ export interface Props extends DefaultQuestionFormProps { isMultiBlast?: boolean; } +interface TransformedProps extends Props { + originalQuestion: Props['state']['question']; +} + export function BlastForm(props: Props) { const targetType = props.state.paramValues[BLAST_DATABASE_TYPE_PARAM_NAME]; const targetMetadataByDataType = useContext(TargetMetadataByDataType); @@ -101,54 +108,47 @@ export function BlastForm(props: Props) { ); } -function BlastFormWithTransformedQuestion(props: Props) { +function BlastFormWithTransformedQuestion(props: TransformedProps) { const canChangeRecordType = props.canChangeRecordType ?? false; const targetType = props.state.paramValues[BLAST_DATABASE_TYPE_PARAM_NAME]; + const blastAlgorithmParameter = props.state.question.parametersByName[ + BLAST_ALGORITHM_PARAM_NAME + ] as SelectEnumParam; + const selectedBlastAlgorithm = props.state.paramValues[BLAST_ALGORITHM_PARAM_NAME]; + const selectedBlastAlgorithmDisplay = + blastAlgorithmParameter.vocabulary.find( + ([term]) => term === selectedBlastAlgorithm + )?.[1] ?? selectedBlastAlgorithm; + const restrictedAdvancedParamGroup = useMemo(() => { const fullAdvancedParamGroup = props.state.question.groupsByName[ADVANCED_PARAMS_GROUP_NAME]; + if (fullAdvancedParamGroup == null) { + return undefined; + } + return { ...fullAdvancedParamGroup, - displayName: `Advanced ${selectedBlastAlgorithm.toUpperCase()} Parameters`, + displayName: `Advanced ${selectedBlastAlgorithmDisplay.toUpperCase()} Parameters`, parameters: fullAdvancedParamGroup.parameters.filter( (paramName) => !isOmittedParam(props.state.question.parametersByName[paramName]) ), }; }, [ - selectedBlastAlgorithm, + selectedBlastAlgorithmDisplay, props.state.question.groupsByName, props.state.question.parametersByName, ]); const enabledAlgorithms = useEnabledAlgorithms(targetType); - const updateQueryParam = useChangeParamValue( - props.state.question.parametersByName[BLAST_QUERY_SEQUENCE_PARAM_NAME], - props.state, - props.eventHandlers.updateParamValue - ); - - const onQueryFileInputChanged = useCallback( - (event: ChangeEvent) => { - const file = - event.target.files == null || event.target.files.length === 0 - ? null - : event.target.files[0]; - - if (file != null) { - file.text().then(updateQueryParam); - } - }, - [updateQueryParam] - ); - const defaultAdvancedParamsMetadata = useDefaultAdvancedParams( props.state.question ); @@ -208,6 +208,11 @@ function BlastFormWithTransformedQuestion(props: Props) { props.eventHandlers.updateParamValue ); + const enableSequenceTextArea = + !props.state.question.parametersByName[ + BLAST_QUERY_SEQUENCE_PARAM_NAME + ]?.properties?.['multiBlastOptions']?.includes('fileOnly'); + const targetParamElement = (
- {props.isMultiBlast - ? 'Paste one or several sequences, or upload a FASTA file.' - : 'Paste one sequence, or upload a one-sequence FASTA file.'} + {props.originalQuestion.parametersByName[ + BLAST_QUERY_SEQUENCE_PARAM_NAME + ]?.visibleHelp ?? + (enableSequenceTextArea + ? props.isMultiBlast + ? 'Paste one or several sequences, or upload a FASTA file. If both are provided, the file will be used.' + : 'Paste one sequence, or upload a one-sequence FASTA file. If both are provided, the file will be used.' + : props.isMultiBlast + ? 'Upload a FASTA file.' + : 'Upload a one-sequence FASTA file.')}
-