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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/frontend/static/frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export default defineConfig([
'no-undef': 'off',
camelcase: 'off',
curly: ['error', 'all'],
'no-restricted-syntax': [
'warn',
{
selector: ':matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression)[async=true]',
message: 'Prefer promise chains with .then()/.catch() over async/await and try/catch for asynchronous operations.'
}
],
'no-multi-spaces': 'error',
'padding-line-between-statements': [
'warn',
Expand Down
34 changes: 19 additions & 15 deletions src/frontend/static/frontend/src/components/assistant/ChatPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,23 +274,27 @@ const ChatPanel = (props: ChatPanelProps) => {
}

loadConversations()
}).catch(async err => {
let detail = err?.message ?? String(err)

if (err?.response) {
try {
const body = await err.response.json()
detail = body?.detail ?? body?.error ?? JSON.stringify(body)
} catch {
detail = `HTTP ${err.response.status}`
}
}).catch(err => {
const appendErrorMessage = (detail: string) => {
console.error('Error sending message:', detail, err)
setMessages(prev => [...prev, {
role: 'assistant',
content: `Error: ${detail}`,
}])
}

if (!err?.response) {
appendErrorMessage(err?.message ?? String(err))
return
}

console.error('Error sending message:', detail, err)
setMessages(prev => [...prev, {
role: 'assistant',
content: `Error: ${detail}`,
}])
return err.response.json().then(
(body: { detail?: string; error?: string }) => {
const detail = body?.detail ?? body?.error ?? JSON.stringify(body) ?? String(body)
appendErrorMessage(detail)
},
() => appendErrorMessage(`HTTP ${err.response.status}`)
)
}).finally(() => {
setIsLoading(false)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ class BiomarkersPanel extends React.Component<BiomarkersPanelProps, BiomarkersPa
* Method that gets symbols while user is writing in Select molecules input
* @param molecules array of strings that is sending to the api
*/
handleGeneSymbols = async (molecules: string[]): Promise<void> => {
handleGeneSymbols = (molecules: string[]): void => {
this.setState(prevState => {
const moleculesSectionPreload = {
...prevState.formBiomarker.moleculesSection,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { PaginatedTable, PaginationCustomFilter } from '../../common/PaginatedTable'
import { Biomarker, BiomarkerState, TrainedModelForTable, TrainedModelState } from '../types'
import { Biomarker, BiomarkerState, FitnessFunction, TrainedModelForTable, TrainedModelState } from '../types'
import { Button, Form, Header, Icon, Modal, Table } from 'semantic-ui-react'
import { TableCellWithTitle } from '../../common/TableCellWithTitle'
import { alertGeneralError, formatDateLocale, getDjangoHeader } from '../../../utils/util_functions'
Expand All @@ -14,6 +14,7 @@ import { StopExperimentButton } from '../../pipeline/all-experiments-view/StopEx
import { DeleteButton } from '../../common/DeleteButton'
import { TableCellSources } from '../../common/TableCellSources'
import { useIntl } from 'react-intl'
import { InfoPopup } from '../../pipeline/experiment-result/gene-gem-details/InfoPopup'

declare const urlBiomarkerTrainedModels: string
declare const urlStopTrainedModel: string
Expand Down Expand Up @@ -221,8 +222,16 @@ export const BiomarkerTrainedModelsTable = (props: BiomarkerTrainedModelsPanelPr
{ name: intl.formatMessage({ id: 'common.state' }), serverCodeToSort: 'state', width: 1 },
{ name: intl.formatMessage({ id: 'biomarkerTrainedModelsTable.header.model' }), serverCodeToSort: 'fitness_function', width: 1 },
{ name: intl.formatMessage({ id: 'common.date' }), serverCodeToSort: 'created' },
{ name: intl.formatMessage({ id: 'common.metric' }), serverCodeToSort: 'fitness_metric' },
{ name: intl.formatMessage({ id: 'biomarkerTrainedModelsTable.header.bestCVMetric' }), serverCodeToSort: 'best_fitness_value' },
{
name: intl.formatMessage({ id: 'common.metric' }),
serverCodeToSort: 'fitness_metric',
infoPopupContent: intl.formatMessage({ id: 'trainedModels.info.fitnessMetric' })
},
{
name: intl.formatMessage({ id: 'biomarkerTrainedModelsTable.header.bestCVMetric' }),
serverCodeToSort: 'best_fitness_value',
infoPopupContent: intl.formatMessage({ id: 'trainedModels.info.bestScore' })
},
{ name: intl.formatMessage({ id: 'biomarkerTrainedModelsTable.header.datasets' }) },
...actionColumn

Expand Down Expand Up @@ -261,6 +270,11 @@ export const BiomarkerTrainedModelsTable = (props: BiomarkerTrainedModelsPanelPr
mapFunction={(trainedModel: TrainedModelForTable) => {
const isInProcess = trainedModel.state === TrainedModelState.IN_PROCESS ||
trainedModel.state === TrainedModelState.WAITING_FOR_QUEUE
const scoreInfoId = trainedModel.fitness_metric === 'Log Likelihood'
? 'modelDetails.info.bestLogLikelihood'
: trainedModel.fitness_function === FitnessFunction.CLUSTERING
? 'modelDetails.info.bestClusteringCIndex'
: 'modelDetails.info.bestCIndex'

return (
<Table.Row
Expand All @@ -282,7 +296,12 @@ export const BiomarkerTrainedModelsTable = (props: BiomarkerTrainedModelsPanelPr
<Table.Cell><FitnessFunctionLabel fitnessFunction={trainedModel.fitness_function} /></Table.Cell>
<TableCellWithTitle value={formatDateLocale(trainedModel.created as string, 'L')} />
<Table.Cell>{trainedModel.fitness_metric ?? '-'}</Table.Cell>
<Table.Cell>{trainedModel.best_fitness_value ? trainedModel.best_fitness_value.toFixed(4) : '-'}</Table.Cell>
<Table.Cell>
{trainedModel.best_fitness_value != null ? trainedModel.best_fitness_value.toFixed(4) : '-'}
{trainedModel.best_fitness_value != null && (
<InfoPopup content={intl.formatMessage({ id: scoreInfoId })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
)}
</Table.Cell>
<Table.Cell>
<TableCellSources
clinical_source={trainedModel.clinical_source}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { ClusteringScoringMethodLabel } from '../labels/ClusteringScoringMethodL
import { FitnessFunctionLabel } from '../labels/FitnessFunctionLabel'
import { SVMKernelLabel } from '../labels/SVMKernelLabel'
import { SVMKernelTask } from '../labels/SVMKernelTask'
import { FitnessFunction, ClusteringModelDetails, SVMModelDetails, ModelDetails, RFModelDetails } from '../types'
import { FitnessFunction, ClusteringModelDetails, SVMModelDetails, ModelDetails, RFModelDetails, ClusteringScoringMethod } from '../types'
import { Nullable } from '../../../utils/interfaces'
import ky from 'ky'
import { alertGeneralError } from '../../../utils/util_functions'
import { useIntl } from 'react-intl'
import { InfoPopup } from '../../pipeline/experiment-result/gene-gem-details/InfoPopup'

declare const urlStatisticalValidationModalDetails: string

Expand All @@ -23,13 +24,19 @@ type GeneralMetricsProps = { data: ModelDetails, fitness_function: FitnessFuncti
*/
const GeneralMetrics = (props: GeneralMetricsProps) => {
const intl = useIntl()
const bestScoreInfoId = props.fitness_function === FitnessFunction.CLUSTERING
? (props.data as ClusteringModelDetails).scoring_method === ClusteringScoringMethod.LOG_LIKELIHOOD
? 'modelDetails.info.bestLogLikelihood'
: 'modelDetails.info.bestClusteringCIndex'
: 'modelDetails.info.bestCIndex'
return (
<>
<List.Item>
<List.Icon name='cogs' size='large' verticalAlign='middle' />
<List.Content>
<List.Header>
{intl.formatMessage({ id: 'common.model' })}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.model' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />

<FitnessFunctionLabel
fluid={false}
Expand All @@ -43,7 +50,8 @@ const GeneralMetrics = (props: GeneralMetricsProps) => {
<List.Icon name='star outline' size='large' verticalAlign='middle' />
<List.Content>
<List.Header>
{intl.formatMessage({ id: 'modelDetails.general.bestFitnessValue' })} {props.data.best_fitness.toFixed(4)}
{intl.formatMessage({ id: 'modelDetails.general.bestFitnessValue' })} {props.data.best_fitness != null ? props.data.best_fitness.toFixed(4) : '-'}
<InfoPopup content={intl.formatMessage({ id: bestScoreInfoId })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
</List.Header>
</List.Content>
</List.Item>
Expand All @@ -52,6 +60,7 @@ const GeneralMetrics = (props: GeneralMetricsProps) => {
<List.Content>
<List.Header>
{intl.formatMessage({ id: 'common.randomState' })} {props.data.random_state ?? '-'}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.randomState' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
</List.Header>
</List.Content>
</List.Item>
Expand All @@ -78,6 +87,7 @@ const ClusteringModelDetailsPanel = (props: ClusteringModelDetailsPanelProps) =>
<List.Content>
<List.Header>
{intl.formatMessage({ id: 'common.algorithm' })}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.algorithm' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />

<ClusteringAlgorithmLabel clusteringAlgorithm={props.data.algorithm} className='margin-left-2' />
</List.Header>
Expand All @@ -88,6 +98,7 @@ const ClusteringModelDetailsPanel = (props: ClusteringModelDetailsPanelProps) =>
<List.Content>
<List.Header>
{intl.formatMessage({ id: 'common.scoringMethod' })}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.scoringMethod' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />

<ClusteringScoringMethodLabel scoreMethod={props.data.scoring_method} className='margin-left-2' />
</List.Header>
Expand All @@ -96,7 +107,10 @@ const ClusteringModelDetailsPanel = (props: ClusteringModelDetailsPanelProps) =>
<List.Item>
<List.Icon name='grid layout' size='large' verticalAlign='middle' />
<List.Content>
<List.Header>{intl.formatMessage({ id: 'common.nClusters' })} {props.data.n_clusters}</List.Header>
<List.Header>
{intl.formatMessage({ id: 'common.nClusters' })} {props.data.n_clusters}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.nClusters' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
</List.Header>
</List.Content>
</List.Item>
</List>
Expand All @@ -122,6 +136,7 @@ const SVMModelDetailsPanel = (props: SVMModelDetailsPanelProps) => {
<List.Content>
<List.Header>
Kernel:
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.kernel' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />

<SVMKernelLabel kernel={props.data.kernel} className='margin-left-2' />
</List.Header>
Expand All @@ -132,6 +147,7 @@ const SVMModelDetailsPanel = (props: SVMModelDetailsPanelProps) => {
<List.Content>
<List.Header>
{intl.formatMessage({ id: 'modelDetails.svm.task' })}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.task' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />

<SVMKernelTask task={props.data.task} className='margin-left-2' />
</List.Header>
Expand All @@ -158,13 +174,19 @@ const RFModelDetailsPanel = (props: RFModelDetailsPanelProps) => {
<List.Item>
<List.Icon name='tree' size='large' verticalAlign='middle' />
<List.Content>
<List.Header>{intl.formatMessage({ id: 'modelDetails.rf.numberOfEstimators' })} {props.data.n_estimators}</List.Header>
<List.Header>
{intl.formatMessage({ id: 'modelDetails.rf.numberOfEstimators' })} {props.data.n_estimators}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.nEstimators' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
</List.Header>
</List.Content>
</List.Item>
<List.Item>
<List.Icon name='angle double down' size='large' verticalAlign='middle' />
<List.Content>
<List.Header>{intl.formatMessage({ id: 'modelDetails.rf.maxDepth' })} {props.data.max_depth ?? '-'}</List.Header>
<List.Header>
{intl.formatMessage({ id: 'modelDetails.rf.maxDepth' })} {props.data.max_depth ?? '-'}
<InfoPopup content={intl.formatMessage({ id: 'modelDetails.info.maxDepth' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
</List.Header>
</List.Content>
</List.Item>
</List>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export const InferenceExperimentsTable = (props: InferenceExperimentsTableProps)
{/* Modal to confirm deleting the InferenceExperiment */}
{getDeletionConfirmModal()}
<PaginatedTable<InferenceExperimentForTable>
headerTitle={intl.formatMessage({ id: 'inference.experiments.table.title' })}
headerTitle={intl.formatMessage({ id: 'inference.table.title' })}
headers={[
{ name: intl.formatMessage({ id: 'common.name' }), serverCodeToSort: 'name', width: 3 },
{ name: intl.formatMessage({ id: 'common.description' }), serverCodeToSort: 'description', width: 4 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ export const SamplesAndGroupsInferenceTable = (props: SamplesAndGroupsInferenceT
<PaginatedTable<SampleAndCluster>
headers={[
{ name: intl.formatMessage({ id: 'common.sample' }), serverCodeToSort: 'sample', width: 3, textAlign: 'center' },
{ name: intl.formatMessage({ id: 'common.cluster' }), serverCodeToSort: 'cluster', width: 2, textAlign: 'center' }
{
name: intl.formatMessage({ id: 'common.cluster' }),
serverCodeToSort: 'cluster',
width: 2,
textAlign: 'center',
infoPopupContent: intl.formatMessage({ id: 'metricInfo.clusterId' })
}

]}
queryParams={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ResultPlaceholder } from '../stat-validations/result/ResultPlaceholder'
import { BoxPlotChart } from '../../../pipeline/experiment-result/gene-gem-details/stats/BoxPlotChart'
import { COLOR_YELLOW_FILL, COLOR_YELLOW_STROKE } from '../../../../utils/constants'
import { useIntl } from 'react-intl'
import { InfoPopup } from '../../../pipeline/experiment-result/gene-gem-details/InfoPopup'

declare const urlClinicalSourceAddOrEditInferenceExperiment: string
declare const urlUnlinkClinicalSourceInferenceExperiment: string
Expand Down Expand Up @@ -128,6 +129,10 @@ export const SamplesAndTimeInferenceCharts = (props: SamplesAndTimeInferenceChar

return (
<div ref={componentRef} className='align-center'>
<div className='margin-bottom-5'>
{intl.formatMessage({ id: 'inference.timeTable.columns.predictedTime' })}
<InfoPopup content={intl.formatMessage({ id: 'metricInfo.inferencePrediction' })} onTop={false} onEvent='hover' noBorder extraClassName='margin-left-5' />
</div>
<BoxPlotChart
width={width}
dataObjects={chartData.map((dataObj: ChartData) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ export const SamplesAndTimeInferenceTable = (props: SamplesAndTimeInferenceTable
<PaginatedTable<SampleAndTime>
headers={[
{ name: intl.formatMessage({ id: 'common.sample' }), serverCodeToSort: 'sample', width: 3, textAlign: 'center' },
{ name: intl.formatMessage({ id: 'inference.timeTable.columns.predictedTime' }), serverCodeToSort: 'prediction', width: 2, textAlign: 'center' }
{
name: intl.formatMessage({ id: 'inference.timeTable.columns.predictedTime' }),
serverCodeToSort: 'prediction',
width: 2,
textAlign: 'center',
infoPopupContent: intl.formatMessage({ id: 'metricInfo.inferencePrediction' })
}

]}
queryParams={{
Expand Down
Loading
Loading