diff --git a/src/DISK/interfaces.tsx b/src/DISK/interfaces.tsx index f782e6b..5f8aff2 100644 --- a/src/DISK/interfaces.tsx +++ b/src/DISK/interfaces.tsx @@ -165,6 +165,7 @@ export interface TriggeredLineOfInquiry extends LOICommon{ queryResults: DataQueryResult; workflows: WorkflowInstantiation[]; metaWorkflows: WorkflowInstantiation[]; + result: GoalResult | null; } export type Status = 'QUEUED' | 'RUNNING' | 'FAILED' | 'SUCCESSFUL' | 'PENDING'; diff --git a/src/components/DataQuery/DataQueryResultTable.tsx b/src/components/DataQuery/DataQueryResultTable.tsx new file mode 100644 index 0000000..4dbc450 --- /dev/null +++ b/src/components/DataQuery/DataQueryResultTable.tsx @@ -0,0 +1,49 @@ +import { DataQueryResult } from "DISK/interfaces"; +import { SimpleTable } from "components/SimpleTable"; +import { useEffect, useState } from "react"; + +interface DataQueryResultTableProps { + result: DataQueryResult +} + +export const DataQueryResultTable = ({result}:DataQueryResultTableProps) => { + console.log(result.variablesToShow); + const [csv, setCSV] = useState<{[varName: string]: string[]}>({}); + + useEffect(() => { + if (result.results && result.variablesToShow && result.variablesToShow.length > 0) { + let lines = result.results.split('\n'); + let header = true; + let csvMap : {[varName: string]: string[]} = {}; + let indexToName : {[index:number]: string} = {}; + for (const line of lines) { + let cells = line.split(/,\s*/); + if (header) { + header = false; + for (let i = 0; i < cells.length; i++) { + if (result.variablesToShow.some(v => v === "?" + cells[i]) ) { + indexToName[i] = cells[i]; + csvMap[cells[i]] = []; + } + } + } else { + for (let i = 0; i < cells.length; i++) { + if (!!indexToName[i]) { + csvMap[indexToName[i]].push(cells[i]); + } + } + } + } + console.log(csvMap); + setCSV(csvMap); + //FIXME: we are missing some variables of the query here, we need to fix it server side. + } else { + setCSV({}); + } + }, [result]) + + if (Object.keys(csv).length === 0) + return <>; + + return +} \ No newline at end of file diff --git a/src/components/DataQuery/DataQueryResultView.tsx b/src/components/DataQuery/DataQueryResultView.tsx new file mode 100644 index 0000000..4757f3e --- /dev/null +++ b/src/components/DataQuery/DataQueryResultView.tsx @@ -0,0 +1,89 @@ +import { sparql } from "@codemirror/legacy-modes/mode/sparql" +import { Box, IconButton, Divider, Skeleton, Tooltip } from "@mui/material" +import { DataEndpoint, DataQueryResult, Endpoint } from "DISK/interfaces" +import { renderDescription } from "DISK/util" +import { TypographySubtitle, TypographyLabel, TypographyInline, InfoInline, TypographySection } from "components/Styles" +import { Fragment, useEffect, useState } from "react" +import { useGetEndpointsQuery } from "redux/apis/server" +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import CodeMirror from '@uiw/react-codemirror'; +import { StreamLanguage } from '@codemirror/language'; +import { DataQueryResultTable } from "./DataQueryResultTable" + +interface DataQueryResultViewProps { + result: DataQueryResult +} + +export const DataQueryResultView = ({result} : DataQueryResultViewProps) => { + const { data:endpoints, isLoading:loadingEndpoints } = useGetEndpointsQuery(); + const [dataSource, setDataSource] = useState(null); + const [formalView, setFormalView] = useState(false); + + useEffect(() => { + if (endpoints && endpoints.length > 0 && result.endpoint) { + for (const e of endpoints) { + if (e.name === result.endpoint.name) { + setDataSource(e); + return; + } + } + } else { + setDataSource(null); + } + + }, [endpoints, result]) + + return + Data: + + Data source: + {loadingEndpoints ? + + : dataSource?.name && { dataSource.name.replaceAll("_"," ") } + } + + {dataSource?.description && renderDescription(dataSource.description)} + + + + + Data query explanation: + {(result.description ? + {result.description} + : None specified )} + + + setFormalView(!formalView)}> + {formalView? : } + + + + + {formalView && + Data query: + + { + }} + /> + + } + + { (result.results && result.variablesToShow ) && + + + + Input data retrieved: + + {result.footnote && + + Table: + {result.footnote} + } + + + } + +} \ No newline at end of file diff --git a/src/components/ResultTable.tsx b/src/components/ResultTable.tsx index f03de36..a537136 100644 --- a/src/components/ResultTable.tsx +++ b/src/components/ResultTable.tsx @@ -2,6 +2,7 @@ import { Box, Button, Skeleton, Table, TableBody, TableCell, TableContainer, Tab import { DataEndpoint } from "DISK/interfaces"; import { useEffect, useState } from "react"; import { useQueryExternalSourceQuery } from "redux/apis/server"; +import { SimpleTable } from "./SimpleTable"; interface ResultTableProps { query: string; @@ -10,98 +11,16 @@ interface ResultTableProps { indexes?: boolean; } -const MAX_PER_PAGE = 10; - export const ResultTable = ({query, variables, dataSource, indexes = true}: ResultTableProps) => { - const [total, setTotal] = useState(0); - const [nCols, setNCols] = useState(0); - const [curPage, setCurPage] = useState(0); const {data:results,isLoading:waiting,isError} = useQueryExternalSourceQuery({dataSource:dataSource,query:query,variables:variables.split(" ")}); - useEffect(() => { - if (results) { - let cols: number = Object.values(results).length; - setNCols(cols); - setTotal(Object.values(results)[0].length); - } else { - setNCols(0); - setTotal(0); - } - }, [results]); - - const renderText = (txt: string) => { - if (txt) { - let name: string = txt.replace(/.*\//g, "").replace(/.*#/g, ""); - if (txt.startsWith("http")) - return ( - - {name} - - ); - return {name}; - } - }; - return ( {waiting ? ( ) : results && Object.keys(results).length > 0 ? ( - - - - - {indexes && ( - # - )} - {Object.keys(results).map((varname: string) => ( - - {varname} - - ))} - - - - {Array(MAX_PER_PAGE).fill(0) - .map((_, i) => curPage * MAX_PER_PAGE + i) - .map((i) => ( - - {indexes && i < total && ( - - {i + 1} - - )} - {i < total && - Object.values(results).map((values: string[], j) => ( - - {renderText(values[i])} - - ))} - - ))} - - - - - - Showing {curPage * MAX_PER_PAGE} -{" "} - {(curPage + 1) * MAX_PER_PAGE < total - ? (curPage + 1) * MAX_PER_PAGE - : total}{" "} - of {total} results - - - - - - -
-
+
) : ( No data found diff --git a/src/components/SimpleTable.tsx b/src/components/SimpleTable.tsx new file mode 100644 index 0000000..4ba4d5b --- /dev/null +++ b/src/components/SimpleTable.tsx @@ -0,0 +1,93 @@ +import { TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Box, Button, Link as MuiLink } from "@mui/material" +import { useState, useEffect } from "react"; + +interface SimpleTableProps { + data: {[varName: string]: string[]}, + showIndex?: boolean, + perPage?: number, + +} +export const SimpleTable = ({data,showIndex=true,perPage=10}:SimpleTableProps) => { + const [total, setTotal] = useState(0); + const [nCols, setNCols] = useState(0); + const [curPage, setCurPage] = useState(0); + + useEffect(() => { + if (data) { + let cols: number = Object.values(data).length; + setNCols(cols); + setTotal(Object.values(data)[0].length); + } else { + setNCols(0); + setTotal(0); + } + }, [data]); + + const renderText = (txt: string) => { + if (txt) { + let name: string = txt.replace(/.*\//g, "").replace(/.*#/g, ""); + if (txt.startsWith("http")) + return ( + + {name} + + ); + return {name}; + } + }; + + return + + + + {showIndex && ( + # + )} + {Object.keys(data).map((varname: string) => ( + + {varname} + + ))} + + + + {Array(perPage).fill(0) + .map((_, i) => curPage * perPage + i) + .map((i) => ( + + {showIndex && i < total && ( + + {i + 1} + + )} + {i < total && + Object.values(data).map((values: string[], j) => ( + + {renderText(values[i])} + + ))} + + ))} + + + + + + Showing {curPage * perPage} -{" "} + {(curPage + 1) * perPage < total + ? (curPage + 1) * perPage + : total}{" "} + of {total} results + + + + + + +
+
+} \ No newline at end of file diff --git a/src/components/files/ExecutionOutputs.tsx b/src/components/files/ExecutionOutputs.tsx new file mode 100644 index 0000000..2753246 --- /dev/null +++ b/src/components/files/ExecutionOutputs.tsx @@ -0,0 +1,111 @@ +import { Box, Card, Divider, Grid, Link, Typography } from "@mui/material" +import { Endpoint, Execution, VariableBinding } from "DISK/interfaces" +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import { useEffect, useState } from "react"; +import { displayConfidenceValue } from "constants/general"; +import { BrainModal } from "components/modal/BrainModal"; +import { ShinyModal } from "components/modal/ShinyModal"; + +interface ExecutionOutputProps { + execution: Execution + outputs: VariableBinding[] + source: Endpoint +} + +export const ExecutionOutput = ({execution, outputs, source}:ExecutionOutputProps) => { + const [shinyLog, setShinyLog] = useState(""); + const [brainCfg, setBrainCfg] = useState(""); + const [visibleOutputs, setVisibleOutputs] = useState([]); + + type RenderableOutput = '_SHINY_LOG_' | '_BRAIN_VISUALIZATION_' | '_CONFIDENCE_VALUE_'; + useEffect(() => { + let renderable : RenderableOutput[] = ['_SHINY_LOG_', '_BRAIN_VISUALIZATION_']; + let variables: Partial> = {}; + let values: Partial> = {}; + let map: Partial> = {}; + + (outputs || []) + .filter(b => b.binding && b.binding.length > 0 && renderable.some(r => r === b.binding[0])) + .forEach(b => variables[b.variable] = b.binding[0] as RenderableOutput); + if (execution && execution.result && execution.result.extras) { + execution.result.extras.forEach(b => { + let key = variables[b.variable]; + if (key && b.binding && b.binding.length > 0) { + values[key] = b.binding[0]; + } + map[b.variable] = b; + }); + } + setShinyLog(values['_SHINY_LOG_'] ? values['_SHINY_LOG_'] : ""); + setBrainCfg(values['_BRAIN_VISUALIZATION_'] ? values['_BRAIN_VISUALIZATION_'] : ""); + // Now remove processed outputs and bind to execution outputs. + renderable.push("_CONFIDENCE_VALUE_"); + + setVisibleOutputs(outputs + .filter(o => !(!o.binding || o.binding.length === 0 || renderable.some(r => r === o.binding[0]))) + .map(o => map[o.variable] ? map[o.variable] as VariableBinding : o) + ); + }, [execution, outputs]); + + const renderOneBinding = (binding:VariableBinding, value:string) => { + let isLiteral = binding.datatype?.endsWith("string") || !value.startsWith("http"); + let isURL = value.startsWith("http") || value.startsWith("www") || value.startsWith("ftp"); + return + {isLiteral ? + '"' + value + '"' + : (isURL ? {value.replaceAll(/.*\//g,'')} : value)} + + } + + const renderBindings = (binding: VariableBinding) => { + if (!binding.isArray || binding.binding.length === 1) { + return renderOneBinding(binding, binding.binding[0]); + } else { + return binding.binding.map(str => renderOneBinding(binding, str)); + } + } + + const getExecutionLink = () => { + if (source && execution.externalId.includes("localhost")) { + let sp1 = source.url.split('wings-portal'); + let sp2 = execution.externalId.split('wings-portal'); + if (sp1.length == 2 && sp2.length == 2) + return sp1[0] + "wings-portal" + sp2[1]; + } + return execution.externalId; + } + + return + + + + + Run: {execution.externalId.replaceAll(/.*#/g, '')} + + + + {execution.result.confidenceValue > 0 && + {execution.result.confidenceType || "confidence"}: + { displayConfidenceValue(execution.result.confidenceValue)} + } + + + + {visibleOutputs.map((binding: VariableBinding) => + + + {binding.variable}: + + + {renderBindings(binding)} + + + )} + + + {shinyLog !== "" && } + {brainCfg !== "" && } + + +} \ No newline at end of file diff --git a/src/components/FileList.tsx b/src/components/files/FileList.tsx similarity index 69% rename from src/components/FileList.tsx rename to src/components/files/FileList.tsx index aed407e..02d242a 100644 --- a/src/components/FileList.tsx +++ b/src/components/files/FileList.tsx @@ -1,9 +1,9 @@ import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material" import { Fragment, useEffect, useState } from "react"; import CloseIcon from '@mui/icons-material/Close'; -import { Endpoint, Execution, LineOfInquiry, RunBinding, TriggeredLineOfInquiry, VariableBinding, Workflow, WorkflowInstantiation, WorkflowRun } from "DISK/interfaces"; +import { Endpoint, Execution, TriggeredLineOfInquiry, VariableBinding, WorkflowInstantiation } from "DISK/interfaces"; import FileCopyIcon from '@mui/icons-material/FileCopy'; -import { PrivateLink } from "./PrivateLink"; +import { PrivateLink } from "../PrivateLink"; interface FileListProps { type: 'input' | 'output', @@ -21,28 +21,19 @@ export const FileList = ({type:displayType, tloi, label: title, renderFiles} : F const [total, setTotal] = useState(0); const [runs, setRuns] = useState([]); - const getFileMap : (run:RunStatus|Execution) => Record = (run) => { + const getFileList : (run:RunStatus|Execution) => VariableBinding[] = (run) => { if (displayType === 'input') { - return (run.inputs || []).reduce>((acc, binding) => { - acc[binding.variable] = binding; - return acc; - }, {}); + return (run.inputs || []).filter(run => run.type === 'DISK_DATA', {}); } let doNoStore : string[] = []; - if (tloi) [...tloi.workflows, ...tloi.metaWorkflows].forEach((wf) => { wf.outputs.forEach((b) => { if (b.type === 'DROP') doNoStore.push(b.variable); }) }); - return (run.outputs || []).reduce>((acc, binding) => { - if (!doNoStore.some(n => n === binding.variable)) { - acc[binding.variable] = binding; - } - return acc; - }, {}); + return (run.outputs || []).filter(binding => !doNoStore.some(n => n === binding.variable)); } // Adds source and all runs @@ -53,14 +44,13 @@ export const FileList = ({type:displayType, tloi, label: title, renderFiles} : F let i = 0; allWfs.forEach(wf => { (wf.executions || []).forEach(run => { - let list = getFileMap(run); - let length = Object.keys(list).length; - if (length > 0) { + let list = getFileList(run); + if (list.length >0) { allRuns.push({ ...run, source: wf.source }); - i += length; + list.forEach(vb => i+=vb.binding.length); } }); }); @@ -73,6 +63,10 @@ export const FileList = ({type:displayType, tloi, label: title, renderFiles} : F return run.externalId.replace(/.*#/,'').replace(/--.*/,''); } + const getFilename = (val:string) => { + return val.replace(/.*#/, '').replace(/SHA[\d\w]{6}_/, '').replace(/-\w{24,25}$/, ''); + } + return ( +
+ } + + {wf.outputs.length > 0 && + + + {meta ? "Meta-workflow" : "Workflow"} outputs: + + {wf.executions && wf.executions.length > 0 ? + wf.executions.map(e => + + + + ) + : + { wf.outputs.map((binding:VariableBinding) => + + + {binding.variable}: + + + {renderWorkflowVariableBinding(binding)} + + + )} + + } + } + + ); +} \ No newline at end of file diff --git a/src/components/methods/WorkflowList.tsx b/src/components/methods/WorkflowSeedList.tsx similarity index 100% rename from src/components/methods/WorkflowList.tsx rename to src/components/methods/WorkflowSeedList.tsx diff --git a/src/components/modal/BrainModal.tsx b/src/components/modal/BrainModal.tsx index af15066..160d98b 100644 --- a/src/components/modal/BrainModal.tsx +++ b/src/components/modal/BrainModal.tsx @@ -1,4 +1,4 @@ -import { Box, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton} from "@mui/material" +import { Box, Button, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton} from "@mui/material" import { Fragment, useEffect, useState } from "react"; import CloseIcon from '@mui/icons-material/Close'; import ThreeDRotationIcon from '@mui/icons-material/ThreeDRotation'; @@ -7,9 +7,10 @@ import { useGetPrivateFileAsTextQuery } from "redux/apis/server"; interface BrainModalProps { brainCfg: string, + iconOnly?: boolean } -export const BrainModal = ({brainCfg: brainCfgTxt} : BrainModalProps) => { +export const BrainModal = ({brainCfg: brainCfgTxt, iconOnly=true} : BrainModalProps) => { const [open, setOpen] = useState(false); const [brainCfg, setBrainCfg] = useState(null); @@ -37,9 +38,15 @@ export const BrainModal = ({brainCfg: brainCfgTxt} : BrainModalProps) => { return ( - - - + {iconOnly ? + + + + : + } setOpen(false)} maxWidth="md" fullWidth> Brain Visualization diff --git a/src/components/modal/NarrativeModal.tsx b/src/components/modal/NarrativeModal.tsx index c91a225..c4791b1 100644 --- a/src/components/modal/NarrativeModal.tsx +++ b/src/components/modal/NarrativeModal.tsx @@ -3,7 +3,7 @@ import { Fragment, useEffect, useState } from "react"; import CloseIcon from '@mui/icons-material/Close'; import TextSnippetIcon from '@mui/icons-material/TextSnippet'; import { Goal, Status, TriggeredLineOfInquiry } from "DISK/interfaces"; -import { WorkflowSeedList } from "../methods/WorkflowList"; +import { WorkflowSeedList } from "../methods/WorkflowSeedList"; import { useGetGoalByIdQuery } from "redux/apis/goals"; interface NarrativeModalProps { diff --git a/src/components/modal/ShinyModal.tsx b/src/components/modal/ShinyModal.tsx index bdeac3f..144b44d 100644 --- a/src/components/modal/ShinyModal.tsx +++ b/src/components/modal/ShinyModal.tsx @@ -6,15 +6,16 @@ import { useGetPrivateFileAsTextQuery } from "redux/apis/server"; interface ShinyModalProps { shinyLog: string, + iconOnly?: boolean } -export const ShinyModal = ({shinyLog} : ShinyModalProps) => { +export const ShinyModal = ({shinyLog, iconOnly=true} : ShinyModalProps) => { const [open, setOpen] = useState(false); const [url, setUrl] = useState(""); useEffect(() => { if (shinyLog) { - let m = shinyLog.match("Application successfully deployed to (.*)"); + let m = shinyLog.match("\\[1\\] \"(.*)\""); if (m && m.length === 2) { setUrl(m[1]); } else { @@ -36,9 +37,15 @@ export const ShinyModal = ({shinyLog} : ShinyModalProps) => { return ( - - - + { iconOnly ? + + + + : + } setOpen(false)} maxWidth="xl" fullWidth> Shiny Visualization diff --git a/src/components/outputs/table.tsx b/src/components/outputs/table.tsx index 46281f8..0c4bb64 100644 --- a/src/components/outputs/table.tsx +++ b/src/components/outputs/table.tsx @@ -7,12 +7,11 @@ import { TLOIEditButton } from "components/tlois/TLOIEdit"; import { PATH_TLOIS } from "constants/routes"; import { Fragment } from "react"; import { Link } from "react-router-dom"; -import { FileList } from "components/FileList"; +import { FileList } from "components/files/FileList"; import ErrorIcon from '@mui/icons-material/ErrorOutline'; import WaitIcon from '@mui/icons-material/HourglassBottom'; import CheckIcon from '@mui/icons-material/Check'; import { displayConfidenceValue } from "constants/general"; -import { OutputBindingValue } from "."; import { BrainModal } from "components/modal/BrainModal"; import { ShinyModal } from "components/modal/ShinyModal"; @@ -49,33 +48,30 @@ const renderConfidenceValue = (tloi: TriggeredLineOfInquiry, i: number) => { } +type RenderableOutput = '_SHINY_LOG_' | '_BRAIN_VISUALIZATION_'; const renderOptionalButtons = (cur: TriggeredLineOfInquiry) => { - let shinyLog: string = ""; - let brainCfg: string = ""; - - + const renderable : RenderableOutput[] = ['_SHINY_LOG_', '_BRAIN_VISUALIZATION_']; + let variables : Partial> = {}; + let values : Partial> = {}; + [ ...cur.workflows, ...cur.metaWorkflows ].forEach((wf:WorkflowInstantiation) => { - (wf.executions || []).forEach((run) => { - let mappedOutputs = (run.result.extras||[]).reduce>((acc, binding) => { - acc[binding.variable] = binding; - return acc; - }, {}); - (run.outputs || []).forEach((binding => { - if ((binding.binding || []).length > 0) { - let value = binding.binding[0] as OutputBindingValue; - if (value === '_SHINY_LOG_' && mappedOutputs[binding.variable]) { - shinyLog = mappedOutputs[binding.variable].binding[0]; - } else if (value === '_BRAIN_VISUALIZATION_') { - brainCfg = mappedOutputs[binding.variable].binding[0]; - } + (wf.outputs||[]) + .filter(b => b.binding && b.binding.length > 0 && renderable.some(r=>r===b.binding[0])) + .forEach(b => variables[b.variable] = b.binding[0] as RenderableOutput); + (wf.executions || []) + .filter(e => e.result && e.result.extras) + .map(e => e.result.extras).flat() + .forEach(b => { + let key = variables[b.variable]; + if (key && b.binding && b.binding.length >0) { + values[key] = b.binding[0]; } - })); - }) + }); }); return ( - {!!shinyLog && ()} - {!!brainCfg && ()} + {!!values['_SHINY_LOG_'] && ()} + {!!values['_BRAIN_VISUALIZATION_'] && ()} ); } diff --git a/src/components/tlois/ConfidencePlot.tsx b/src/components/tlois/ConfidencePlot.tsx index 38ed08d..7988aa4 100644 --- a/src/components/tlois/ConfidencePlot.tsx +++ b/src/components/tlois/ConfidencePlot.tsx @@ -1,4 +1,4 @@ -import { Box, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, Skeleton } from "@mui/material"; +import { Box, FormControl, FormControlLabel, FormLabel, MenuItem, Radio, RadioGroup, Select, Skeleton } from "@mui/material"; import { Goal, RunBinding, TriggeredLineOfInquiry, Workflow, WorkflowRun } from "DISK/interfaces"; import { useEffect, useState } from "react"; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, @@ -11,13 +11,13 @@ import { useFilteredTLOIs, useOutputs } from "redux/hooks/tloi"; ChartJS.register(CategoryScale, LogarithmicScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); interface ConfidencePlotProps { - goal?: Goal, - loiId?: string, + goalId: string, + loiId: string, } -export const ConfidencePlot = ({ goal: hypothesis, loiId }: ConfidencePlotProps) => { +export const ConfidencePlot = ({ goalId, loiId }: ConfidencePlotProps) => { const { data: visibleTLOIs, isLoading: loadingTLOIs} = useFilteredTLOIs({ - hypothesisId: hypothesis?.id, + goalId: goalId, loiId: loiId, sort: (t1, t2) => t1.dateCreated!.localeCompare(t2.dateCreated!) }); @@ -47,25 +47,26 @@ export const ConfidencePlot = ({ goal: hypothesis, loiId }: ConfidencePlotProps) let labelDic: { [uri: string]: string } = {}; let labels: string[] = []; - // TODO: - //visibleTLOIs - // .sort((t1, t2) => { - // return t1.dateCreated.localeCompare(t2.dateCreated); - // }).forEach((tloi: TriggeredLineOfInquiry) => { - // let nInputs = 0; - // [...tloi.workflows, ...tloi.metaWorkflows].forEach((wf: Workflow) => { - // Object.values(wf.runs || {}).forEach((run: WorkflowRun) => { - // Object.keys(run.outputs).forEach((outName) => { - // outputs[outName] = run.outputs[outName]; - // }) - // nInputs += Object.keys(run.inputs).length; - // }); - // }); - // let label = "N = " + String(nInputs); - // labels.push(label); - // labelDic[tloi.id] = label; - // pValues[label] = tloi.confidenceValue; - // }); + visibleTLOIs + .forEach((tloi: TriggeredLineOfInquiry) => { + // We analyse only the first execution of the first wf + for (const wf of [...tloi.workflows, ...tloi.metaWorkflows]) { + for (const exec of wf.executions) { + if (exec.result && exec.result.confidenceValue > 0) { + let nInputs = 0; + wf.dataBindings.forEach(db => { + if (nInputs < db.binding.length) + nInputs = db.binding.length; + }); + let label = "N = " + String(nInputs); + labels.push(label); + labelDic[tloi.id] = label; + pValues[label] = exec.result.confidenceValue; + break; + } + } + } + }); setData({ labels: labels, @@ -81,6 +82,7 @@ export const ConfidencePlot = ({ goal: hypothesis, loiId }: ConfidencePlotProps) setFiles(outputs); setIdToLabel(labelDic); + //TODO: add a image selector for tooltip and a date option for labels. setSelectedFile(Object.keys(outputs).length > 0 ? Object.keys(outputs)[0] : ""); }, [visibleTLOIs]) @@ -307,9 +309,12 @@ export const ConfidencePlot = ({ goal: hypothesis, loiId }: ConfidencePlotProps) } + {data && - - + + + + } diff --git a/src/components/tlois/TLOIBundle.tsx b/src/components/tlois/TLOIBundle.tsx index 5b55e5f..62797e7 100644 --- a/src/components/tlois/TLOIBundle.tsx +++ b/src/components/tlois/TLOIBundle.tsx @@ -1,4 +1,4 @@ -import { Box, Card, Divider, Skeleton, breadcrumbsClasses } from "@mui/material" +import { Box, Card, Divider, Skeleton } from "@mui/material" import { TypographyLabel } from "components/Styles" import { ConfidencePlot } from "./ConfidencePlot" import SettingsIcon from '@mui/icons-material/Settings'; @@ -9,7 +9,6 @@ import { Goal, TriggeredLineOfInquiry } from "DISK/interfaces"; import { TLOITable } from "./TLOITable"; import { useGetLOIByIdQuery } from "redux/apis/lois"; import { ImagePreview } from "components/files/ImagePreview"; -import { OutputBindingValue } from "components/outputs"; interface TLOIBundleProps { loiId: string, @@ -21,9 +20,7 @@ export const TLOIBundle = ({loiId, goal}:TLOIBundleProps) => { const { data:loi, isLoading:LOILoading} = useGetLOIByIdQuery(loiId); const [list, setList] = useState([]); const [name, setName] = useState(""); - - const [showConfidencePlot, setShowConfidencePlot] = useState(false); - const [mainVisualizations, setMainVisualizations] = useState<{[name:string]: [string, string]}>({}); + const [mainVisualizations, setMainVisualizations] = useState<{[name:string]: string}>({}); //only the last one of these are shown. useEffect(() => { //Create list for this hypothesis and line of inquiry @@ -42,49 +39,34 @@ export const TLOIBundle = ({loiId, goal}:TLOIBundleProps) => { }, [loi]) useEffect(() => { - let plots = new Set(); - let viz = new Set(); - let ignore = new Set(); - let vizMap : {[name:string] : [string, string]} = {}; - if (loi) { - [...loi.workflowSeeds, ...loi.metaWorkflowSeeds ].map(wf => wf.outputs).flat().forEach((binding) => { - if (binding.type === 'DROP') { - ignore.add(binding.variable); - } else if (!binding.isArray && binding.binding && binding.binding.length > 0) { - let value = binding.binding[0] as OutputBindingValue; - if (value.startsWith("_") && value.endsWith("_")) { - switch (value) { - case "_CONFIDENCE_VALUE_": - plots.add(binding.variable); - break; - case "_VISUALIZE_": - viz.add(binding.variable); - break; - default: - break; - } - } + let vizMap : {[name:string] : string} = {}; + if (loi && list.length > 0) { + let allSeeds = [...loi.workflowSeeds, ...loi.metaWorkflowSeeds ]; + let varSet = new Set(); + allSeeds.map(wf => wf.outputs).flat().forEach((binding) => { + if (binding.binding.length === 1 && binding.binding[0] === "_VISUALIZE_" ) { + varSet.add(binding.variable); } }); - } - if (list.length > 0) { - let last = list[list.length - 1]; - let vizArr = Array.from(viz); - let ignoreArr = Array.from(ignore); - [...last.workflows, ...last.metaWorkflows].forEach((wf) => { - (wf.executions || []).map(run => run.result.extras).flat().forEach(output => { - if (vizArr.some(v => output.variable === v) && !ignoreArr.some(i => output.variable === i) && output.binding.length > 0) { - let url = output.binding[output.binding.length-1]; //If is a list, maybe is better to show them all. - if (url != null) { - vizMap[output.variable] = [wf.source.url, url]; //FIXME: not sure if is url - } - } - }) - }) - } + let visualizations = Array.from(varSet); + let filtered = (list||[]) + .filter(tloi => [...tloi.workflows, ...tloi.metaWorkflows].every(wf => wf.executions.length > 0)); + + let last = filtered[filtered.length-1]; + [...last.workflows, ...last.metaWorkflows] + .filter(wf => wf.executions.length > 0 && wf.executions[0].result) + .forEach((wf) => { + (wf.executions[0].result.extras || []).forEach(binding => { + visualizations.forEach(varName => { + if (binding.variable === varName && binding.binding.length > 0) { + vizMap[varName] = binding.binding[binding.binding.length-1]; + } + }); + }); + }); + } setMainVisualizations(vizMap); - setShowConfidencePlot(plots.size > 0); }, [loi, list]) if (TLOILoading || LOILoading) @@ -107,12 +89,12 @@ export const TLOIBundle = ({loiId, goal}:TLOIBundleProps) => { Overview of results: - + {Object.keys(mainVisualizations).map((name) => )} - {showConfidencePlot && list.length > 2 && } + } \ No newline at end of file diff --git a/src/components/tlois/TLOIEdit.tsx b/src/components/tlois/TLOIEdit.tsx index 288d96b..813d200 100644 --- a/src/components/tlois/TLOIEdit.tsx +++ b/src/components/tlois/TLOIEdit.tsx @@ -2,7 +2,7 @@ import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitl import { Fragment, useEffect, useState } from "react"; import CloseIcon from '@mui/icons-material/Close'; import { useAppDispatch, useAuthenticated } from "redux/hooks"; -import { TriggeredLineOfInquiry, VariableBinding, Workflow } from "DISK/interfaces"; +import { TriggeredLineOfInquiry, VariableBinding, WorkflowInstantiation } from "DISK/interfaces"; import EditIcon from '@mui/icons-material/Edit'; import PlayIcon from '@mui/icons-material/PlayArrow'; import { cleanTLOI, getBindingAsArray } from "DISK/util"; @@ -20,7 +20,7 @@ export const TLOIEditButton = ({tloi, label: title} : FileListProps) => { const authenticated = useAuthenticated(); const [open, setOpen] = useState(false); const [selectedBindings, setSelectedBindings] = useState<{[id:string]: boolean}>({}); - const [editableWfs, setEditableWfs] = useState([]); + const [editableWfs, setEditableWfs] = useState([]); const [arraySizes, setArraySizes] = useState([]); const [meta, setMeta] = useState(false); const [postTLOI, {}] = usePostTLOIMutation(); @@ -30,10 +30,12 @@ export const TLOIEditButton = ({tloi, label: title} : FileListProps) => { let newTLOI : TriggeredLineOfInquiry = { ...tloi, id: "", - //confidenceValue: 0, dateCreated: "", - //workflows: meta ? cleanWorkflows(tloi.workflows) : getEditedWorkflows(), - //metaWorkflows: meta ? getEditedWorkflows() : cleanWorkflows(tloi.metaWorkflows), + dateModified: "", + status: "PENDING", + result: null, + workflows: meta ? cleanWorkflows(tloi.workflows) : getEditedWorkflows(), + metaWorkflows: meta ? getEditedWorkflows() : cleanWorkflows(tloi.metaWorkflows), }; dispatch(openBackdrop()); @@ -60,36 +62,33 @@ export const TLOIEditButton = ({tloi, label: title} : FileListProps) => { } const getEditedWorkflows = () => { - let wfs : Workflow[] = []; - editableWfs.forEach((wf:Workflow) => { + let wfs : WorkflowInstantiation[] = []; + editableWfs.forEach((wf:WorkflowInstantiation) => { wfs.push({ ...wf, - //bindings: wf.bindings - // .map((vb:VariableBinding) => { - // return !vb.collection ? vb : { - // ...vb, - // binding: getSelectedBindings(vb.binding, wf.source), - // } - // }), - //meta: undefined, - //runs: undefined, + status: 'PENDING', + executions: [], + dataBindings: wf.dataBindings + .map((vb:VariableBinding) => { + return vb.isArray ? + { ...vb, binding: getSelectedBindings(vb.binding, wf.source.url)} + : vb; + }), }); }); return wfs; } - const cleanWorkflows : (wfs:Workflow[]) => Workflow[] = (wfs:Workflow[]) => { - return wfs.map((wf:Workflow) => { - let newWf : Workflow = { ...wf }; - newWf.meta = undefined; - newWf.runs = undefined; - return newWf; - }) + const cleanWorkflows : (wfs:WorkflowInstantiation[]) => WorkflowInstantiation[] = (wfs:WorkflowInstantiation[]) => { + return wfs.map((wf:WorkflowInstantiation) => ({ + ...wf, + status: 'PENDING', + executions: [] + })); } - const getSelectedBindings = (bindings:string, source:string) => { - let arr : string [] = getBindingAsArray(bindings); - return "[" + arr.filter((_,i) => selectedBindings[source + "+" + i]).join(", ") + "]"; + const getSelectedBindings = (bindings:string[], source:string) => { + return bindings.filter((_,i) => selectedBindings[source + "+" + i]); } useEffect(() => { @@ -102,22 +101,22 @@ export const TLOIEditButton = ({tloi, label: title} : FileListProps) => { let wfs = meta ? tloi.metaWorkflows : tloi.workflows; let newSizes : number[] = []; setMeta(meta); - //setEditableWfs(wfs); + setEditableWfs(wfs); - //wfs.forEach((wf:Workflow) => { - // let size : number = 0; - // wf.bindings.forEach((vb:VariableBinding) => { - // let c : number = vb.collection ? vb.binding.split(', ').length : 1; - // if (size < c) size = c; - // }); - // newSizes.push(size); + wfs.forEach((wf) => { + let size : number = 0; + wf.dataBindings.forEach((vb:VariableBinding) => { + let c : number = vb.binding.length; + if (size < c) size = c; + }); + newSizes.push(size); - // setSelectedBindings((curBindings:{[id:string]: boolean}) => { - // for (let i = 0; i < size; i++) - // curBindings[wf.source+"+"+i] = true; - // return { ...curBindings}; - // }); - //}); + setSelectedBindings((curBindings:{[id:string]: boolean}) => { + for (let i = 0; i < size; i++) + curBindings[wf.source.url+"+"+i] = true; + return { ...curBindings}; + }); + }); setArraySizes(newSizes); } }, [tloi]) @@ -154,15 +153,15 @@ export const TLOIEditButton = ({tloi, label: title} : FileListProps) => { - {editableWfs.map((wf:Workflow, i:number) => + {editableWfs.map((wf:WorkflowInstantiation, i:number) => - Editing workflow: {renderRunTitle(wf.runs && Object.keys(wf.runs)[0] ? Object.keys(wf.runs)[0] : wf.workflow)} + Editing workflow: {renderRunTitle(wf.executions && wf.executions.length > 0 ? wf.executions[0].externalId : wf.link)} # - {wf.bindings.filter(b => !b.binding[0].startsWith("_")).map((b:VariableBinding) => + {wf.dataBindings.filter(b => !b.variable.startsWith("_") && !b.binding[0].startsWith("_")).map((b:VariableBinding) => {b.variable} )} @@ -173,15 +172,15 @@ export const TLOIEditButton = ({tloi, label: title} : FileListProps) => { + setSelectedBindings((curBinding:{[id:string]: boolean}) => { - curBinding[wf.source + "+" + j] = ev.target.checked; + curBinding[wf.source.url + "+" + j] = ev.target.checked; return { ...curBinding }; }) }/> }/> - {wf.bindings.filter(b => !b.binding[0].startsWith("_")).map((b:VariableBinding) => + {wf.dataBindings.filter(b => !b.variable.startsWith("_") && !b.binding[0].startsWith("_")).map((b:VariableBinding) => {renderName(b.isArray ? b.binding[j]: b.binding[0])} )} diff --git a/src/components/tlois/TLOIPreview.tsx b/src/components/tlois/TLOIPreview.tsx index fb87b33..501cd48 100644 --- a/src/components/tlois/TLOIPreview.tsx +++ b/src/components/tlois/TLOIPreview.tsx @@ -1,9 +1,10 @@ import { Card } from "@mui/material"; -import { TriggeredLineOfInquiry } from "DISK/interfaces"; +import { GoalResult, TriggeredLineOfInquiry } from "DISK/interfaces"; import { getId } from "DISK/util"; import { GREEN_BLUE, GREEN_YELLOW, LIGHT_GREEN, RED } from "constants/colors"; import { BRAIN_FILENAME, SHINY_FILENAME, displayConfidenceValue } from "constants/general"; import { PATH_TLOIS } from "constants/routes"; +import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; @@ -12,6 +13,19 @@ interface TLOIPreviewProps { } export const TLOIPreview = ({tloi} : TLOIPreviewProps) => { + const [result, setResult] = useState(null); + + useEffect(() => { + for (const wf of [...tloi.workflows, ...tloi.metaWorkflows]) { + for (const exec of wf.executions) { + if (exec.result) { + setResult(exec.result); + break; + } + } + } + }, [tloi]) + const color = tloi.status === "SUCCESSFUL" ? LIGHT_GREEN : (tloi.status === "RUNNING" ? @@ -40,9 +54,10 @@ export const TLOIPreview = ({tloi} : TLOIPreviewProps) => { {tloi.name} - {tloi.dateCreated} {nViz > 0 && This run has {nViz} visualization{nViz > 1 && 's'}} - - P-value: {/*displayConfidenceValue(tloi.confidenceValue)*/} - + { result && + + {result.confidenceType || 'P-value'}: {displayConfidenceValue(result.confidenceValue)} + } ); diff --git a/src/components/tlois/TLOITable.tsx b/src/components/tlois/TLOITable.tsx index c27841b..a1e17a4 100644 --- a/src/components/tlois/TLOITable.tsx +++ b/src/components/tlois/TLOITable.tsx @@ -1,33 +1,34 @@ -import { TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Box } from "@mui/material"; -import { NarrativeModal } from "components/modal/NarrativeModal"; -import { PATH_TLOIS } from "constants/routes"; -import { Link } from "react-router-dom"; -import { TLOIEditButton } from "./TLOIEdit"; -import { Fragment, useState, useEffect } from "react"; -import { GoalResult, LineOfInquiry, TriggeredLineOfInquiry, Workflow, WorkflowInstantiation } from "DISK/interfaces"; -import { FileList } from "components/FileList"; -import { BrainModal } from "components/modal/BrainModal"; -import { ShinyModal } from "components/modal/ShinyModal"; -import { BRAIN_FILENAME, SHINY_FILENAME, displayConfidenceValue } from "constants/general"; -import { TLOIDeleteButton } from "./TLOIDeleteButton"; -import { getId } from "DISK/util"; +import { TableContainer, Table, TableHead, TableRow, TableCell, TableBody } from "@mui/material"; +import { useState, useEffect } from "react"; +import { LineOfInquiry, TriggeredLineOfInquiry } from "DISK/interfaces"; import { ALL_COLUMNS, ColumnName } from "components/outputs/table"; interface TLOITableProps { list: TriggeredLineOfInquiry[] - loi: LineOfInquiry, - showConfidence: boolean } -export const TLOITable = ({list, loi, showConfidence} : TLOITableProps) => { +export const TLOITable = ({list} : TLOITableProps) => { const [visibleColumns, setVisibleColumns] = useState(['#', "Date", "Run Status", "Input Files", "Output Files", "Confidence Value", "Extras"]); useEffect(() => { + // If theres at least one confidence value, show that column + let showConfidence = false; + for (const tloi of list) { + for (const wf of [...tloi.workflows, ...tloi.metaWorkflows]) { + for (const exec of wf.executions) { + if (exec.result && exec.result.confidenceValue > 0) { + showConfidence = true; + break; + } + } + } + } + if (!showConfidence) setVisibleColumns(['#', "Date", "Run Status", "Input Files", "Output Files", "Extras"]); else setVisibleColumns(['#', "Date", "Run Status", "Input Files", "Output Files", "Confidence Value", "Extras"]); - }, [showConfidence]) + }, [list]) return
diff --git a/src/pages/Goals/GoalEditor.tsx b/src/pages/Goals/GoalEditor.tsx index a149495..8228709 100644 --- a/src/pages/Goals/GoalEditor.tsx +++ b/src/pages/Goals/GoalEditor.tsx @@ -79,7 +79,6 @@ export const HypothesisEditor = () => { } const getQuestionBindings : () => VariableBinding[] = () => { - console.log(formQuestionBindings); return Object.keys(formQuestionBindings).map((varId: string) => ( { variable: varId, diff --git a/src/pages/Goals/GoalView.tsx b/src/pages/Goals/GoalView.tsx index 070f5bc..46aeceb 100644 --- a/src/pages/Goals/GoalView.tsx +++ b/src/pages/Goals/GoalView.tsx @@ -11,9 +11,9 @@ import { closeBackdrop, openBackdrop } from "redux/slices/backdrop"; import { openNotification } from "redux/slices/notifications"; import { useGetGoalByIdQuery } from "redux/apis/goals"; import { useExecuteHypothesisByIdMutation, useGetTLOIsQuery } from "redux/apis/tlois"; -import { TLOIBundle } from "components/tlois/TLOIBundle"; import { TypographyLabel, TypographyInline, InfoInline, TypographySubtitle } from "components/Styles"; import { getId } from "DISK/util"; +import { TLOIBundle } from "components/tlois/TLOIBundle"; export const HypothesisView = () => { diff --git a/src/pages/LOI/LOIEditor.tsx b/src/pages/LOI/LOIEditor.tsx index 470c79a..cf26a37 100644 --- a/src/pages/LOI/LOIEditor.tsx +++ b/src/pages/LOI/LOIEditor.tsx @@ -8,7 +8,7 @@ import CopyIcon from '@mui/icons-material/ContentCopy'; import { PATH_LOIS } from "constants/routes"; import { useAppDispatch } from "redux/hooks"; import { QuestionLinker } from "components/questions/QuestionLinker"; -import { WorkflowSeedList } from "components/methods/WorkflowList"; +import { WorkflowSeedList } from "components/methods/WorkflowSeedList"; import { getId } from "DISK/util"; import { useGetLOIByIdQuery, usePostLOIMutation, usePutLOIMutation } from "redux/apis/lois"; import { closeBackdrop, openBackdrop } from "redux/slices/backdrop"; diff --git a/src/pages/LOI/LOIView.tsx b/src/pages/LOI/LOIView.tsx index bef6550..2870f8f 100644 --- a/src/pages/LOI/LOIView.tsx +++ b/src/pages/LOI/LOIView.tsx @@ -14,7 +14,7 @@ import React from "react"; import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import VisibilityIcon from '@mui/icons-material/Visibility'; import { getId, renderDescription } from "DISK/util"; -import { WorkflowSeedList } from "components/methods/WorkflowList"; +import { WorkflowSeedList } from "components/methods/WorkflowSeedList"; import { useGetEndpointsQuery } from "redux/apis/server"; import { useGetLOIByIdQuery } from "redux/apis/lois"; import { InfoInline, TypographyInline, TypographyLabel, TypographySection, TypographySubtitle } from "components/Styles"; diff --git a/src/pages/TLOI/TLOIView.tsx b/src/pages/TLOI/TLOIView.tsx index 1e68952..f67ab23 100644 --- a/src/pages/TLOI/TLOIView.tsx +++ b/src/pages/TLOI/TLOIView.tsx @@ -1,29 +1,24 @@ import { Box, Button, Card, Divider, FormHelperText, IconButton, Skeleton, TextField, Tooltip, Typography } from "@mui/material"; import { DataEndpoint, TriggeredLineOfInquiry } from "DISK/interfaces"; -import { Fragment, useEffect } from "react"; +import { useEffect } from "react"; import { Link, useParams } from 'react-router-dom' import EditIcon from '@mui/icons-material/Edit'; import CancelIcon from '@mui/icons-material/Cancel'; import { styled } from '@mui/material/styles'; import { PATH_LOIS } from "constants/routes"; import { useAppDispatch, useAuthenticated } from "redux/hooks"; -import { sparql } from "@codemirror/legacy-modes/mode/sparql"; -import CodeMirror from '@uiw/react-codemirror'; -import { StreamLanguage } from '@codemirror/language'; import React from "react"; -import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; -import VisibilityIcon from '@mui/icons-material/Visibility'; import SettingsIcon from '@mui/icons-material/Settings'; -import { ResultTable } from "components/ResultTable"; -import { WorkflowSeedList } from "components/methods/WorkflowList"; +import { WorkflowSeedList } from "components/methods/WorkflowSeedList"; import { QuestionPreview } from "components/questions/QuestionPreview"; -import { getId, renderDescription } from "DISK/util"; +import { getId } from "DISK/util"; import { useGetGoalByIdQuery } from "redux/apis/goals"; import { useGetEndpointsQuery } from "redux/apis/server"; import { useGetTLOIByIdQuery, usePutTLOIMutation } from "redux/apis/tlois"; import { closeBackdrop, openBackdrop } from "redux/slices/backdrop"; import { openNotification } from "redux/slices/notifications"; -import { SHINY_FILENAME, BRAIN_FILENAME } from "constants/general"; +import { DataQueryResultView } from "components/DataQuery/DataQueryResultView"; +import { WorkflowInstantiationList } from "components/methods/WorkflowInstantiationList"; const TypographyLabel = styled(Typography)(({ theme }) => ({ color: 'gray', @@ -151,7 +146,6 @@ export const TLOIView = ({edit} : TLOIViewProps) => { return; } - return {loading ? @@ -212,73 +206,15 @@ export const TLOIView = ({edit} : TLOIViewProps) => { } - + {TLOI && TLOI.queryResults && <> + + + } - - Data: - - Data source: - {loadingEndpoints ? - : - (dataSource && ( - {dataSource.name} - - {renderDescription(dataSource.description)} - - )) - } - - - - Data query explanation: - {loading ? - : - (!!TLOI && false ? - {TLOI?.description} : - None specified - ) - } - - - setFormalView(!formalView)}> - {formalView? : } - - - - - {formalView && - Data query: - - { - }} - /> - - } - - - - {//loading ? - // : - // - // {!!TLOI && (TLOI.tableDescription || (TLOI.dataQuery && TLOI.tableVariables && dataSource)) && - // Input data retrieved:} - // {!!TLOI && TLOI.tableDescription && - // - // Table: - // {TLOI.tableDescription} - // } - // {!!TLOI && dataSource && TLOI.tableVariables && TLOI.dataQuery && - // } - // - } - - Methods: - {!!TLOI && } + {!!TLOI && } } \ No newline at end of file diff --git a/src/redux/hooks/tloi.tsx b/src/redux/hooks/tloi.tsx index 249e217..8d55ae1 100644 --- a/src/redux/hooks/tloi.tsx +++ b/src/redux/hooks/tloi.tsx @@ -8,20 +8,20 @@ interface DataPackage { } interface FilterProps { - hypothesisId?: string, + goalId?: string, loiId?: string, sort?: (t1:TriggeredLineOfInquiry, t2:TriggeredLineOfInquiry) => number, } -export const useFilteredTLOIs : ({hypothesisId, loiId, sort}:FilterProps) => DataPackage = ({hypothesisId, loiId, sort}) => { +export const useFilteredTLOIs : ({goalId, loiId, sort}:FilterProps) => DataPackage = ({goalId, loiId, sort}) => { const { data: TLOIs, isLoading } = useGetTLOIsQuery(); const [filteredTLOIs, setFilteredTLOIs] = useState([]); useEffect(() => { let cur: TriggeredLineOfInquiry[] = []; - if (TLOIs && TLOIs.length > 0 && hypothesisId && loiId) { + if (TLOIs && TLOIs.length > 0 && goalId && loiId) { cur = [...TLOIs]; - if (hypothesisId) cur = cur.filter((tloi) => tloi.parentGoal.id === hypothesisId); + if (goalId) cur = cur.filter((tloi) => tloi.parentGoal.id === goalId); if (loiId) cur = cur.filter((tloi) => tloi.parentLoi.id === loiId); //cur = cur.filter((tloi) => tloi.confidenceValue > 0); }