From 33cdb5b60c562dd6cde74c4e4bbf1deffe697630 Mon Sep 17 00:00:00 2001 From: Lijiaoa <61399850+Lijiaoa@users.noreply.github.com> Date: Thu, 4 Aug 2022 15:27:16 +0800 Subject: [PATCH] Bug fix: after clicking compare button, the dropdown button (for selecting different keys) is not shown (#4990) --- .../common/ExpandableDetails/OpenRow.tsx | 4 +- .../experiment/trialdetail/TrialsDetail.tsx | 7 +- .../trialdetail/chart/DefaultMetricPoint.tsx | 11 +- .../experiment/trialdetail/chart/Duration.tsx | 28 +- .../trialdetail/chart/Intermediate.tsx | 82 ++---- .../experiment/trialdetail/chart/Para.tsx | 9 +- .../trialdetail/table/TableList.tsx | 84 ++---- .../{Compare.tsx => CompareIndex.tsx} | 193 +++++++------ .../table/tableFunction/CustomizedTrial.tsx | 4 +- ts/webui/src/static/const.ts | 2 +- ts/webui/src/static/function.ts | 17 +- ts/webui/src/static/interface.ts | 53 +--- ts/webui/src/static/model/experiment.ts | 1 - ts/webui/src/static/model/searchspace.ts | 7 +- ts/webui/src/static/model/trial.ts | 264 +++++++----------- ts/webui/src/static/model/trialmanager.ts | 32 ++- 16 files changed, 333 insertions(+), 465 deletions(-) rename ts/webui/src/components/experiment/trialdetail/table/tableFunction/{Compare.tsx => CompareIndex.tsx} (58%) diff --git a/ts/webui/src/components/common/ExpandableDetails/OpenRow.tsx b/ts/webui/src/components/common/ExpandableDetails/OpenRow.tsx index 4c8ec7eca..8b021ba9e 100644 --- a/ts/webui/src/components/common/ExpandableDetails/OpenRow.tsx +++ b/ts/webui/src/components/common/ExpandableDetails/OpenRow.tsx @@ -26,7 +26,7 @@ const OpenRow = (props): any => { const trialId = props.trialId; const trial = TRIALS.getTrial(trialId); const logPathRow = trial.info.logPath || "This trial's log path is not available."; - const originParameters = trial.description.parameters; + const originParameters = trial.parameter; const hasVisualHyperParams = RETIARIIPARAMETERS in originParameters; const hideMessageInfo = (): void => { @@ -55,7 +55,7 @@ const OpenRow = (props): any => { const copyParams = (trial: Trial): void => { // get copy parameters - const params = JSON.stringify(reformatRetiariiParameter(trial.description.parameters as any), null, 4); + const params = JSON.stringify(reformatRetiariiParameter(trial.parameter as any), null, 4); if (copy.default(params)) { getCopyStatus('Successfully copy parameters to clipboard in form of python dict !', 'success'); } else { diff --git a/ts/webui/src/components/experiment/trialdetail/TrialsDetail.tsx b/ts/webui/src/components/experiment/trialdetail/TrialsDetail.tsx index 89573a8be..b5fa96f36 100644 --- a/ts/webui/src/components/experiment/trialdetail/TrialsDetail.tsx +++ b/ts/webui/src/components/experiment/trialdetail/TrialsDetail.tsx @@ -48,6 +48,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { render(): React.ReactNode { const { whichChart } = this.state; const source = TRIALS.toArray(); + const paraSource = TRIALS.succeededTrials(); const succeededTrialIds = TRIALS.succeededTrials().map(trial => trial.id); return ( @@ -74,12 +75,12 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { {/* */} - + {/* */} - + {/* */} { key='Intermediate result' > {/* *why this graph has small footprint? */} - + diff --git a/ts/webui/src/components/experiment/trialdetail/chart/DefaultMetricPoint.tsx b/ts/webui/src/components/experiment/trialdetail/chart/DefaultMetricPoint.tsx index dc2cc7483..70bd57b46 100644 --- a/ts/webui/src/components/experiment/trialdetail/chart/DefaultMetricPoint.tsx +++ b/ts/webui/src/components/experiment/trialdetail/chart/DefaultMetricPoint.tsx @@ -138,16 +138,17 @@ class DefaultPoint extends React.Component trial.sequenceId, trial.acc === undefined ? 0 : this.formatAccuracy(trial.acc[userSelectAccuracyNumberKey]), trial.id, - trial.description.parameters + trial.parameter ]); } else { data = trials.map(trial => [ trial.sequenceId, this.formatAccuracy(trial.accuracy), trial.id, - trial.description.parameters + trial.parameter ]); } + return { symbolSize: 6, type: 'scatter', @@ -163,7 +164,7 @@ class DefaultPoint extends React.Component best.sequenceId, best.acc === undefined ? 0 : this.formatAccuracy(best.acc[userSelectAccuracyNumberKey]), best.id, - best.description.parameters + best.parameter ] ]; for (let i = 1; i < trials.length; i++) { @@ -176,7 +177,7 @@ class DefaultPoint extends React.Component trial.sequenceId, trial.acc === undefined ? 0 : this.formatAccuracy(trial.acc[userSelectAccuracyNumberKey]), best.id, - trial.description.parameters + trial.parameter ]); best = trial; } else { @@ -184,7 +185,7 @@ class DefaultPoint extends React.Component trial.sequenceId, best.acc === undefined ? 0 : this.formatAccuracy(best.acc[userSelectAccuracyNumberKey]), best.id, - trial.description.parameters + trial.parameter ]); } } diff --git a/ts/webui/src/components/experiment/trialdetail/chart/Duration.tsx b/ts/webui/src/components/experiment/trialdetail/chart/Duration.tsx index 83df4d22a..1caac76ce 100644 --- a/ts/webui/src/components/experiment/trialdetail/chart/Duration.tsx +++ b/ts/webui/src/components/experiment/trialdetail/chart/Duration.tsx @@ -1,18 +1,19 @@ import * as React from 'react'; import ReactEcharts from 'echarts-for-react'; -import { TableObj, EventMap } from '@static/interface'; -import { filterDuration, convertDuration } from '@static/function'; +import { EventMap } from '@static/interface'; +import { Trial } from '@model/trial'; +import { convertDuration } from '@static/function'; import 'echarts/lib/chart/bar'; import 'echarts/lib/component/tooltip'; import 'echarts/lib/component/title'; interface Runtrial { - trialId: string[]; + trialId: number[]; trialTime: number[]; } interface DurationProps { - source: Array; + source: Trial[]; } interface DurationState { @@ -31,12 +32,10 @@ class Duration extends React.Component { }; } - initDuration = (source: Array): any => { + initDuration = (source: Trial[]): any => { const trialId: number[] = []; const trialTime: number[] = []; - const trialJobs = source.filter(filterDuration); - - trialJobs.forEach(item => { + source.forEach(item => { trialId.push(item.sequenceId); trialTime.push(item.duration); }); @@ -146,17 +145,16 @@ class Duration extends React.Component { }; }; - drawDurationGraph = (source: Array): void => { + drawDurationGraph = (source: Trial[]): void => { // why this function run two times when props changed? - const trialId: string[] = []; + const trialId: number[] = []; const trialTime: number[] = []; const trialRun: Runtrial[] = []; - const trialJobs = source.filter(filterDuration); - Object.keys(trialJobs).map(item => { - const temp = trialJobs[item]; - trialId.push(temp.sequenceId); - trialTime.push(temp.duration); + source.forEach(item => { + trialId.push(item.sequenceId); + trialTime.push(item.duration); }); + trialRun.push({ trialId: trialId, trialTime: trialTime diff --git a/ts/webui/src/components/experiment/trialdetail/chart/Intermediate.tsx b/ts/webui/src/components/experiment/trialdetail/chart/Intermediate.tsx index d0a72decf..2850c9d25 100644 --- a/ts/webui/src/components/experiment/trialdetail/chart/Intermediate.tsx +++ b/ts/webui/src/components/experiment/trialdetail/chart/Intermediate.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Stack, PrimaryButton, Toggle, IStackTokens } from '@fluentui/react'; -import { TooltipForIntermediate, TableObj, Intermedia, EventMap } from '@static/interface'; +import { TooltipForIntermediate, EventMap, allTrialsIntermediateChart } from '@static/interface'; import { reformatRetiariiParameter } from '@static/function'; import ReactEcharts from 'echarts-for-react'; import 'echarts/lib/component/tooltip'; @@ -11,20 +11,19 @@ const stackTokens: IStackTokens = { }; interface IntermediateState { - detailSource: Array; + detailSource: allTrialsIntermediateChart[]; interSource: object; - filterSource: Array; + filterSource: allTrialsIntermediateChart[]; eachIntermediateNum: number; // trial's intermediate number count isLoadconfirmBtn: boolean; isFilter?: boolean | undefined; length: number; - clickCounts: number; // user filter intermediate click confirm btn's counts startMediaY: number; endMediaY: number; } interface IntermediateProps { - source: Array; + source: allTrialsIntermediateChart[]; } class Intermediate extends React.Component { @@ -43,43 +42,24 @@ class Intermediate extends React.Component isLoadconfirmBtn: false, isFilter: false, length: 100000, - clickCounts: 0, startMediaY: 0, endMediaY: 100 }; } - drawIntermediate = (source: Array): void => { + drawIntermediate = (source: allTrialsIntermediateChart[]): void => { if (source.length > 0) { this.setState({ length: source.length, detailSource: source }); const { startMediaY, endMediaY } = this.state; - const trialIntermediate: Array = []; - Object.keys(source).map(item => { - const temp = source[item]; - trialIntermediate.push({ - name: temp.id, - trialNum: temp.sequenceId, - data: temp.description.intermediate, - type: 'line', - hyperPara: temp.description.parameters - }); - }); - // find max intermediate number - trialIntermediate.sort((a, b) => { + const xAxis: number[] = []; + // find having most intermediate number + source.sort((a, b) => { return b.data.length - a.data.length; }); - const legend: string[] = []; - // max length - const length = trialIntermediate[0].data.length; - const xAxis: number[] = []; - Object.keys(trialIntermediate).map(item => { - const temp = trialIntermediate[item]; - legend.push(temp.name); - }); - for (let i = 1; i <= length; i++) { + for (let i = 1; i <= source[0].data.length; i++) { xAxis.push(i); } const option = { @@ -89,20 +69,21 @@ class Intermediate extends React.Component confine: true, formatter: function (data: TooltipForIntermediate): React.ReactNode { const trialId = data.seriesName; - let parameters = {}; - let trialNum = 0; - const temp = trialIntermediate.find(key => key.name === trialId); - if (temp !== undefined) { - parameters = temp.hyperPara; - trialNum = temp.trialNum; + // parameter and trialNo need to have the init value otherwise maybe cause page broke down + let parameter = {}; + let trialNo = 0; + const renderTrial = source.find(key => key.name === trialId); + if (renderTrial !== undefined) { + parameter = renderTrial.parameter; + trialNo = renderTrial.sequenceId; } return `
-
Trial No.: ${trialNum}
+
Trial No.: ${trialNo}
Trial ID: ${trialId}
Intermediate: ${data.data}
Parameters:
${JSON.stringify(
-                                    reformatRetiariiParameter(parameters),
+                                    reformatRetiariiParameter(parameter),
                                     null,
                                     4
                                 )}
@@ -137,7 +118,7 @@ class Intermediate extends React.Component end: endMediaY } ], - series: trialIntermediate + series: source }; this.setState({ interSource: option @@ -164,7 +145,7 @@ class Intermediate extends React.Component // confirm btn function [filter data] filterLines = (): void => { - const filterSource: Array = []; + const filterSource: allTrialsIntermediateChart[] = []; this.setState({ isLoadconfirmBtn: true }, () => { const { source } = this.props; // get input value @@ -175,32 +156,29 @@ class Intermediate extends React.Component if (pointVal === '' || minVal === '') { alert('Please input filter message'); } else { - // user not input max value const position = JSON.parse(pointVal); const min = JSON.parse(minVal); if (maxVal === '') { - Object.keys(source).map(item => { - const temp = source[item]; - const val = temp.description.intermediate[position - 1]; + // user not input max value + for (const item of source) { + const val = item.data[position - 1]; if (val >= min) { - filterSource.push(temp); + filterSource.push(item); } - }); + } } else { const max = JSON.parse(maxVal); - Object.keys(source).map(item => { - const temp = source[item]; - const val = temp.description.intermediate[position - 1]; + for (const item of source) { + const val = item.data[position - 1]; if (val >= min && val <= max) { - filterSource.push(temp); + filterSource.push(item); } - }); + } } this.setState({ filterSource: filterSource }); this.drawIntermediate(filterSource); } - const counts = this.state.clickCounts + 1; - this.setState({ isLoadconfirmBtn: false, clickCounts: counts }); + this.setState({ isLoadconfirmBtn: false }); }); }; diff --git a/ts/webui/src/components/experiment/trialdetail/chart/Para.tsx b/ts/webui/src/components/experiment/trialdetail/chart/Para.tsx index 96af893a5..6acf52fbb 100644 --- a/ts/webui/src/components/experiment/trialdetail/chart/Para.tsx +++ b/ts/webui/src/components/experiment/trialdetail/chart/Para.tsx @@ -3,9 +3,9 @@ import * as d3 from 'd3'; import { Dropdown, IDropdownOption, Stack, DefaultButton } from '@fluentui/react'; import ParCoords from 'parcoord-es'; import { SearchSpace } from '@model/searchspace'; -import { filterByStatus } from '@static/function'; import { EXPERIMENT, TRIALS } from '@static/datamodel'; -import { TableObj, SingleAxis, MultipleAxes } from '@static/interface'; +import { SingleAxis, MultipleAxes } from '@static/interface'; +import { Trial } from '@model/trial'; import ChangeColumnComponent from '../ChangeColumnComponent'; import { optimizeModeValue } from './optimizeMode'; @@ -25,7 +25,7 @@ interface ParaState { } interface ParaProps { - trials: Array; + trials: Trial[]; searchSpace: SearchSpace; } @@ -304,9 +304,8 @@ class Para extends React.Component { private getTrialsAsObjectList(inferredSearchSpace: MultipleAxes, inferredMetricSpace: MultipleAxes): {}[] { const { trials } = this.props; - const succeededTrials = trials.filter(filterByStatus); - return succeededTrials.map(s => { + return trials.map(s => { const entries = Array.from(s.parameters(inferredSearchSpace).entries()); entries.push(...Array.from(s.metrics(inferredMetricSpace).entries())); const ret = {}; diff --git a/ts/webui/src/components/experiment/trialdetail/table/TableList.tsx b/ts/webui/src/components/experiment/trialdetail/table/TableList.tsx index 724a32149..c39f24798 100644 --- a/ts/webui/src/components/experiment/trialdetail/table/TableList.tsx +++ b/ts/webui/src/components/experiment/trialdetail/table/TableList.tsx @@ -13,22 +13,15 @@ import { import { Trial } from '@model/trial'; import { TOOLTIP_BACKGROUND_COLOR } from '@static/const'; import { EXPERIMENT, TRIALS } from '@static/datamodel'; -import { - convertDuration, - formatTimestamp, - copyAndSort, - parametersType, - _inferColumnTitle, - getIntermediateAllKeys -} from '@static/function'; -import { TableObj, SortInfo, SearchItems } from '@static/interface'; +import { convertDuration, formatTimestamp, copyAndSort, parametersType, _inferColumnTitle } from '@static/function'; +import { SortInfo, SearchItems } from '@static/interface'; import { blocked, copy, LineChart, tableListIcon } from '@components/fluent/Icon'; import Customize from './tableFunction/CustomizedTrial'; import TensorboardUI from './tableFunction/tensorboard/TensorboardUI'; import Search from './tableFunction/search/Search'; import ExpandableDetails from '@components/common/ExpandableDetails/ExpandableIndex'; import ChangeColumnComponent from '../ChangeColumnComponent'; -import Compare from './tableFunction/Compare'; +import Compare from './tableFunction/CompareIndex'; import KillJobIndex from './tableFunction/killJob/KillJobIndex'; import { getTrialsBySearchFilters } from './tableFunction/search/searchFunction'; import PaginationTable from '@components/common/PaginationTable'; @@ -43,7 +36,7 @@ type SearchOptionType = 'id' | 'trialnum' | 'status' | 'parameters'; const defaultDisplayedColumns = ['sequenceId', 'id', 'duration', 'status', 'latestAccuracy']; interface TableListProps { - tableSource: TableObj[]; + tableSource: Trial[]; } interface TableListState { @@ -55,12 +48,11 @@ interface TableListState { selectedRowIds: string[]; customizeColumnsDialogVisible: boolean; compareDialogVisible: boolean; - intermediateDialogTrial: TableObj | undefined; + intermediateDialogTrial: Trial[] | undefined; copiedTrialId: string | undefined; sortInfo: SortInfo; searchItems: Array; relation: Map; - intermediateKeyList: string[]; } class TableList extends React.Component { @@ -86,8 +78,7 @@ class TableList extends React.Component { copiedTrialId: undefined, sortInfo: { field: '', isDescend: true }, searchItems: [], - relation: parametersType(), - intermediateKeyList: [] + relation: parametersType() }; this._expandedTrialIds = new Set(); @@ -113,8 +104,7 @@ class TableList extends React.Component { selectedRowIds, intermediateDialogTrial, copiedTrialId, - searchItems, - intermediateKeyList + searchItems } = this.state; return ( @@ -145,10 +135,23 @@ class TableList extends React.Component { text='Compare' className='allList-compare' onClick={(): void => { - this.setState({ compareDialogVisible: true }); + this.setState({ + compareDialogVisible: true + }); }} disabled={selectedRowIds.length === 0} /> + {/* compare model: trial intermediates graph; table: id,no,status,default dict value */} + {compareDialogVisible && ( + selectedRowIds.includes(trial.id))} + onHideDialog={(): void => { + this.setState({ compareDialogVisible: false }); + }} + changeSelectTrialIds={this.changeSelectTrialIds} + /> + )} { }} /> )} - {compareDialogVisible && ( - selectedRowIds.includes(trial.id))} - onHideDialog={(): void => { - this.setState({ compareDialogVisible: false }); - }} - changeSelectTrialIds={this.changeSelectTrialIds} - /> - )} {intermediateDialogTrial !== undefined && ( { this.setState({ intermediateDialogTrial: undefined }); }} @@ -236,32 +226,21 @@ class TableList extends React.Component { ); } - private _trialsToTableItems(trials: TableObj[]): any[] { + private _trialsToTableItems(trials: Trial[]): any[] { // TODO: use search space and metrics space from TRIALS will cause update issues. const searchSpace = TRIALS.inferredSearchSpace(EXPERIMENT.searchSpaceNew); const metricSpace = TRIALS.inferredMetricSpace(); const { selectedRowIds } = this.state; const items = trials.map(trial => { - const ret = { - sequenceId: trial.sequenceId, - id: trial.id, - _checked: selectedRowIds.includes(trial.id) ? true : false, - startTime: (trial as Trial).info.startTime, // FIXME: why do we need info here? - endTime: (trial as Trial).info.endTime, - duration: trial.duration, - status: trial.status, - message: (trial as Trial).info.message || '--', - intermediateCount: trial.intermediates.length, - _expandDetails: this._expandedTrialIds.has(trial.id) // hidden field names should start with `_` - }; + const ret = trial.tableRecord; + ret['_checked'] = selectedRowIds.includes(trial.id) ? true : false; + ret['_expandDetails'] = this._expandedTrialIds.has(trial.id); // hidden field names should start with `_` for (const [k, v] of trial.parameters(searchSpace)) { ret[`space/${k.baseName}`] = v; } for (const [k, v] of trial.metrics(metricSpace)) { ret[`metric/${k.baseName}`] = v; } - ret['latestAccuracy'] = (trial as Trial).latestAccuracy; - ret['_formattedLatestAccuracy'] = (trial as Trial).formatLatestAccuracy(); return ret; }); @@ -397,9 +376,6 @@ class TableList extends React.Component { ...(k === 'status' && { // color status onRender: (record): React.ReactNode => ( - // kill 成功之后,重新拉取的数据如果有 endtime 字段,会马上render出user_cancel - // 的状态,反之,没有这个字段,table依然是部分刷新,只刷新duration,不会 - // 刷新 status {record.status} ) }), @@ -461,7 +437,7 @@ class TableList extends React.Component { } }} > -
{record._formattedLatestAccuracy}
+
{record.formattedLatestAccuracy}
) }), @@ -545,11 +521,9 @@ class TableList extends React.Component { title='Intermediate' onClick={(): void => { const { tableSource } = this.props; - const trial = tableSource.find(trial => trial.id === record.id) as TableObj; - const intermediateKeyListResult = getIntermediateAllKeys(trial); + const trial = tableSource.find(trial => trial.id === record.id) as Trial; this.setState({ - intermediateDialogTrial: trial, - intermediateKeyList: intermediateKeyListResult + intermediateDialogTrial: [trial] }); }} > diff --git a/ts/webui/src/components/experiment/trialdetail/table/tableFunction/Compare.tsx b/ts/webui/src/components/experiment/trialdetail/table/tableFunction/CompareIndex.tsx similarity index 58% rename from ts/webui/src/components/experiment/trialdetail/table/tableFunction/Compare.tsx rename to ts/webui/src/components/experiment/trialdetail/table/tableFunction/CompareIndex.tsx index c99a3e81e..10d5b3740 100644 --- a/ts/webui/src/components/experiment/trialdetail/table/tableFunction/Compare.tsx +++ b/ts/webui/src/components/experiment/trialdetail/table/tableFunction/CompareIndex.tsx @@ -1,11 +1,12 @@ -import * as React from 'react'; +import React, { useState, useEffect } from 'react'; import { renderToString } from 'react-dom/server'; import { Stack, Modal, IconButton, IDragOptions, ContextualMenu, Dropdown, IDropdownOption } from '@fluentui/react'; import ReactEcharts from 'echarts-for-react'; -import { TooltipForIntermediate, TableObj, SingleAxis } from '@static/interface'; -import { contentStyles, iconButtonStyles } from '@components/fluent/ModalTheme'; -import { convertDuration, parseMetrics } from '@static/function'; import { EXPERIMENT, TRIALS } from '@static/datamodel'; +import { Trial } from '@model/trial'; +import { TooltipForIntermediate, SingleAxis } from '@static/interface'; +import { contentStyles, iconButtonStyles } from '@components/fluent/ModalTheme'; +import { convertDuration, parseMetrics, getIntermediateAllKeys } from '@static/function'; import '@style/experiment/trialdetail/compare.scss'; /*** @@ -26,7 +27,7 @@ const dragOptions: IDragOptions = { // TODO: this should be refactored to the common modules // copied from trial.ts -function _parseIntermediates(trial: TableObj, key: string): number[] { +function _parseIntermediates(trial: Trial, key: string): number[] { const intermediates: number[] = []; for (const metric of trial.intermediates) { if (metric === undefined) { @@ -53,30 +54,46 @@ interface Item { } interface CompareProps { - trials: TableObj[]; + trials: Trial[]; title: string; - showDetails: boolean; - intermediateKeyList?: string[]; onHideDialog: () => void; changeSelectTrialIds?: () => void; } -interface CompareState { - intermediateKey: string; // default, dict other keys -} +function CompareIndex(props: CompareProps): any { + const { trials, title } = props; + const atrial = trials.find(item => item.intermediates.length > 0); + const intermediateAllKeysList = getIntermediateAllKeys(atrial === undefined ? trials[0] : atrial); + const [intermediateKey, setIntermediateKey] = useState( + intermediateAllKeysList.length > 0 ? intermediateAllKeysList[0] : 'default' + ); + const runningTrial = trials.find(item => item.status === 'RUNNING'); + const runningTrialIntermediateListLength = runningTrial !== undefined ? runningTrial.intermediates.length : -1; -class Compare extends React.Component { - constructor(props: CompareProps) { - super(props); - - this.state = { - // intermediate result maybe don't have the 'default' key... - intermediateKey: - this.props.intermediateKeyList !== undefined ? this.props.intermediateKeyList[0] : 'default' + function itemsList(): Item[] { + const inferredSearchSpace = TRIALS.inferredSearchSpace(EXPERIMENT.searchSpaceNew); + const flatten = (m: Map): Map => { + return new Map(Array.from(m).map(([key, value]) => [key.baseName, value])); }; + return trials.map(trial => ({ + id: trial.id, + sequenceId: trial.sequenceId, + duration: convertDuration(trial.duration), + parameters: flatten(trial.parameters(inferredSearchSpace)), + metrics: flatten(trial.metrics(TRIALS.inferredMetricSpace())), + intermediates: _parseIntermediates(trial, intermediateKey) + })); } - private _generateTooltipSummary = (row: Item, value: string): string => + const [items, setItems] = useState(itemsList()); + + // react componentDidMount & componentDidUpdate + useEffect(() => { + setItems(itemsList()); + }, [intermediateKey, runningTrialIntermediateListLength]); // update condition + + // page related function + const _generateTooltipSummary = (row: Item, value: string): string => renderToString(
Trial No.: {row.sequenceId}
@@ -85,7 +102,7 @@ class Compare extends React.Component {
); - private _intermediates(items: Item[]): React.ReactNode { + function _intermediates(items: Item[]): React.ReactNode { // Precondition: make sure `items` is not empty const xAxisMax = Math.max(...items.map(item => item.intermediates.length)); const xAxis = Array(xAxisMax) @@ -104,7 +121,7 @@ class Compare extends React.Component { confine: true, formatter: (data: TooltipForIntermediate): string => { const item = items.find(k => k.id === data.seriesName) as Item; - return this._generateTooltipSummary(item, data.data); + return _generateTooltipSummary(item, data.data); } }, grid: { @@ -141,7 +158,7 @@ class Compare extends React.Component { ); } - private _renderRow( + function _renderRow( key: string, rowName: string, className: string, @@ -160,7 +177,7 @@ class Compare extends React.Component { ); } - private _overlapKeys(s: Map[]): string[] { + function _overlapKeys(s: Map[]): string[] { // Calculate the overlapped keys for multiple const intersection: string[] = []; for (const i of s[0].keys()) { @@ -179,7 +196,7 @@ class Compare extends React.Component { } // render table column --- - private _columns(items: Item[]): React.ReactNode { + function _columns(items: Item[]): React.ReactNode { // Precondition: make sure `items` is not empty const width = _getWebUIWidth(); let scrollClass: string = ''; @@ -190,23 +207,21 @@ class Compare extends React.Component { } else { scrollClass = items.length > 2 ? 'flex' : ''; } - const parameterKeys = this._overlapKeys(items.map(item => item.parameters)); - const metricKeys = this._overlapKeys(items.map(item => item.metrics)); + const parameterKeys = _overlapKeys(items.map(item => item.parameters)); + const metricKeys = _overlapKeys(items.map(item => item.metrics)); return ( - {this._renderRow('id', 'ID', 'value idList', items, item => item.id)} - {this._renderRow('trialnum', 'Trial No.', 'value', items, item => item.sequenceId.toString())} - {this._renderRow('duration', 'Duration', 'value', items, item => item.duration)} + {_renderRow('id', 'ID', 'value idList', items, item => item.id)} + {_renderRow('trialnum', 'Trial No.', 'value', items, item => item.sequenceId.toString())} + {_renderRow('duration', 'Duration', 'value', items, item => item.duration)} {parameterKeys.map(k => - this._renderRow(`space_${k}`, k, 'value', items, item => item.parameters.get(k)) + _renderRow(`space_${k}`, k, 'value', items, item => item.parameters.get(k)) )} {metricKeys !== undefined ? metricKeys.map(k => - this._renderRow(`metrics_${k}`, `Metric: ${k}`, 'value', items, item => - item.metrics.get(k) - ) + _renderRow(`metrics_${k}`, `Metric: ${k}`, 'value', items, item => item.metrics.get(k)) ) : null} @@ -214,80 +229,62 @@ class Compare extends React.Component { ); } - private closeCompareModal = (): void => { - const { showDetails, changeSelectTrialIds, onHideDialog } = this.props; - if (showDetails === true) { + const closeCompareModal = (): void => { + const { title, changeSelectTrialIds, onHideDialog } = props; + if (title === 'Compare trials') { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion changeSelectTrialIds!(); } onHideDialog(); }; - private selectOtherKeys = (_event: React.FormEvent, item?: IDropdownOption): void => { + const selectOtherKeys = (_event: React.FormEvent, item?: IDropdownOption): void => { if (item !== undefined) { - this.setState(() => ({ intermediateKey: item.text })); + setIntermediateKey(item.text); } }; - render(): React.ReactNode { - const { trials, title, showDetails, intermediateKeyList } = this.props; - const { intermediateKey } = this.state; - const intermediateAllKeysList: string[] = intermediateKeyList !== undefined ? intermediateKeyList : []; - const flatten = (m: Map): Map => { - return new Map(Array.from(m).map(([key, value]) => [key.baseName, value])); - }; - const inferredSearchSpace = TRIALS.inferredSearchSpace(EXPERIMENT.searchSpaceNew); - const items: Item[] = trials.map(trial => ({ - id: trial.id, - sequenceId: trial.sequenceId, - duration: convertDuration(trial.duration), - parameters: flatten(trial.parameters(inferredSearchSpace)), - metrics: flatten(trial.metrics(TRIALS.inferredMetricSpace())), - intermediates: _parseIntermediates(trial, intermediateKey) - })); - - return ( - -
-
- {title} - -
- {intermediateAllKeysList.length > 1 || - (intermediateAllKeysList.length === 1 && intermediateAllKeysList !== ['default']) ? ( - - ({ - key: key, - text: intermediateAllKeysList[item] - }))} - onChange={this.selectOtherKeys} - /> - - ) : null} - - {this._intermediates(items)} - # Intermediate result - - {showDetails && {this._columns(items)}} + return ( + +
+
+ {title} +
- - ); - } + {intermediateAllKeysList.length > 1 || + (intermediateAllKeysList.length === 1 && intermediateAllKeysList !== ['default']) ? ( + + ({ + key: key, + text: intermediateAllKeysList[item] + }))} + onChange={selectOtherKeys} + /> + + ) : null} + + {_intermediates(items)} + # Intermediate result + + {title === 'Compare trials' && {_columns(items)}} +
+
+ ); } -export default Compare; +export default CompareIndex; diff --git a/ts/webui/src/components/experiment/trialdetail/table/tableFunction/CustomizedTrial.tsx b/ts/webui/src/components/experiment/trialdetail/table/tableFunction/CustomizedTrial.tsx index b0e93c52e..847b01b23 100644 --- a/ts/webui/src/components/experiment/trialdetail/table/tableFunction/CustomizedTrial.tsx +++ b/ts/webui/src/components/experiment/trialdetail/table/tableFunction/CustomizedTrial.tsx @@ -154,7 +154,7 @@ class Customize extends React.Component { componentDidMount(): void { const { copyTrialId } = this.props; if (copyTrialId !== undefined && TRIALS.getTrial(copyTrialId) !== undefined) { - const originCopyTrialPara = TRIALS.getTrial(copyTrialId).description.parameters; + const originCopyTrialPara = TRIALS.getTrial(copyTrialId).parameter; this.setState(() => ({ copyTrialParameter: originCopyTrialPara })); } } @@ -163,7 +163,7 @@ class Customize extends React.Component { if (this.props.copyTrialId !== prevProps.copyTrialId) { const { copyTrialId } = this.props; if (copyTrialId !== undefined && TRIALS.getTrial(copyTrialId) !== undefined) { - const originCopyTrialPara = TRIALS.getTrial(copyTrialId).description.parameters; + const originCopyTrialPara = TRIALS.getTrial(copyTrialId).parameter; this.setState(() => ({ copyTrialParameter: originCopyTrialPara })); } } diff --git a/ts/webui/src/static/const.ts b/ts/webui/src/static/const.ts index 02cb58ea2..9af7ac5d8 100644 --- a/ts/webui/src/static/const.ts +++ b/ts/webui/src/static/const.ts @@ -14,7 +14,7 @@ const METRIC_GROUP_UPDATE_SIZE = 20; const prefix = getPrefix(); const RESTAPI = '/api/v1/nni'; const MANAGER_IP = prefix === undefined ? RESTAPI : `${prefix}${RESTAPI}`; -const DOWNLOAD_IP = `${prefix}/logs`; +const DOWNLOAD_IP = prefix === undefined ? '/logs' : `${prefix}/logs`; const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/experiment/webui.html'; diff --git a/ts/webui/src/static/function.ts b/ts/webui/src/static/function.ts index e0ca4cbbf..38f2c426d 100644 --- a/ts/webui/src/static/function.ts +++ b/ts/webui/src/static/function.ts @@ -3,7 +3,7 @@ import axios from 'axios'; import { IContextualMenuProps } from '@fluentui/react'; import { RETIARIIPARAMETERS } from './const'; import { EXPERIMENT } from './datamodel'; -import { MetricDataRecord, FinalType, TableObj, Tensorboard } from './interface'; +import { MetricDataRecord, FinalType, Tensorboard } from './interface'; function getPrefix(): string | undefined { const pathName = window.location.pathname; @@ -188,15 +188,6 @@ const intermediateGraphOption = (intermediateArr: number[], id: string): any => }; }; -const filterByStatus = (item: TableObj): boolean => { - return item.status === 'SUCCEEDED'; -}; - -// a waittiong trial may havn't start time -const filterDuration = (item: TableObj): boolean => { - return item.status !== 'WAITING'; -}; - const downFile = (content: string, fileName: string): void => { const aTag = document.createElement('a'); const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; @@ -376,8 +367,8 @@ function _inferColumnTitle(columnKey: string): string { const getIntermediateAllKeys = (intermediateDialogTrial: any): string[] => { let intermediateAllKeysList: string[] = []; - if (intermediateDialogTrial!.intermediateMetrics !== undefined && intermediateDialogTrial!.intermediateMetrics[0]) { - const parsedMetric = parseMetrics(intermediateDialogTrial!.intermediateMetrics[0].data); + if (intermediateDialogTrial!.intermediates !== undefined && intermediateDialogTrial!.intermediates[0]) { + const parsedMetric = parseMetrics(intermediateDialogTrial!.intermediates[0].data); if (parsedMetric !== undefined && typeof parsedMetric === 'object') { const allIntermediateKeys: string[] = []; // just add type=number keys @@ -407,8 +398,6 @@ export { getFinal, downFile, intermediateGraphOption, - filterByStatus, - filterDuration, formatAccuracy, formatTimestamp, expformatTimestamp, diff --git a/ts/webui/src/static/interface.ts b/ts/webui/src/static/interface.ts index 114116f4e..a9d9f40e7 100644 --- a/ts/webui/src/static/interface.ts +++ b/ts/webui/src/static/interface.ts @@ -26,23 +26,6 @@ interface MultipleAxes { axes: Map; } -// draw accuracy graph data export interface -interface TableObj { - key: number; - sequenceId: number; - id: string; - duration: number; - status: string; - acc?: FinalType; // draw accuracy graph - description: Parameters; - color?: string; - startTime?: number; - endTime?: number; - intermediates: (MetricDataRecord | undefined)[]; - parameters(axes: MultipleAxes): Map; - metrics(axes: MultipleAxes): Map; -} - interface TableRecord { key: string; sequenceId: number; @@ -53,7 +36,6 @@ interface TableRecord { status: string; message: string; intermediateCount: number; - accuracy?: number | any; latestAccuracy: number | undefined; formattedLatestAccuracy: string; // format (LATEST/FINAL), } @@ -67,17 +49,6 @@ interface FinalType { default: string; } -interface ErrorParameter { - error?: string; -} - -interface Parameters { - parameters: ErrorParameter; - logPath?: string; - intermediate: number[]; - multiProgress?: number; -} - // trial accuracy interface AccurPoint { acc: number; @@ -120,14 +91,6 @@ interface ParaObj { parallelAxis: Array; } -interface Intermedia { - name: string; // id - type: string; - data: Array; // intermediate data - hyperPara: object; // each trial hyperpara value - trialNum: number; -} - interface MetricDataRecord { timestamp: number; trialJobId: string; @@ -223,20 +186,25 @@ interface SearchItems { isChoice: boolean; // for parameters: type = choice and status also as choice type } +interface allTrialsIntermediateChart { + name: string; + // id: string; + sequenceId: number; + data: number[]; + parameter: object; + type: string; +} + export { - TableObj, TableRecord, SearchSpace, FinalType, - ErrorParameter, - Parameters, AccurPoint, DetailAccurPoint, TooltipForIntermediate, TooltipForAccuracy, Dimobj, ParaObj, - Intermedia, MetricDataRecord, TrialJobInfo, ExperimentProfile, @@ -248,5 +216,6 @@ export { SortInfo, AllExperimentList, Tensorboard, - SearchItems + SearchItems, + allTrialsIntermediateChart }; diff --git a/ts/webui/src/static/model/experiment.ts b/ts/webui/src/static/model/experiment.ts index 6ddd92e3a..709e751b5 100644 --- a/ts/webui/src/static/model/experiment.ts +++ b/ts/webui/src/static/model/experiment.ts @@ -51,7 +51,6 @@ class Experiment { private profileField?: ExperimentProfile; private metadataField?: ExperimentMetadata = undefined; private statusField?: NNIManagerStatus = undefined; - private isNestedExperiment: boolean = false; private isexperimentError: boolean = false; private experimentErrorMessage: string = ''; private isStatusError: boolean = false; diff --git a/ts/webui/src/static/model/searchspace.ts b/ts/webui/src/static/model/searchspace.ts index 443b66e7f..9c4b7d834 100644 --- a/ts/webui/src/static/model/searchspace.ts +++ b/ts/webui/src/static/model/searchspace.ts @@ -1,4 +1,5 @@ -import { SingleAxis, MultipleAxes, TableObj } from '../interface'; +import { SingleAxis, MultipleAxes } from '../interface'; +import { Trial } from './trial'; import { SUPPORTED_SEARCH_SPACE_TYPE } from '../const'; import { formatComplexTypeValue } from '../function'; @@ -111,7 +112,7 @@ export class SearchSpace implements MultipleAxes { }); } - static inferFromTrials(searchSpace: SearchSpace, trials: TableObj[]): SearchSpace { + static inferFromTrials(searchSpace: SearchSpace, trials: Trial[]): SearchSpace { const newSearchSpace = new SearchSpace(searchSpace.baseName, searchSpace.fullName, undefined); for (const [k, v] of searchSpace.axes) { newSearchSpace.axes.set(k, v); @@ -153,7 +154,7 @@ export class MetricSpace implements MultipleAxes { baseName = ''; fullName = ''; - constructor(trials: TableObj[]) { + constructor(trials: Trial[]) { const columns = new Map(); for (const trial of trials) { if (trial.acc === undefined) { diff --git a/ts/webui/src/static/model/trial.ts b/ts/webui/src/static/model/trial.ts index 850614b83..0743a1625 100644 --- a/ts/webui/src/static/model/trial.ts +++ b/ts/webui/src/static/model/trial.ts @@ -1,14 +1,4 @@ -import * as JSON5 from 'json5'; -import { - MetricDataRecord, - TrialJobInfo, - TableObj, - TableRecord, - Parameters, - FinalType, - MultipleAxes, - SingleAxis -} from '../interface'; +import { MetricDataRecord, TrialJobInfo, TableRecord, FinalType, MultipleAxes, SingleAxis } from '../interface'; import { getFinal, formatAccuracy, @@ -59,12 +49,11 @@ function inferTrialParameters( return [parameters, unexpectedEntries]; } -class Trial implements TableObj { +class Trial { private metricsInitialized: boolean = false; private infoField: TrialJobInfo | undefined; + public accuracy: number | undefined; // trial default metric val: number value or undefined public intermediates: (MetricDataRecord | undefined)[] = []; - public final: MetricDataRecord | undefined; - private finalAcc: number | undefined; constructor(info?: TrialJobInfo, metrics?: MetricDataRecord[]) { this.infoField = info; @@ -78,7 +67,7 @@ class Trial implements TableObj { return undefined; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.finalAcc! - otherTrial.finalAcc!; + return this.accuracy! - otherTrial.accuracy!; } get info(): TrialJobInfo { @@ -86,25 +75,58 @@ class Trial implements TableObj { return this.infoField!; } - get intermediateMetrics(): MetricDataRecord[] { - const ret: MetricDataRecord[] = []; - for (let i = 0; i < this.intermediates.length; i++) { - if (this.intermediates[i]) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ret.push(this.intermediates[i]!); - } else { - break; - } - } - return ret; + get sequenceId(): number { + return this.info.sequenceId; } - get accuracy(): number | undefined { - return this.finalAcc; + get id(): string { + return this.info.trialJobId; + } + + get duration(): number { + const endTime = this.info.endTime || new Date().getTime(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return (endTime - this.info.startTime!) / 1000; + } + + get status(): string { + return this.info.status; + } + + get parameter(): object { + return JSON.parse(this.info.hyperParameters![0]).parameters; + } + + // return dict final result: {default: xxx...} + get acc(): FinalType | undefined { + if (this.info === undefined) { + return undefined; + } + return getFinal(this.info.finalMetricData); + } + + public parameters(axes: MultipleAxes): Map { + const ret = new Map(Array.from(axes.axes.values()).map(k => [k, null])); + if (this.info === undefined || this.info.hyperParameters === undefined) { + throw ret; + } else { + let params = JSON.parse(this.info.hyperParameters[0]).parameters; + if (typeof params === 'string') { + params = JSON.parse(params); + } + const [updated, unexpectedEntries] = inferTrialParameters(params, axes); + if (unexpectedEntries.size) { + throw unexpectedEntries; + } + for (const [k, v] of updated) { + ret.set(k, v); + } + return ret; + } } get sortable(): boolean { - return this.metricsInitialized && this.finalAcc !== undefined && isFinite(this.finalAcc); + return this.metricsInitialized && this.accuracy !== undefined && isFinite(this.accuracy); } get latestAccuracy(): number | undefined { @@ -131,66 +153,6 @@ class Trial implements TableObj { return undefined; } } - /* table obj start */ - - get tableRecord(): TableRecord { - const endTime = this.info.endTime || new Date().getTime(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const duration = (endTime - this.info.startTime!) / 1000; - let accuracy; - if (this.acc !== undefined && this.acc.default !== undefined) { - if (typeof this.acc.default === 'number') { - accuracy = JSON5.parse(this.acc.default); - } else { - accuracy = this.acc.default; - } - } - - return { - key: this.info.trialJobId, - sequenceId: this.info.sequenceId, - id: this.info.trialJobId, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - startTime: this.info.startTime!, - endTime: this.info.endTime, - duration, - status: this.info.status, - message: this.info.message || '--', - intermediateCount: this.intermediates.length, - accuracy: accuracy, - latestAccuracy: this.latestAccuracy, - formattedLatestAccuracy: this.formatLatestAccuracy() - }; - } - - get key(): number { - return this.info.sequenceId; - } - - get sequenceId(): number { - return this.info.sequenceId; - } - - get id(): string { - return this.info.trialJobId; - } - - get duration(): number { - const endTime = this.info.endTime || new Date().getTime(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return (endTime - this.info.startTime!) / 1000; - } - - get status(): string { - return this.info.status; - } - - get acc(): FinalType | undefined { - if (this.info === undefined) { - return undefined; - } - return getFinal(this.info.finalMetricData); - } get accuracyNumberTypeDictKeys(): string[] { let accuracyTypeList: string[] = []; @@ -208,59 +170,27 @@ class Trial implements TableObj { return accuracyTypeList; } - get description(): Parameters { - const ret: Parameters = { - parameters: {}, - intermediate: [], - multiProgress: 1 + /* table obj start */ + + get tableRecord(): TableRecord { + const endTime = this.info.endTime || new Date().getTime(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const duration = (endTime - this.info.startTime!) / 1000; + + return { + key: this.info.trialJobId, + sequenceId: this.info.sequenceId, + id: this.info.trialJobId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + startTime: this.info.startTime!, + endTime: this.info.endTime, + duration, + status: this.info.status, + message: this.info.message ?? '--', + intermediateCount: this.intermediates.length, + latestAccuracy: this.latestAccuracy, + formattedLatestAccuracy: this.formatLatestAccuracy() }; - const tempHyper = this.info.hyperParameters; - if (tempHyper !== undefined) { - const getPara = JSON.parse(tempHyper[tempHyper.length - 1]).parameters; - ret.multiProgress = tempHyper.length; - if (typeof getPara === 'string') { - ret.parameters = JSON.parse(getPara); - } else { - ret.parameters = getPara; - } - } else { - ret.parameters = { error: "This trial's parameters are not available." }; - } - if (this.info.logPath !== undefined) { - ret.logPath = this.info.logPath; - } - - const mediate: number[] = []; - for (const items of this.intermediateMetrics) { - if (typeof parseMetrics(items.data) === 'object') { - mediate.push(parseMetrics(items.data).default); - } else { - mediate.push(parseMetrics(items.data)); - } - } - ret.intermediate = mediate; - return ret; - } - - public parameters(axes: MultipleAxes): Map { - const ret = new Map(Array.from(axes.axes.values()).map(k => [k, null])); - if (this.info === undefined || this.info.hyperParameters === undefined) { - throw ret; - } else { - const tempHyper = this.info.hyperParameters; - let params = JSON.parse(tempHyper[tempHyper.length - 1]).parameters; - if (typeof params === 'string') { - params = JSON.parse(params); - } - const [updated, unexpectedEntries] = inferTrialParameters(params, axes); - if (unexpectedEntries.size) { - throw unexpectedEntries; - } - for (const [k, v] of updated) { - ret.set(k, v); - } - return ret; - } } public metrics(space: MultipleAxes): Map { @@ -270,8 +200,7 @@ class Trial implements TableObj { if (this.acc === undefined) { return ret; } - const acc = typeof this.acc === 'number' ? { default: this.acc } : this.acc; - Object.entries(acc).forEach(item => { + Object.entries(this.acc).forEach(item => { const [k, v] = item; const column = space.axes.get(k); @@ -287,14 +216,6 @@ class Trial implements TableObj { return ret; } - public finalKeys(): string[] { - if (this.acc !== undefined) { - return Object.keys(this.acc); - } else { - return []; - } - } - /* table obj end */ public initialized(): boolean { @@ -304,7 +225,7 @@ class Trial implements TableObj { public updateMetrics(metrics: MetricDataRecord[]): boolean { // parameter `metrics` must contain all known metrics of this trial this.metricsInitialized = true; - const prevMetricCnt = this.intermediates.length + (this.final ? 1 : 0); + const prevMetricCnt = this.intermediates.length + (this.accuracy ? 1 : 0); if (metrics.length <= prevMetricCnt) { return false; } @@ -312,8 +233,7 @@ class Trial implements TableObj { if (metric.type === 'PERIODICAL') { this.intermediates[metric.sequence] = metric; } else { - this.final = metric; - this.finalAcc = metricAccuracy(metric); + this.accuracy = metricAccuracy(metric); } } return true; @@ -328,9 +248,8 @@ class Trial implements TableObj { updated = updated || !this.intermediates[metric.sequence]; this.intermediates[metric.sequence] = metric; } else { - updated = updated || !this.final; - this.final = metric; - this.finalAcc = metricAccuracy(metric); + updated = updated || !this.accuracy; + this.accuracy = metricAccuracy(metric); } } return updated; @@ -340,13 +259,20 @@ class Trial implements TableObj { const same = this.infoField && this.infoField.status === trialJobInfo.status; this.infoField = trialJobInfo; if (trialJobInfo.finalMetricData) { - this.final = trialJobInfo.finalMetricData[trialJobInfo.finalMetricData.length - 1]; - this.finalAcc = metricAccuracy(this.final); + this.accuracy = metricAccuracy(trialJobInfo.finalMetricData[0]); } return !same; } - private renderNumber(val: any): string { + /** + * + * @param val trial latest accuracy + * @returns 0.9(FINAL) or 0.9(LATEST) + * NaN or Infinity + * string object such as: '{tensor: {data}}' + * + */ + private formatLatestAccuracyToString(val: any): string { if (typeof val === 'number') { if (isNaNorInfinity(val)) { return `${val}`; // show 'NaN' or 'Infinity' @@ -363,19 +289,27 @@ class Trial implements TableObj { } } - public formatLatestAccuracy(): string { - // TODO: this should be private + /** + * + * @param val trial latest accuracy + * @returns 0.9(FINAL) or 0.9(LATEST) + * NaN or Infinity + * string object such as: '{tensor: {data}}' + * +1 describe type undefined: -- + * + */ + private formatLatestAccuracy(): string { if (this.status === 'SUCCEEDED') { - return this.accuracy === undefined ? '--' : this.renderNumber(this.accuracy); + return this.accuracy === undefined ? '--' : this.formatLatestAccuracyToString(this.accuracy); } else { if (this.accuracy !== undefined) { - return this.renderNumber(this.accuracy); + return this.formatLatestAccuracyToString(this.accuracy); } else if (this.intermediates.length === 0) { return '--'; } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const latest = this.intermediates[this.intermediates.length - 1]!; - return this.renderNumber(metricAccuracy(latest)); + return this.formatLatestAccuracyToString(metricAccuracy(latest)); } } } diff --git a/ts/webui/src/static/model/trialmanager.ts b/ts/webui/src/static/model/trialmanager.ts index f27147388..e4ab882b2 100644 --- a/ts/webui/src/static/model/trialmanager.ts +++ b/ts/webui/src/static/model/trialmanager.ts @@ -2,7 +2,8 @@ import { MANAGER_IP, METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE } f import { MetricDataRecord, TableRecord, TrialJobInfo, MultipleAxes } from '../interface'; import { Trial } from './trial'; import { SearchSpace, MetricSpace } from './searchspace'; -import { requestAxios } from '../function'; +import { requestAxios, parseMetrics } from '../function'; +import { allTrialsIntermediateChart } from '../interface'; function groupMetricsByTrial(metrics: MetricDataRecord[]): Map { const ret = new Map(); @@ -86,10 +87,37 @@ class TrialManager { return this.filter(trial => trial.status === 'SUCCEEDED'); } + public notWaittingTrials(): Trial[] { + return this.filter(trial => trial.status !== 'WAITING'); + } + + public allTrialsIntermediateChart(): allTrialsIntermediateChart[] { + const ret: allTrialsIntermediateChart[] = []; + for (const trial of this.trials.values()) { + const mediate: number[] = []; + for (const items of trial.intermediates) { + if (typeof parseMetrics(items!.data) === 'object') { + mediate.push(parseMetrics(items!.data).default); + } else { + mediate.push(parseMetrics(items!.data)); + } + } + ret.push({ + name: trial.id, + sequenceId: trial.sequenceId, + data: mediate, + parameter: trial.parameter, + type: 'line' + }); + } + + return ret; + } + public finalKeys(): string[] { const succeedTrialsList = this.filter(trial => trial.status === 'SUCCEEDED'); if (succeedTrialsList !== undefined && succeedTrialsList[0] !== undefined) { - return succeedTrialsList[0].finalKeys(); + return succeedTrialsList[0].acc !== undefined ? Object.keys(succeedTrialsList[0].acc) : []; } else { return ['default']; }