diff --git a/src/DISK/interfaces.tsx b/src/DISK/interfaces.tsx index 20499b1..f782e6b 100644 --- a/src/DISK/interfaces.tsx +++ b/src/DISK/interfaces.tsx @@ -119,10 +119,13 @@ export interface VariableOption { commnet: string | null, } -export interface LineOfInquiry extends DISKResource { +export interface LOICommon extends DISKResource { updateCondition: number; goalQuery: string; question: Question | ObjectWithId; +} + +export interface LineOfInquiry extends LOICommon { dataQueryTemplate?: DataQueryTemplate; workflowSeeds: WorkflowSeed[]; metaWorkflowSeeds: WorkflowSeed[]; @@ -132,7 +135,7 @@ export interface DataQueryTemplate { endpoint: Endpoint | (Partial & Pick); template: string; description: string; - variablesToShow: string; + variablesToShow: string[]; footnote: string; } @@ -155,7 +158,7 @@ export interface WorkflowSeed { export type SeedBindings = Pick; -export interface TriggeredLineOfInquiry extends LineOfInquiry{ +export interface TriggeredLineOfInquiry extends LOICommon{ status: Status; parentLoi: LineOfInquiry; parentGoal: Goal; @@ -180,15 +183,16 @@ export interface WorkflowInstantiation extends WorkflowSeed { export interface Execution extends ExecutionRecord { externalId: string ; result: GoalResult ; - steps: ExecutionRecord ; - inputs: VariableBinding; - outputs: VariableBinding; + steps: ExecutionRecord[]; + inputs: VariableBinding[]; + outputs: VariableBinding[]; } export interface GoalResult { confidenceType: string; - confidenceValue: string; + confidenceValue: number; + extras: VariableBinding[]; } export interface ExecutionRecord { @@ -315,7 +319,9 @@ export type MultiValueAssignation = { export type QuestionVariableAssignation = { [questionURI:string] : { values: string[], - type?: 'http://www.w3.org/2001/XMLSchema#anyURI' | 'http://www.w3.org/2001/XMLSchema#float' | 'http://www.w3.org/2001/XMLSchema#string' + type?: 'http://www.w3.org/2001/XMLSchema#anyURI' + | 'http://www.w3.org/2001/XMLSchema#float' + | 'http://www.w3.org/2001/XMLSchema#string' } } diff --git a/src/components/DataQuery/DataQueryTemplateForm.tsx b/src/components/DataQuery/DataQueryTemplateForm.tsx index bdac117..15baa42 100644 --- a/src/components/DataQuery/DataQueryTemplateForm.tsx +++ b/src/components/DataQuery/DataQueryTemplateForm.tsx @@ -40,7 +40,7 @@ export const DataQueryTemplateForm = ({value,onChange, showErrors}:DataQueryTemp if (value.description !== dataQueryExplanation) setDataQueryExplanation(value.description || ""); if (value.footnote !== tableFootnote) setTableFootnote(value.footnote || ""); if (value.template !== template) setTemplate(value.template || ""); - if (value.variablesToShow !== tableVariables) setTableVariables(value.variablesToShow || ""); + if (value.variablesToShow && value.variablesToShow.join(", ") !== tableVariables) setTableVariables(value.variablesToShow.join(", ") || ""); if (value.endpoint && value.endpoint.url && value.endpoint.url !== sourceUrl) setSourceUrl(value.endpoint.url); if (showErrors) { if (!value.endpoint || !value.endpoint.url) setErrorDataSource(true); @@ -74,7 +74,7 @@ export const DataQueryTemplateForm = ({value,onChange, showErrors}:DataQueryTemp description: dataQueryExplanation, template: template, footnote: tableFootnote, - variablesToShow: tableVariables, + variablesToShow: tableVariables.split(/, */g), endpoint: {id:sourceID}, }) }, [dataQueryExplanation, template, tableFootnote, tableVariables, sourceID]); diff --git a/src/components/FileList.tsx b/src/components/FileList.tsx index 73f5a38..aed407e 100644 --- a/src/components/FileList.tsx +++ b/src/components/FileList.tsx @@ -1,79 +1,68 @@ 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 { LineOfInquiry, RunBinding, TriggeredLineOfInquiry, Workflow, WorkflowRun } from "DISK/interfaces"; +import { Endpoint, Execution, LineOfInquiry, RunBinding, TriggeredLineOfInquiry, VariableBinding, Workflow, WorkflowInstantiation, WorkflowRun } from "DISK/interfaces"; import FileCopyIcon from '@mui/icons-material/FileCopy'; import { PrivateLink } from "./PrivateLink"; interface FileListProps { type: 'input' | 'output', tloi: TriggeredLineOfInquiry | null, - loi?: LineOfInquiry, label: string, renderFiles?: boolean, } -interface RunStatus extends WorkflowRun { - source: string, +interface RunStatus extends Execution { + source: Endpoint, } -export const FileList = ({type:displayType, tloi, loi, label: title, renderFiles} : FileListProps) => { +export const FileList = ({type:displayType, tloi, label: title, renderFiles} : FileListProps) => { const [open, setOpen] = useState(false); const [total, setTotal] = useState(0); const [runs, setRuns] = useState([]); - const getFileMap : (run:RunStatus|WorkflowRun) => {[name:string]: RunBinding} = (run) => { + const getFileMap : (run:RunStatus|Execution) => Record = (run) => { if (displayType === 'input') { - let newInputs : {[name:string]: RunBinding} = {}; - Object.keys(run.inputs || {}).forEach((inName) => { - let data = run.inputs[inName]; - if (data.datatype == null || !data.datatype.startsWith("http://www.w3.org/2001/XMLSchema#s")) { - newInputs[inName] = run.inputs[inName]; - } - }); - return newInputs; + return (run.inputs || []).reduce>((acc, binding) => { + acc[binding.variable] = binding; + return acc; + }, {}); } - if (!loi) - return (run.outputs ? run.outputs : {}) - let doNoStore : string[] = []; - //TODO: - //[...loi.workflows, ...loi.metaWorkflows].forEach((wf) => { - // wf.bindings.forEach((b) => { - // if (b.binding === '_DO_NO_STORE_') - // doNoStore.push(b.variable); - // }) - //}); - let newOutputs : {[name:string]: RunBinding} = {}; - Object.keys(run.outputs || {}).forEach((outName) => { - if (!doNoStore.some(n => outName.startsWith(n))) { - newOutputs[outName] = run.outputs[outName]; - } + + if (tloi) [...tloi.workflows, ...tloi.metaWorkflows].forEach((wf) => { + wf.outputs.forEach((b) => { + if (b.type === 'DROP') + doNoStore.push(b.variable); + }) }); - return newOutputs; + return (run.outputs || []).reduce>((acc, binding) => { + if (!doNoStore.some(n => n === binding.variable)) { + acc[binding.variable] = binding; + } + return acc; + }, {}); } // Adds source and all runs useEffect(() => { if (tloi) { - let allWfs : Workflow[] = []; // TODO: [ ...(tloi.workflows ? tloi.workflows : []) , ...(tloi.metaWorkflows ? tloi.metaWorkflows : []) ]; + let allWfs : WorkflowInstantiation[] = [ ...(tloi.workflows ? tloi.workflows : []) , ...(tloi.metaWorkflows ? tloi.metaWorkflows : []) ]; let allRuns : RunStatus[] = []; let i = 0; - allWfs.forEach((wf:Workflow) => { - if (wf.runs !== undefined) { - Object.values(wf.runs).forEach((run:WorkflowRun) => { - let list = getFileMap(run); - let length = Object.keys(list).length; - if (length > 0) { - allRuns.push({ - ...run, - source: wf.source - }); - i += length; - } - }); - } + allWfs.forEach(wf => { + (wf.executions || []).forEach(run => { + let list = getFileMap(run); + let length = Object.keys(list).length; + if (length > 0) { + allRuns.push({ + ...run, + source: wf.source + }); + i += length; + } + }); }); setTotal(i); setRuns(allRuns); @@ -81,7 +70,7 @@ export const FileList = ({type:displayType, tloi, loi, label: title, renderFiles }, [tloi]) const renderRunTitle = (run:RunStatus) => { - return run.id.replace(/.*#/,'').replace(/--.*/,''); + return run.externalId.replace(/.*#/,'').replace(/--.*/,''); } return ( @@ -113,16 +102,16 @@ export const FileList = ({type:displayType, tloi, loi, label: title, renderFiles {Object.keys(getFileMap(run)).map((id:string, i:number) => { let fileMap = getFileMap(run); - let value: RunBinding = fileMap[id]; - if (value.id == null) + let value: VariableBinding = fileMap[id]; + if ((value.binding||[]).length === 0) return null - let filename: string = value.id.replace(/.*#/, '').replace(/SHA[\d\w]{6}_/, '').replace(/-\w{24,25}$/, ''); + let filename: string = value.binding[0].replace(/.*#/, '').replace(/SHA[\d\w]{6}_/, '').replace(/-\w{24,25}$/, ''); return {i+1} - + {/*renderFile(run, id)*/} diff --git a/src/components/goals/GoalsPreview.tsx b/src/components/goals/GoalsPreview.tsx index 8abf615..533dc52 100644 --- a/src/components/goals/GoalsPreview.tsx +++ b/src/components/goals/GoalsPreview.tsx @@ -51,7 +51,7 @@ export const HypothesisPreview = ({hypothesis, displayDeleteButton=true, display [...TLOIs].sort((t1, t2) => { return new Date(t2.dateCreated!).getTime() - new Date(t1.dateCreated!).getTime(); }).forEach((t) => { - if (t.parentGoal.id === hypothesis.id && t.parentLoi.id && !usedLOIs.has(t.parentLoi.id)) { + if (t.parentLoi && t.parentGoal.id === hypothesis.id && t.parentLoi.id && !usedLOIs.has(t.parentLoi.id)) { usedLOIs.add(t.parentLoi.id); latest.push(t); } diff --git a/src/components/methods/MethodOutputSelector.tsx b/src/components/methods/MethodOutputSelector.tsx index 96987b6..3f741e3 100644 --- a/src/components/methods/MethodOutputSelector.tsx +++ b/src/components/methods/MethodOutputSelector.tsx @@ -1,7 +1,8 @@ -import { Grid, Select, MenuItem, Typography, Box, Tooltip, TextField } from "@mui/material"; +import { Grid, Select, MenuItem, Box, Tooltip } from "@mui/material"; import { MethodVariables, OutputType, VariableBinding } from "DISK/interfaces"; import { useEffect, useState } from "react"; import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import { SECOND_SELECTORS, OUTPUT_SELECTORS, OutputBindingValue } from "components/outputs"; interface MethodInputSelectorProps { variable: MethodVariables, // This variable represents a parameter @@ -9,53 +10,6 @@ interface MethodInputSelectorProps { onChange?: (newBinding:VariableBinding) => void, } -interface SelectorField { - label:string, - tooltip:string, -} - -interface SelectorExtraValue extends SelectorField { - value: string, -} - -const OUTPUT_SELECTORS : Record = { - DROP: { - label: "Ignore output", - tooltip: "Do not store the file on DISK. The file will still be available on WINGS." - }, - SAVE: { - label: "Save file", - tooltip: "The file will be stored on the DISK server." - }, - PROCESS: { - label: "Process file", - tooltip: "The file will be read by DISK." - } -} - -const SECOND_SELECTORS : Record> = { - DROP: {}, - PROCESS: { - _CONFIDENCE_VALUE_: { - label: "Process as confidence value file", - tooltip: "The first line of the file should be a point floating number.", - } - }, - SAVE: { - _DOWNLOAD_ONLY_: { - label: "Provide download link", - tooltip: "The file will be available for download to any DISK user.", - }, _IMAGE_: { - label: "Store as image to be displayed", - tooltip: "The file will be available for visualization and download.", - }, _VISUALIZE_: { - label: "Store as main visualization", - tooltip: "The file will be available for visualization and download." + - "The latest version of this file will be show in the hypothesis page.", - } - } -} - export const MethodOutputSelector = ({variable, value, onChange}:MethodInputSelectorProps) => { const [paramType, setParamType] = useState('DROP'); const [dataType, setDatatype] = useState(''); @@ -118,7 +72,7 @@ export const MethodOutputSelector = ({variable, value, onChange}:MethodInputSele return case 'DROP': default: @@ -145,9 +99,9 @@ export const MethodOutputSelector = ({variable, value, onChange}:MethodInputSele {renderBindingSelector()} - {SECOND_SELECTORS[paramType] && SECOND_SELECTORS[paramType][bindValue] && ( + {SECOND_SELECTORS[paramType] && SECOND_SELECTORS[paramType][bindValue as OutputBindingValue] && ( - + diff --git a/src/components/methods/WorkflowList.tsx b/src/components/methods/WorkflowList.tsx index 395be49..3a23a12 100644 --- a/src/components/methods/WorkflowList.tsx +++ b/src/components/methods/WorkflowList.tsx @@ -68,23 +68,6 @@ export const WorkflowSeedList = ({editable, workflows: inputWorkflows, metaworkf } const onWorkflowSave = (wf:WorkflowSeed) => { - //FIXME: This is a hack, there are no way to send multiple strings as a list - // so we transform demographic_value into a list here. - //wf.bindings = wf.bindings.map((vb:VariableBinding) => { - // let curBinding = vb.binding; - // //TODO: - // //if (vb.variable === 'demographic_value') { - // // if (!curBinding.startsWith("[")) curBinding = '[' + curBinding; - // // if (!curBinding.endsWith("]")) curBinding += "]"; - // //} - // return { - // variable: vb.variable, - // binding: curBinding, - // isArray: vb.isArray, - // type: vb.type, - // } as VariableBinding; - //}); - //----- if (notifyChange) { let curWfs : WorkflowSeed[] = [ ...(editingMeta ? metaWorkflows : workflows) ]; if (selectedWorkflow) curWfs[editingIndex] = wf; //Editing diff --git a/src/components/methods/WorkflowSeedPreview.tsx b/src/components/methods/WorkflowSeedPreview.tsx index 7ddec4b..f9956f6 100644 --- a/src/components/methods/WorkflowSeedPreview.tsx +++ b/src/components/methods/WorkflowSeedPreview.tsx @@ -114,13 +114,16 @@ export const WorkflowSeedPreview = ({workflow:wf, button:externalButton, onDelet editedValue = "This file will be make available to download" fontS = "italic" } else if (value === "_IMAGE_") { - editedValue = "This file will be used on visualizations" + editedValue = "This file will be used to generate visualizations"; fontS = "italic" } else if (value === "_VISUALIZE_") { editedValue = "The latest version of this file will be show on TLOI preview" fontS = "italic" } else if (value === "_BRAIN_VISUALIZATION_") { - editedValue = "This file is a brain visualization configuration file" + editedValue = "This file will be used to generate a brain visualization" + fontS = "italic" + } else if (value === "_SHINY_LOG_") { + editedValue = "This file will be used to generate Shiny visualizations" fontS = "italic" } else if (value === "_CONFIDENCE_VALUE_") { editedValue = "This file contains the confidence value" diff --git a/src/components/modal/BrainModal.tsx b/src/components/modal/BrainModal.tsx index c1c1c30..af15066 100644 --- a/src/components/modal/BrainModal.tsx +++ b/src/components/modal/BrainModal.tsx @@ -6,33 +6,26 @@ import { BrainCfgItem, BrainVisualization } from "./BrainVisualization"; import { useGetPrivateFileAsTextQuery } from "redux/apis/server"; interface BrainModalProps { - source: string, - brainUrl: string, + brainCfg: string, } -export const BrainModal = ({source, brainUrl} : BrainModalProps) => { +export const BrainModal = ({brainCfg: brainCfgTxt} : BrainModalProps) => { const [open, setOpen] = useState(false); const [brainCfg, setBrainCfg] = useState(null); - const {data, isLoading:loading} = useGetPrivateFileAsTextQuery({dataSource: source, dataId: brainUrl}, {skip:!open}); useEffect(() => { - if (brainUrl) - setBrainCfg(null); - }, [brainUrl]); - - useEffect(() => { - if (data && open) { - if (data.startsWith('[')) { - let cfg: BrainCfgItem[] = JSON.parse(data.replaceAll('\n', ' ').replaceAll('\t', '')); + if (brainCfgTxt && open) { + if (brainCfgTxt.startsWith('[')) { + let cfg: BrainCfgItem[] = JSON.parse(brainCfgTxt.replaceAll('\n', ' ').replaceAll('\t', '')); setBrainCfg(cfg); } else { - console.warn("Could not decode:", data); + console.warn("Could not decode:", brainCfgTxt); setBrainCfg(null); } } else { setBrainCfg(null); } - }, [data, open]); + }, [brainCfgTxt, open]); const onOpenDialog = () => { setOpen(true); @@ -56,8 +49,8 @@ export const BrainModal = ({source, brainUrl} : BrainModalProps) => { - - {loading ? + + {false ? : (brainCfg != null ? diff --git a/src/components/modal/ShinyModal.tsx b/src/components/modal/ShinyModal.tsx index 9d48a25..bdeac3f 100644 --- a/src/components/modal/ShinyModal.tsx +++ b/src/components/modal/ShinyModal.tsx @@ -5,31 +5,26 @@ import QueryStatsIcon from '@mui/icons-material/QueryStats'; import { useGetPrivateFileAsTextQuery } from "redux/apis/server"; interface ShinyModalProps { - source: string, - shinyUrl: string, + shinyLog: string, } -export const ShinyModal = ({source, shinyUrl} : ShinyModalProps) => { +export const ShinyModal = ({shinyLog} : ShinyModalProps) => { const [open, setOpen] = useState(false); - const {data, isLoading:loading} = useGetPrivateFileAsTextQuery({dataSource: source, dataId: shinyUrl}, {skip:!open}); const [url, setUrl] = useState(""); useEffect(() => { - if (shinyUrl) - setUrl(""); - }, [shinyUrl]); - - useEffect(() => { - if (data) { - let m = data.match("Application successfully deployed to (.*)"); + if (shinyLog) { + let m = shinyLog.match("Application successfully deployed to (.*)"); if (m && m.length === 2) { setUrl(m[1]); } else { - console.warn("Could not decode:", data); + console.warn("Could not decode:", shinyLog); setUrl(""); } + } else { + setUrl(""); } - }, [data]); + }, [shinyLog]); const onOpenDialog = () => { setOpen(true); @@ -54,7 +49,7 @@ export const ShinyModal = ({source, shinyUrl} : ShinyModalProps) => { - {loading ? + {false ? : (url && diff --git a/src/components/outputs/index.tsx b/src/components/outputs/index.tsx new file mode 100644 index 0000000..8ee7e6d --- /dev/null +++ b/src/components/outputs/index.tsx @@ -0,0 +1,59 @@ +import { OutputType } from "DISK/interfaces" + +export interface SelectorField { + label:string, + tooltip:string, +} + +export const OUTPUT_SELECTORS : Record = { + DROP: { + label: "Ignore output", + tooltip: "Do not store the file on DISK. The file will still be available on WINGS." + }, + SAVE: { + label: "Save file", + tooltip: "The file will be stored on the DISK server." + }, + PROCESS: { + label: "Process file", + tooltip: "The file will be read by DISK." + } +} + +export type OutputBindingValue = '_CONFIDENCE_VALUE_' | + '_SHINY_LOG_' | + '_BRAIN_VISUALIZATION_' | + '_DOWNLOAD_ONLY_' | + '_IMAGE_' | + '_VISUALIZE_'; + +export const SECOND_SELECTORS : Record>> = { + DROP: {}, + PROCESS: { + _CONFIDENCE_VALUE_: { + label: "Process as confidence value file", + tooltip: "The first line of the file will be read as a number.", + }, + _SHINY_LOG_: { + label: "Process as Shiny log", + tooltip: "The file is a shiny output log, where we can find links to visualizations.", + }, + _BRAIN_VISUALIZATION_: { + label: "Process as brain visualization", + tooltip: "The file must be a json configuration file.", + }, + }, + SAVE: { + _DOWNLOAD_ONLY_: { + label: "Provide download link", + tooltip: "The file will be available for download to any DISK user.", + }, _IMAGE_: { + label: "Store as image to be displayed", + tooltip: "The file will be available for visualization and download.", + }, _VISUALIZE_: { + label: "Store as main visualization", + tooltip: "The file will be available for visualization and download." + + "The latest version of this file will be show in the hypothesis page.", + } + } +} \ No newline at end of file diff --git a/src/components/outputs/table.tsx b/src/components/outputs/table.tsx new file mode 100644 index 0000000..46281f8 --- /dev/null +++ b/src/components/outputs/table.tsx @@ -0,0 +1,112 @@ +import { Box } from "@mui/material"; +import { GoalResult, TriggeredLineOfInquiry, VariableBinding, WorkflowInstantiation } from "DISK/interfaces"; +import { getId } from "DISK/util"; +import { NarrativeModal } from "components/modal/NarrativeModal"; +import { TLOIDeleteButton } from "components/tlois/TLOIDeleteButton"; +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 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"; + +export type TLOICell = (tloi: TriggeredLineOfInquiry, i: number) => JSX.Element; +export type ColumnName = '#' | "Date" | "Run Status" | "Input Files" | "Output Files" | "Confidence Value" | "Extras"; + +export const getIconStatus = (status: TriggeredLineOfInquiry["status"]) => { + if (status === 'FAILED') + return ; + if (status === 'SUCCESSFUL') + return ; + if (status === 'QUEUED' || status === 'RUNNING') + return ; +} + +const renderConfidenceValue = (tloi: TriggeredLineOfInquiry, i: number) => { + let all = [...(tloi.workflows||[]), ...(tloi.metaWorkflows||[])]; + //We show the bigger p-value from the executions. + let result : GoalResult | undefined = undefined; + let min = 0; + for (const wf of all) { + for (const e of (wf.executions||[])) { + if (!!e.result && e.result.confidenceValue > min) { + result = e.result; + min = e.result.confidenceValue; + } + } + } + if (!result) + return <>-; + return + {displayConfidenceValue(result.confidenceValue)} + {result.confidenceType ? ` (${result.confidenceType})` : " (P-value)"} + +} + +const renderOptionalButtons = (cur: TriggeredLineOfInquiry) => { + let shinyLog: string = ""; + let brainCfg: string = ""; + + + [ ...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]; + } + } + })); + }) + }); + + return ( + {!!shinyLog && ()} + {!!brainCfg && ()} + ); +} + +export const ALL_COLUMNS: Record = { + '#': (tloi: TriggeredLineOfInquiry, i: number) => + + {i + 1} + , + 'Date': (tloi: TriggeredLineOfInquiry, i: number) => + + {tloi.dateCreated} + , + 'Run Status': (tloi: TriggeredLineOfInquiry, i: number) => + + {getIconStatus(tloi.status)} + + {tloi.status === 'SUCCESSFUL' ? 'DONE' : tloi.status} + + , + 'Input Files': (tloi: TriggeredLineOfInquiry, i: number) => + , + 'Output Files': (tloi: TriggeredLineOfInquiry, i: number) => + { + (tloi.status === 'SUCCESSFUL' && ) + }, + 'Confidence Value': renderConfidenceValue, + 'Extras': (tloi: TriggeredLineOfInquiry, i: number) => + + + {renderOptionalButtons(tloi)} + + + +} \ No newline at end of file diff --git a/src/components/tlois/TLOIBundle.tsx b/src/components/tlois/TLOIBundle.tsx index cc67504..5b55e5f 100644 --- a/src/components/tlois/TLOIBundle.tsx +++ b/src/components/tlois/TLOIBundle.tsx @@ -9,6 +9,7 @@ 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, @@ -37,60 +38,49 @@ export const TLOIBundle = ({loiId, goal}:TLOIBundleProps) => { }, [TLOIs, loiId, goal, TLOILoading]) useEffect(() => { - // Sets name for this bundle - if (list.length > 0) { - setName(list[0].name.replace("Triggered: ","")); - } else { - setName(""); - } - }, [list]) + setName(loi && loi.name ? loi.name : ""); + }, [loi]) useEffect(() => { let plots = new Set(); let viz = new Set(); let ignore = new Set(); let vizMap : {[name:string] : [string, string]} = {}; - //TODO: - //if (loi) { - // [...loi.workflows, ...loi.metaWorkflows ].map(wf => wf.bindings).flat().forEach((binding) => { - // if (binding.binding.startsWith("_") && binding.binding.endsWith("_")) { - // switch (binding.binding as OutputSelectorIds) { - // case "_CONFIDENCE_VALUE_": - // plots.add(binding.variable); - // break; - // case "_VISUALIZE_": - // viz.add(binding.variable); - // break; - // case "_DO_NO_STORE_": - // ignore.add(binding.variable); - // break; - // default: - // break; - // } - // } - // }); - //} + 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; + } + } + } + }); + } if (list.length > 0) { let last = list[list.length - 1]; let vizArr = Array.from(viz); let ignoreArr = Array.from(ignore); - //TODO: - //[...last.workflows, ...last.metaWorkflows].forEach((wf) => { - // if (wf.runs) { - // Object.values(wf.runs) - // .flat().map(run => - // Object.keys(run.outputs) - // .filter(key => vizArr.some(v => key.startsWith(v)) && !ignoreArr.some(i => key.startsWith(i))) - // .map(key => { - // let vizName = vizArr.filter(v => key.startsWith(v))[0]; - // let url = run.outputs[key].id; - // if (url != null) { - // vizMap[vizName] = [wf.source, url]; - // } - // }) - // ) - // } - //}) + [...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 + } + } + }) + }) } setMainVisualizations(vizMap); diff --git a/src/components/tlois/TLOITable.tsx b/src/components/tlois/TLOITable.tsx index 41aa782..c27841b 100644 --- a/src/components/tlois/TLOITable.tsx +++ b/src/components/tlois/TLOITable.tsx @@ -4,27 +4,14 @@ import { PATH_TLOIS } from "constants/routes"; import { Link } from "react-router-dom"; import { TLOIEditButton } from "./TLOIEdit"; import { Fragment, useState, useEffect } from "react"; -import { LineOfInquiry, TriggeredLineOfInquiry, Workflow } from "DISK/interfaces"; -import ErrorIcon from '@mui/icons-material/ErrorOutline'; -import WaitIcon from '@mui/icons-material/HourglassBottom'; -import CheckIcon from '@mui/icons-material/Check'; +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"; - -const getIconStatus = (status: TriggeredLineOfInquiry["status"]) => { - if (status === 'FAILED') - return ; - if (status === 'SUCCESSFUL') - return ; - if (status === 'QUEUED' || status === 'RUNNING') - return ; -} - -type TLOICell = (tloi: TriggeredLineOfInquiry, i: number) => JSX.Element; +import { ALL_COLUMNS, ColumnName } from "components/outputs/table"; interface TLOITableProps { list: TriggeredLineOfInquiry[] @@ -33,42 +20,7 @@ interface TLOITableProps { } export const TLOITable = ({list, loi, showConfidence} : TLOITableProps) => { - const COLUMNS: { [name: string]: TLOICell } = { - '#': (tloi: TriggeredLineOfInquiry, i: number) => - - {i + 1} - , - 'Date': (tloi: TriggeredLineOfInquiry, i: number) => - - {tloi.dateCreated} - , - 'Run Status': (tloi: TriggeredLineOfInquiry, i: number) => - - {getIconStatus(tloi.status)} - - {tloi.status === 'SUCCESSFUL' ? 'DONE' : tloi.status} - - , - 'Input Files': (tloi: TriggeredLineOfInquiry, i: number) => - , - 'Output Files': (tloi: TriggeredLineOfInquiry, i: number) => - { - (tloi.status === 'SUCCESSFUL' && ) - }, - //'Confidence Value': (tloi: TriggeredLineOfInquiry, i: number) => - // - // {tloi.status === 'SUCCESSFUL' && displayConfidenceValue(tloi.confidenceValue)} - // {tloi.confidenceType ? ` (${tloi.confidenceType})` : " (P-value)"} - // , - 'Extras': (tloi: TriggeredLineOfInquiry, i: number) => - - - {renderOptionalButtons(tloi)} - - - - } - const [visibleColumns, setVisibleColumns] = useState(['#', "Date", "Run Status", "Input Files", "Output Files", "Confidence Value", "Extras"]); + const [visibleColumns, setVisibleColumns] = useState(['#', "Date", "Run Status", "Input Files", "Output Files", "Confidence Value", "Extras"]); useEffect(() => { if (!showConfidence) @@ -77,34 +29,6 @@ export const TLOITable = ({list, loi, showConfidence} : TLOITableProps) => { setVisibleColumns(['#', "Date", "Run Status", "Input Files", "Output Files", "Confidence Value", "Extras"]); }, [showConfidence]) - const renderOptionalButtons = (cur:TriggeredLineOfInquiry) => { - let shinyUrl : string = ""; - let shinySource : string = ""; - let brainUrl : string = ""; - let brainSource : string = ""; - // TODO: - //[ ...cur.workflows, ...cur.metaWorkflows ].forEach((wf:Workflow) => { - // Object.values(wf.runs||{}).forEach((run) => { - // if (run.outputs) { - // Object.keys(run.outputs).forEach(((name:string) => { - // if (name === SHINY_FILENAME) { - // shinyUrl = run ? run.outputs[name].id || "" : ""; - // shinySource = wf.source; - // } else if (name === BRAIN_FILENAME) { - // brainUrl = run ? run.outputs[name].id || "" : ""; - // brainSource = wf.source; - // } - // })); - // } - // }) - //}); - - return ( - {!!shinyUrl && ()} - {!!brainUrl && ()} - ); - } - return @@ -115,9 +39,9 @@ export const TLOITable = ({list, loi, showConfidence} : TLOITableProps) => { {list.map((tloi:TriggeredLineOfInquiry, i:number) => { - visibleColumns.map((name:string, j:number) => + visibleColumns.map((name:ColumnName, j:number) => - { COLUMNS[name](tloi, i) } + { ALL_COLUMNS[name](tloi, i) } ) } diff --git a/src/pages/Goals/GoalView.tsx b/src/pages/Goals/GoalView.tsx index 1b9afa3..070f5bc 100644 --- a/src/pages/Goals/GoalView.tsx +++ b/src/pages/Goals/GoalView.tsx @@ -27,7 +27,7 @@ export const HypothesisView = () => { useEffect(() => { let list : string[] = (TLOIs||[]) - .filter((tloi) => tloi.parentGoal.id === goalId) + .filter((tloi) => getId(tloi.parentGoal) === goalId && tloi.parentLoi != null) .map((tloi) => tloi.parentLoi.id); setLOIList(Array.from(new Set(list))); }, [TLOIs, goalId]);