diff --git a/src/src/components/MediaList/ImageBox.tsx b/src/src/components/MediaList/ImageBox.tsx index 88e35689..bd8353ef 100644 --- a/src/src/components/MediaList/ImageBox.tsx +++ b/src/src/components/MediaList/ImageBox.tsx @@ -41,19 +41,24 @@ const ImageBox = ({ let subscription: any; if (blobData === null) { - if (blobsURIModel.getState()[blob_uri]) { - setBlobData(blobsURIModel.getState()[blob_uri]); + // Get the formatted URI containing the run hash and experiment id + const formattedUri = data.run.props.experiment.artifact_location + ? `${data.run.props.experiment.artifact_location}/${data.run.hash}/artifacts/${blob_uri}` + : `artifacts/${data.run.props.experiment.id}/${data.run.hash}/artifacts/${blob_uri}`; + + if (blobsURIModel.getState()[formattedUri]) { + setBlobData(blobsURIModel.getState()[formattedUri]); } else { - subscription = blobsURIModel.subscribe(blob_uri, (data) => { - setBlobData(data[blob_uri]); + subscription = blobsURIModel.subscribe(formattedUri, (data) => { + setBlobData(data[formattedUri]); subscription.unsubscribe(); }); timeoutID = window.setTimeout(() => { - if (blobsURIModel.getState()[blob_uri]) { - setBlobData(blobsURIModel.getState()[blob_uri]); + if (blobsURIModel.getState()[formattedUri]) { + setBlobData(blobsURIModel.getState()[formattedUri]); subscription.unsubscribe(); } else { - addUriToList(blob_uri); + addUriToList(formattedUri); } }, BATCH_COLLECT_DELAY); } @@ -67,7 +72,14 @@ const ImageBox = ({ subscription.unsubscribe(); } }; - }, [addUriToList, blobData, blob_uri]); + }, [ + addUriToList, + blobData, + blob_uri, + data.run.hash, + data.run.props.experiment.id, + data.run.props.experiment.artifact_location, + ]); function onImageFullSizeModeButtonClick(e: React.ChangeEvent): void { e.stopPropagation(); diff --git a/src/src/config/table/tableConfigs.ts b/src/src/config/table/tableConfigs.ts index fd3d9ac3..0ee5c36c 100644 --- a/src/src/config/table/tableConfigs.ts +++ b/src/src/config/table/tableConfigs.ts @@ -111,6 +111,23 @@ export const TABLE_DEFAULT_CONFIG: Record = { }, height: '0.5', }, + [AppNameEnum.IMAGES]: { + resizeMode: ResizeModeEnum.Resizable, + rowHeight: RowHeightSize.md, + sortFields: [], + unselectedColumnState: UnselectedColumnState.FORCE_HIDE, + hiddenMetrics: [], + hiddenColumns: ['hash', 'description'], + nonHidableColumns: new Set(['#', 'run', 'actions']), + hideSystemMetrics: true, + columnsWidths: {}, + columnsOrder: { + left: ['run'], + middle: [], + right: [], + }, + height: '0.5', + }, }; export const AVOID_COLUMNS_TO_HIDE_LIST = new Set([ diff --git a/src/src/pages/ImagesExplore/ImagesExplore.tsx b/src/src/pages/ImagesExplore/ImagesExplore.tsx index ed1b87ae..9d42ef77 100644 --- a/src/src/pages/ImagesExplore/ImagesExplore.tsx +++ b/src/src/pages/ImagesExplore/ImagesExplore.tsx @@ -216,6 +216,14 @@ function ImagesExplore(): React.FunctionComponentElement { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + React.useEffect(() => { + imagesExploreAppModel.fetchProjectParamsAndUpdateState(); + const pollingTimer = setInterval(() => { + imagesExploreAppModel.fetchProjectParamsAndUpdateState(); + }, 30000); + return () => clearInterval(pollingTimer); + }, []); + return (
@@ -227,6 +235,12 @@ function ImagesExplore(): React.FunctionComponentElement { onBookmarkUpdate={imagesExploreAppModel.onBookmarkUpdate} onResetConfigData={imagesExploreAppModel.onResetConfigData} title={pageTitlesEnum.IMAGES_EXPLORER} + onSelectExperimentsChange={ + imagesExploreAppModel.onSelectExperimentsChange + } + onToggleAllExperiments={ + imagesExploreAppModel.onToggleAllExperiments + } />
{ const [popover, setPopover] = React.useState(''); + const [selectedExperiments, setSelectedExperiments] = React.useState< + IExperimentDataShort[] + >(getSelectedExperiments()); + const route = useRouteMatch(); + const { current: experimentsEngine } = React.useRef(createExperimentEngine); + + const experimentsStore: IResourceState = + experimentsEngine.experimentsState((state) => state); + + React.useEffect(() => { + experimentsEngine.fetchExperiments(); + return () => { + experimentsEngine.destroy(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Fetch all experiments along with default + const { + experimentState, + experimentsState, + selectedExperiments: filteredSelectedExperiments, + getExperimentsData, + } = useExperimentState(experimentsStore.data?.[0]?.id); + + // Remove selected experiments that are not in the list of fetched experiments + React.useEffect(() => { + if (filteredSelectedExperiments.length !== selectedExperiments.length) { + setSelectedExperiments(filteredSelectedExperiments); + } + }, [filteredSelectedExperiments, selectedExperiments]); + + const { data: experimentData, loading: isExperimentLoading } = + experimentState; + + const { data: experimentsData, loading: isExperimentsLoading } = + experimentsState; + function handleBookmarkClick(value: string): void { setPopover(value); } @@ -37,9 +89,23 @@ function ImagesExploreAppBar({ handleClosePopover(); } + function handleExperimentsChange(experiment: IExperimentDataShort): void { + onSelectExperimentsChange(experiment); + setSelectedExperiments(getSelectedExperiments()); + } + return ( + {route.params.appId ? ( { - return API.getStream(endpoints.GET_IMAGES, params); + return API.getStream>( + endpoints.GET_IMAGES, + params, + { + method: 'POST', + }, + ); } function getImagesByURIs(body: string[]): IApiRequest { diff --git a/src/src/services/models/explorer/createAppModel.ts b/src/src/services/models/explorer/createAppModel.ts index 258447f6..10010edb 100644 --- a/src/src/services/models/explorer/createAppModel.ts +++ b/src/src/services/models/explorer/createAppModel.ts @@ -1085,7 +1085,7 @@ function createAppModel(appConfig: IAppInitialConfig) { x_axis_iters, } = filterMetricsData( trace, - configData?.chart?.alignmentConfigs[0].type, + configData?.chart?.alignmentConfigs?.[0]?.type, configData?.chart?.axesScaleType, ); @@ -1469,7 +1469,7 @@ function createAppModel(appConfig: IAppInitialConfig) { function alignData( data: IMetricsCollection[], type: AlignmentOptionsEnum = model.getState()!.config!.chart - ?.alignmentConfigs[0].type, + ?.alignmentConfigs?.[0]?.type, chartId: number = 0, ): IMetricsCollection[] { const alignmentObj: { [key: string]: Function } = { @@ -1478,14 +1478,15 @@ function createAppModel(appConfig: IAppInitialConfig) { [AlignmentOptionsEnum.RELATIVE_TIME]: alignByRelativeTime, [AlignmentOptionsEnum.ABSOLUTE_TIME]: alignByAbsoluteTime, [AlignmentOptionsEnum.CUSTOM_METRIC]: alignByCustomMetric, - default: () => { - throw new Error('Unknown value for X axis alignment'); - }, + // default: () => { + // throw new Error('Unknown value for X axis alignment'); + // }, + default: alignByStep, }; const alignmentConfig = model.getState()!.config!.chart?.alignmentConfigs[chartId]; const alignment = - alignmentObj[alignmentConfig.type] || alignmentObj.default; + alignmentObj[alignmentConfig?.type] || alignmentObj.default; return alignment(data, model); } diff --git a/src/src/services/models/imagesExplore/imagesExploreAppModel.ts b/src/src/services/models/imagesExplore/imagesExploreAppModel.ts index e4a8b3eb..e087c666 100644 --- a/src/src/services/models/imagesExplore/imagesExploreAppModel.ts +++ b/src/src/services/models/imagesExplore/imagesExploreAppModel.ts @@ -16,6 +16,8 @@ import { DATE_EXPORTING_FORMAT, TABLE_DATE_FORMAT } from 'config/dates/dates'; import { getSuggestionsByExplorer } from 'config/monacoConfig/monacoConfig'; import { GroupNameEnum } from 'config/grouping/GroupingPopovers'; +import { IExperimentDataShort } from 'modules/core/api/experimentsApi'; + import { getImagesExploreTableColumns, imagesExploreTableRowRenderer, @@ -95,6 +97,10 @@ import getFilteredRow from 'utils/app/getFilteredRow'; import { getMetricHash } from 'utils/app/getMetricHash'; import onRunsTagsChange from 'utils/app/onRunsTagsChange'; import saveRecentSearches from 'utils/saveRecentSearches'; +import onSelectExperimentsChange from 'utils/app/onSelectExperimentsChange'; +import onToggleAllExperiments from 'utils/app/onToggleAllExperiments'; +import { getSelectedExperiments } from 'utils/app/getSelectedExperiments'; +import { removeOldSelectedMetrics } from 'utils/app/removeOldSelectedMetrics'; import createModel from '../model'; import { AppNameEnum } from '../explorer'; @@ -199,29 +205,7 @@ function initialize(appId: string): void { if (!appId) { setDefaultAppConfigData(); } - projectsService - .getProjectParams(['images']) - .call() - .then((data: IProjectParamsMetrics) => { - const advancedSuggestions: Record = getAdvancedSuggestion( - data.images, - ); - model.setState({ - selectFormData: { - options: getSelectFormOptions(data), - suggestions: getSuggestionsByExplorer(AppNameEnum.IMAGES, data), - advancedSuggestions: { - ...getSuggestionsByExplorer(AppNameEnum.IMAGES, data), - images: { - name: '', - context: _.isEmpty(advancedSuggestions) - ? '' - : { ...advancedSuggestions }, - }, - }, - }, - }); - }); + fetchProjectParamsAndUpdateState(); } function setDefaultAppConfigData(recoverTableState: boolean = true) { @@ -461,6 +445,37 @@ function getImagesData( }; } +function fetchProjectParamsAndUpdateState() { + const selectedExperiments = getSelectedExperiments(); + projectsService + .getProjectParams( + ['images'], + selectedExperiments.map((exp) => exp.id), + ) + .call() + .then((data: IProjectParamsMetrics) => { + const advancedSuggestions: Record = getAdvancedSuggestion( + data.images, + ); + model.setState({ + selectFormData: { + options: getSelectFormOptions(data), + suggestions: getSuggestionsByExplorer(AppNameEnum.IMAGES, data), + advancedSuggestions: { + ...getSuggestionsByExplorer(AppNameEnum.IMAGES, data), + images: { + name: '', + context: _.isEmpty(advancedSuggestions) + ? '' + : { ...advancedSuggestions }, + }, + }, + }, + }); + removeOldSelectedMetrics(model); + }); +} + function getSelectFormOptions(projectsData: IProjectParamsMetrics) { let data: ISelectOption[] = []; let index: number = 0; @@ -2321,6 +2336,16 @@ function onStackingToggle(): void { } } +function onModelSelectExperimentsChange(experiment: IExperimentDataShort) { + onSelectExperimentsChange(experiment); + getImagesData(false, true).call(); +} + +function onModelToggleAllExperiments(experiments: IExperimentDataShort[]) { + onToggleAllExperiments(experiments); + getImagesData(false, true).call(); +} + const imagesExploreAppModel = { ...model, initialize, @@ -2375,6 +2400,9 @@ const imagesExploreAppModel = { archiveRuns, onRowSelect, onRunsTagsChange: onModelRunsTagsChange, + onSelectExperimentsChange: onModelSelectExperimentsChange, + onToggleAllExperiments: onModelToggleAllExperiments, + fetchProjectParamsAndUpdateState, }; export default imagesExploreAppModel; diff --git a/src/src/types/services/models/imagesExplore/imagesExploreAppModel.d.ts b/src/src/types/services/models/imagesExplore/imagesExploreAppModel.d.ts index 633401a0..b76baa49 100644 --- a/src/src/types/services/models/imagesExplore/imagesExploreAppModel.d.ts +++ b/src/src/types/services/models/imagesExplore/imagesExploreAppModel.d.ts @@ -132,7 +132,7 @@ export interface IImageRunData { archived: 0 | 1; creation_time: number; end_time: number; - experiment: string; + experiment: { [key: string]: unknown }; name: string; tags: any[]; };