From 5176424f88aec219182ffb6c02e604c134f57caa Mon Sep 17 00:00:00 2001 From: hsoni92 Date: Tue, 16 Dec 2025 17:31:00 +0530 Subject: [PATCH 1/9] 2.21.2 changes migrated to 3.7.0 --- mlflow-changes.patch | 11915 ++++++++++++++++ mlflow/server/js/.npmrc | 3 + .../trace_embedding.html | 105 + .../trace_viewer_full.html | 10172 +++++++++++++ .../js/src/common/utils/FeatureUtils.ts | 4 + .../server/js/src/common/utils/FileUtils.ts | 12 +- .../js/src/common/utils/RoutingUtils.tsx | 2 +- mlflow/server/js/src/common/utils/Utils.tsx | 3 +- .../components/ProjectListView.tsx | 96 + .../ShowArtifactPage.tsx | 6 +- .../ShowArtifactTraceView.css | 10 + .../ShowArtifactTraceView.tsx | 130 + .../models/ExperimentPageUIState.tsx | 2 +- .../components/run-page/GrafanaIframe.tsx | 23 + .../components/run-page/RunPage.tsx | 3 + 15 files changed, 22480 insertions(+), 6 deletions(-) create mode 100644 mlflow-changes.patch create mode 100644 mlflow/server/js/.npmrc create mode 100644 mlflow/server/js/public/lib/artifact-trace-viewer/trace_embedding.html create mode 100644 mlflow/server/js/public/lib/artifact-trace-viewer/trace_viewer_full.html create mode 100644 mlflow/server/js/src/experiment-tracking/components/ProjectListView.tsx create mode 100644 mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.css create mode 100644 mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.tsx create mode 100644 mlflow/server/js/src/experiment-tracking/components/run-page/GrafanaIframe.tsx diff --git a/mlflow-changes.patch b/mlflow-changes.patch new file mode 100644 index 000000000..374fc01c8 --- /dev/null +++ b/mlflow-changes.patch @@ -0,0 +1,11915 @@ +diff --git a/mlflow/server/js/.npmrc b/mlflow/server/js/.npmrc +new file mode 100644 +index 000000000..411a14d89 +--- /dev/null ++++ b/mlflow/server/js/.npmrc +@@ -0,0 +1,2 @@ ++registry=https://registry.npmjs.com/ ++email=@ +\ No newline at end of file +diff --git a/mlflow/server/js/public/lib/artifact-trace-viewer/trace_embedding.html b/mlflow/server/js/public/lib/artifact-trace-viewer/trace_embedding.html +new file mode 100644 +index 000000000..120a6380e +--- /dev/null ++++ b/mlflow/server/js/public/lib/artifact-trace-viewer/trace_embedding.html +@@ -0,0 +1,104 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/mlflow/server/js/public/lib/artifact-trace-viewer/trace_viewer_full.html b/mlflow/server/js/public/lib/artifact-trace-viewer/trace_viewer_full.html +new file mode 100644 +index 000000000..15169a457 +--- /dev/null ++++ b/mlflow/server/js/public/lib/artifact-trace-viewer/trace_viewer_full.html +@@ -0,0 +1,10174 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx b/mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx +index f35852e73..edd7b151b 100644 +--- a/mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx ++++ b/mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx +@@ -60,7 +60,7 @@ export class CollapsibleTagsCell extends React.Component { + const tooltipContent = value === '' ? tagName : `${tagName}: ${value}`; + return ( +
+- ++ + + {value === '' ? ( + {tagName} +diff --git a/mlflow/server/js/src/common/components/SearchTree.tsx b/mlflow/server/js/src/common/components/SearchTree.tsx +index 610313d48..67b0c0581 100644 +--- a/mlflow/server/js/src/common/components/SearchTree.tsx ++++ b/mlflow/server/js/src/common/components/SearchTree.tsx +@@ -143,6 +143,7 @@ export class SearchTreeImpl extends React.Component + +- +diff --git a/mlflow/server/js/src/common/utils/FeatureUtils.ts b/mlflow/server/js/src/common/utils/FeatureUtils.ts +index 9ff520519..575add9f2 100644 +--- a/mlflow/server/js/src/common/utils/FeatureUtils.ts ++++ b/mlflow/server/js/src/common/utils/FeatureUtils.ts +@@ -40,6 +40,8 @@ export const shouldEnableChartExpressions = () => false; + */ + export const shouldEnableToggleIndividualRunsInGroups = () => false; + ++export const shouldEnableDeepLearningUI = () => false; ++ + /** + * Update relative time axis to use date + */ +@@ -63,6 +65,8 @@ export const isUnstableNestedComponentsMigrated = () => true; + */ + export const isExperimentLoggedModelsUIEnabled = () => false; + ++export const shouldEnableExperimentDatasetTracking = () => false; ++ + /** + * Determines if evaluation results online monitoring UI is enabled + */ +diff --git a/mlflow/server/js/src/common/utils/FileUtils.ts b/mlflow/server/js/src/common/utils/FileUtils.ts +index c30d8ebdd..bbdc1cdc3 100644 +--- a/mlflow/server/js/src/common/utils/FileUtils.ts ++++ b/mlflow/server/js/src/common/utils/FileUtils.ts +@@ -11,8 +11,15 @@ export const getBasename = (path: any) => { + }; + + export const getExtension = (path: any) => { +- const parts = path.split(/[./]/); +- return parts[parts.length - 1]; ++ const tracefileRegex = /.*\.(pt\.trace\.json(?:\.gz|\.zip)?)$/; ++ const traceMatch = path.match(tracefileRegex); ++ ++ if (traceMatch) { ++ return traceMatch[1]; ++ } else { ++ const parts = path.split(/[./]/); ++ return parts[parts.length - 1]; ++ } + }; + + export const getLanguage = (path: any) => { +@@ -62,6 +69,7 @@ export const HTML_EXTENSIONS = new Set(['html']); + export const MAP_EXTENSIONS = new Set(['geojson']); + export const PDF_EXTENSIONS = new Set(['pdf']); + export const DATA_EXTENSIONS = new Set(['csv', 'tsv']); ++export const TRACE_EXTENSIONS = new Set(['pt.trace.json', 'pt.trace.json.gz']); + // Audio extensions supported by wavesurfer.js + // Source https://github.com/katspaugh/wavesurfer.js/discussions/2703#discussioncomment-5259526 + export const AUDIO_EXTENSIONS = new Set(['m4a', 'mp3', 'mp4', 'wav', 'aac', 'wma', 'flac', 'opus', 'ogg']); +diff --git a/mlflow/server/js/src/common/utils/RoutingUtils.tsx b/mlflow/server/js/src/common/utils/RoutingUtils.tsx +index 78294eab2..f379f7be7 100644 +--- a/mlflow/server/js/src/common/utils/RoutingUtils.tsx ++++ b/mlflow/server/js/src/common/utils/RoutingUtils.tsx +@@ -32,7 +32,6 @@ import { + /** + * Import React Router V5 parts + */ +-import { HashRouter as HashRouterV5, Link as LinkV5, NavLink as NavLinkV5 } from 'react-router-dom'; + import React, { ComponentProps } from 'react'; + + const useLocation = useLocationDirect; +@@ -55,6 +54,7 @@ export { + MemoryRouter, + HashRouter, + Link, ++ NavLink, + useNavigate, + useLocation, + useParams, +diff --git a/mlflow/server/js/src/common/utils/Utils.tsx b/mlflow/server/js/src/common/utils/Utils.tsx +index 0682fa328..2c82f5597 100644 +--- a/mlflow/server/js/src/common/utils/Utils.tsx ++++ b/mlflow/server/js/src/common/utils/Utils.tsx +@@ -802,7 +802,8 @@ class Utils { + // @ts-expect-error TS(2345): Argument of type 'string | string[] | ParsedQs | P... Remove this comment to see the full error message + const lineSmoothness = params['line_smoothness'] ? parseFloat(params['line_smoothness']) : 0; + // @ts-expect-error TS(2345): Argument of type 'string | string[] | ParsedQs | P... Remove this comment to see the full error message +- const layout = params['plot_layout'] ? JSON.parse(params['plot_layout']) : { autosize: true }; ++ const layoutStr = params['plot_layout'] ? params['plot_layout'].replaceAll(" ","") : undefined ++ const layout = layoutStr ? JSON.parse(layoutStr) : { autosize: true }; + // Default to displaying all runs, i.e. to deselectedCurves being empty + const deselectedCurves = params['deselected_curves'] + ? // @ts-expect-error TS(2345): Argument of type 'string | string[] | ParsedQs | P... Remove this comment to see the full error message +diff --git a/mlflow/server/js/src/experiment-tracking/components/App.tsx b/mlflow/server/js/src/experiment-tracking/components/App.tsx +index eb743c4f3..c11d7c9dd 100644 +--- a/mlflow/server/js/src/experiment-tracking/components/App.tsx ++++ b/mlflow/server/js/src/experiment-tracking/components/App.tsx +@@ -9,12 +9,11 @@ import React, { Component } from 'react'; + import { connect } from 'react-redux'; + + import { +- HashRouterV5, ++ HashRouter as HashRouterV5, + Route, + Routes, +- CompatRouter, +- LinkV5, +- NavLinkV5, ++ Link as LinkV5, ++ NavLink as NavLinkV5, + } from '../../common/utils/RoutingUtils'; + + import AppErrorBoundary from '../../common/components/error-boundaries/AppErrorBoundary'; +@@ -29,7 +28,7 @@ import { ModelRegistryRoutes, ModelRegistryRoutePaths } from '../../model-regist + import ExperimentTrackingRoutes, { RoutePaths as ExperimentTrackingRoutePaths } from '../routes'; + import './App.css'; + import CompareRunPage from './CompareRunPage'; +-import { HomePage } from './HomePage'; ++import HomePage from './HomePage'; + import { MetricPage } from './MetricPage'; + import { PageNotFoundView } from '../../common/components/PageNotFoundView'; + import { RunPage } from './RunPage'; +@@ -60,125 +59,123 @@ class App extends Component { + return ( + + {/* This layer enables intercompatibility between react-router APIs v5 and v6 */} + {/* TODO: Remove after migrating to react-router v6 */} +- +-
+- +- {/* @ts-expect-error TS(4111): Property 'HIDE_HEADER' comes from an index signatu... Remove this comment to see the full error message */} +- {process.env.HIDE_HEADER === 'true' ? null : ( +-
+-
+- +- MLflow +- +- {Version} +-
+-
+- +-
+- Experiments +-
+-
+- +-
+- Models +-
+-
+-
+- +-
+- )} +- +- +- {/* The block below contains React Router v6 routes */} +- ++
++ ++ {/* @ts-expect-error TS(4111): Property 'HIDE_HEADER' comes from an index signatu... Remove this comment to see the full error message */} ++ {process.env.HIDE_HEADER === 'true' ? null : ( ++
++
++ ++ MLflow ++ ++ {Version} ++
++
++ ++
++ Experiments ++
++
++ ++
++ Models ++
++
++
++ ++
++ )} ++ ++ ++ {/* The block below contains React Router v6 routes */} ++ ++ } ++ /> ++ } ++ /> ++ } ++ /> ++ } /> ++ {/* If deep learning UI features are enabled, use more ++ versatile route (with backward compatibility) */} ++ {shouldEnableDeepLearningUI() ? ( + } ++ path={ExperimentTrackingRoutePaths.runPageWithTab} ++ element={} + /> +- } +- /> +- } +- /> +- } /> +- {/* If deep learning UI features are enabled, use more +- versatile route (with backward compatibility) */} +- {shouldEnableDeepLearningUI() ? ( ++ ) : ( ++ <> + } + /> +- ) : ( +- <> +- } +- /> +- } /> +- +- )} +- } +- /> +- } /> +- } +- /> +- } /> +- } +- /> +- } /> +- } /> +- } +- /> +- } +- /> +- } /> +- +- {/* End of React Router v6 routes */} +- +- +-
+- ++ } /> ++ ++ )} ++ } ++ /> ++ } /> ++ } ++ /> ++ } /> ++ } ++ /> ++ } /> ++ } /> ++ } ++ /> ++ } ++ /> ++ } /> ++
++ {/* End of React Router v6 routes */} ++
++
++
+
+ ); + } +diff --git a/mlflow/server/js/src/experiment-tracking/components/ArtifactView.css b/mlflow/server/js/src/experiment-tracking/components/ArtifactView.css +index 5604863c9..f552536bc 100644 +--- a/mlflow/server/js/src/experiment-tracking/components/ArtifactView.css ++++ b/mlflow/server/js/src/experiment-tracking/components/ArtifactView.css +@@ -1,6 +1,9 @@ + div.artifact-view { ++ height: 100%; ++ width: 100%; + display: flex; + overflow: hidden; ++ position: relative; + } + + .artifact-left { +diff --git a/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx b/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx +index 51be7f69e..1fd477ac2 100644 +--- a/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx ++++ b/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx +@@ -22,6 +22,7 @@ import { DeleteExperimentModal } from './modals/DeleteExperimentModal'; + import { RenameExperimentModal } from './modals/RenameExperimentModal'; + import { withRouterNext, WithRouterNextProps } from '../../common/utils/withRouterNext'; + import { ExperimentEntity } from '../types'; ++import ProjectListView, { filterExperimentsByProject } from './ProjectListView'; + + type Props = { + activeExperimentIds: string[]; +@@ -41,12 +42,13 @@ type State = { + }; + + export class ExperimentListView extends Component { +- list?: VList = undefined; +- ++ list: any; ++ selectedExperiments: string[] = JSON.parse(localStorage.getItem('selected-experiments') || '[]'); + state = { +- checkedKeys: this.props.activeExperimentIds, ++ checkedKeys: JSON.parse(localStorage.getItem('selected-experiments') || '[]'), + hidden: false, + searchInput: '', ++ project: localStorage.getItem('mlflow-exp-project') || 'All', + showCreateExperimentModal: false, + showDeleteExperimentModal: false, + showRenameExperimentModal: false, +@@ -58,19 +60,23 @@ export class ExperimentListView extends Component { + this.list = ref; + }; + +- componentDidUpdate = () => { ++ componentDidUpdate = (prevProps: Props) => { + // Ensure the filter is applied + if (this.list) { + this.list.forceUpdateGrid(); + } +- }; ++ const exps = JSON.parse(localStorage.getItem('selected-experiments') || '[]'); ++ if (prevProps.activeExperimentIds.length !== exps.length) { ++ this.pushExperimentRoute(); ++ } ++ } + +- filterExperiments = (searchInput: string) => { +- const { experiments } = this.props; ++ filterExperiments = (searchInput: any) => { ++ const experiments = filterExperimentsByProject(this.props.experiments, this.state.project) + const lowerCasedSearchInput = searchInput.toLowerCase(); + return lowerCasedSearchInput === '' +- ? this.props.experiments +- : experiments.filter(({ name }) => name.toLowerCase().includes(lowerCasedSearchInput)); ++ ? experiments ++ : experiments.filter(({ name }: any) => name.toLowerCase().includes(lowerCasedSearchInput)); + }; + + handleSearchInputChange: React.ChangeEventHandler = (event) => { +@@ -79,7 +85,15 @@ export class ExperimentListView extends Component { + }); + }; + +- updateSelectedExperiment = (experimentId: string, experimentName: string) => { ++ handleProjectChange = (value: any) => { ++ const experiments = filterExperimentsByProject(this.props.experiments, value) ++ localStorage.setItem('mlflow-exp-project', value); ++ this.setState((prevState: any, props: any) => { ++ return {project: value, checkedKeys: experiments.length ?[experiments[0].experiment_id] : [] }; ++ }, this.pushExperimentRoute); ++ }; ++ ++ updateSelectedExperiment = (experimentId: any, experimentName: any) => { + this.setState({ + selectedExperimentId: experimentId, + selectedExperimentName: experimentName, +@@ -128,6 +142,10 @@ export class ExperimentListView extends Component { + this.updateSelectedExperiment('0', ''); + }; + ++ persistSelectedExperiemnts = (selected: any) => { ++ localStorage.setItem('selected-experiments', JSON.stringify(selected)); ++ } ++ + // Add a key if it does not exist, remove it if it does + // Always keep at least one experiment checked if it is only the active one. + handleCheck = (isChecked: boolean, key: string) => { +@@ -144,6 +162,7 @@ export class ExperimentListView extends Component { + }; + + pushExperimentRoute = () => { ++ this.persistSelectedExperiemnts(this.state.checkedKeys); + if (this.state.checkedKeys.length > 0) { + const route = + this.state.checkedKeys.length === 1 +@@ -285,6 +304,8 @@ export class ExperimentListView extends Component { + experimentId={this.state.selectedExperimentId} + experimentName={this.state.selectedExperimentName} + /> ++
++ +
{ + +
+
++
+ { +- const sorted = [...experiments].sort(Utils.compareExperiments); +- return sorted.find(({ lifecycleStage }) => lifecycleStage === 'active'); ++ const selectedProject = localStorage.getItem('mlflow-exp-project'); ++ let filteredExperiments = experiments ++ if(selectedProject){ ++ filteredExperiments = filterExperimentsByProject(experiments, selectedProject); ++ } ++ const sorted = [...filteredExperiments].sort(Utils.compareExperiments); ++ return sorted.find(({ lifecycleStage }) => lifecycleStage === 'active' ); + }; + + const HomePage = () => { +diff --git a/mlflow/server/js/src/experiment-tracking/components/MetricsPlotPanel.tsx b/mlflow/server/js/src/experiment-tracking/components/MetricsPlotPanel.tsx +index 54bfe5cf6..6d000f73d 100644 +--- a/mlflow/server/js/src/experiment-tracking/components/MetricsPlotPanel.tsx ++++ b/mlflow/server/js/src/experiment-tracking/components/MetricsPlotPanel.tsx +@@ -32,7 +32,7 @@ export const METRICS_PLOT_POLLING_INTERVAL_MS = 10 * 1000; // 10 seconds + // A run is considered as 'hanging' if its status is 'RUNNING' but its latest metric was logged + // prior to this threshold. The metrics plot doesn't automatically update hanging runs. + export const METRICS_PLOT_HANGING_RUN_THRESHOLD_MS = 3600 * 24 * 7 * 1000; // 1 week +-const MAXIMUM_METRIC_DATA_POINTS = 100_000; ++const MAXIMUM_METRIC_DATA_POINTS = 1_000_000; + const GET_METRIC_HISTORY_MAX_RESULTS = 25000; + + export const convertMetricsToCsv = (metrics: any) => { +diff --git a/mlflow/server/js/src/experiment-tracking/components/ProjectListView.tsx b/mlflow/server/js/src/experiment-tracking/components/ProjectListView.tsx +new file mode 100644 +index 000000000..5830b5e6d +--- /dev/null ++++ b/mlflow/server/js/src/experiment-tracking/components/ProjectListView.tsx +@@ -0,0 +1,96 @@ ++import 'react-virtualized/styles.css'; ++ ++import { ++ DialogCombobox, ++ DialogComboboxContent, ++ DialogComboboxOptionList, ++ DialogComboboxOptionListSelectItem, ++ DialogComboboxTrigger, ++ Typography, ++} from '@databricks/design-system'; ++import React, { Component } from 'react'; ++ ++import { ExperimentEntity } from '../types'; ++import { css } from '@emotion/react'; ++ ++type Props = { ++ experiments: ExperimentEntity[]; ++ project: string; ++ handleProjectChange: any; ++}; ++ ++type State = any; ++ ++export const filterExperimentsByProject = (experiments:any, selectedProject:any) =>{ ++ if (selectedProject === "All") { ++ return experiments; ++ } else if (selectedProject === "Default") { ++ return experiments.filter( ++ (experiment:any) => !experiment.tags || !experiment.tags.some((tag:any) => tag.key.toLowerCase() === "project") ++ ); ++ } else { ++ return experiments.filter( ++ (experiment:any) => experiment.tags && experiment.tags.some((tag:any) => tag.key.toLowerCase() === "project" && tag.value === selectedProject) ++ ); ++ } ++} ++export class ProjectListView extends Component { ++ ++ listProjects = () => { ++ const { experiments } = this.props; ++ const projects = experiments ++ .filter(experiment => { ++ const projectTag = experiment.tags && experiment.tags.find((tag :any) => tag.key.toLowerCase() === "project"); ++ return projectTag !== undefined; ++ }) ++ .map(experiment => { ++ const projectTag = experiment.tags.find((tag:any) => tag.key.toLowerCase() === "project"); ++ return projectTag ? projectTag.value : null; ++ }); ++ return ['All','Default', ...new Set(projects)]; ++ }; ++ ++ render() { ++ const projects = this.listProjects(); ++ return ( ++
++ ++ Projects ++ ++ ++ ++ ++ ++ {projects.map((project:any) => ( ++ this.props.handleProjectChange(project)} ++ > ++ {project} ++ ++ ))} ++ ++ ++ ++
++ ); ++ } ++} ++ ++const classNames = { ++ projectsContainer: { ++ marginBottom: '8px', ++ width:'100%' ++ }, ++}; ++ ++export default ProjectListView; +diff --git a/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactPage.tsx b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactPage.tsx +index ff26e64a3..9b9ccb4a2 100644 +--- a/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactPage.tsx ++++ b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactPage.tsx +@@ -14,6 +14,7 @@ import { + HTML_EXTENSIONS, + PDF_EXTENSIONS, + DATA_EXTENSIONS, ++ TRACE_EXTENSIONS, + AUDIO_EXTENSIONS, + } from '../../../common/utils/FileUtils'; + import { getLoggedModelPathsFromTags, getLoggedTablesFromTags } from '../../../common/utils/TagUtils'; +@@ -22,6 +23,7 @@ import ShowArtifactImageView from './ShowArtifactImageView'; + import ShowArtifactTextView from './ShowArtifactTextView'; + import { LazyShowArtifactMapView } from './LazyShowArtifactMapView'; + import ShowArtifactHtmlView from './ShowArtifactHtmlView'; ++import ShowArtifactTraceView from './ShowArtifactTraceView'; + import { LazyShowArtifactPdfView } from './LazyShowArtifactPdfView'; + import { LazyShowArtifactTableView } from './LazyShowArtifactTableView'; + import ShowArtifactLoggedModelView from './ShowArtifactLoggedModelView'; +@@ -90,7 +92,9 @@ class ShowArtifactPage extends Component { + } else if (this.props.showArtifactLoggedTableView) { + return ; + } else if (normalizedExtension) { +- if (IMAGE_EXTENSIONS.has(normalizedExtension.toLowerCase())) { ++ if (TRACE_EXTENSIONS.has(normalizedExtension.toLowerCase())) { ++ return ; ++ } else if (IMAGE_EXTENSIONS.has(normalizedExtension.toLowerCase())) { + return ; + } else if (DATA_EXTENSIONS.has(normalizedExtension.toLowerCase())) { + return ; +diff --git a/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.css b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.css +new file mode 100644 +index 000000000..de635ffa4 +--- /dev/null ++++ b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.css +@@ -0,0 +1,9 @@ ++.trace-iframe { ++ border: none; ++} ++ ++.artifact-trace-view { ++ width: 100%; ++ height: 100%; ++ overflow: auto; ++} +diff --git a/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.tsx b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.tsx +new file mode 100644 +index 000000000..9fa1956d5 +--- /dev/null ++++ b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.tsx +@@ -0,0 +1,129 @@ ++/** ++ * NOTE: this code file was automatically migrated to TypeScript using ts-migrate and ++ * may contain multiple `any` type annotations and `@ts-expect-error` directives. ++ * If possible, please improve types while making changes to this file. If the type ++ * annotations are already looking good, please remove this comment. ++ */ ++ ++import pako from 'pako'; ++import React, { Component } from 'react'; ++import { getArtifactContent, getArtifactLocationUrl } from '../../../common/utils/ArtifactUtils'; ++import './ShowArtifactTraceView.css'; ++import { ArtifactViewSkeleton } from './ArtifactViewSkeleton'; ++ ++type ShowArtifactTraceViewState = { ++ loading: boolean; ++ error?: any; ++ path: string; ++ tracedata: string; ++}; ++ ++type ShowArtifactTraceViewProps = { ++ runUuid: string; ++ path: string; ++ getArtifact: (artifactLocation: string, isBinary: boolean) => Promise; ++}; ++ ++class ShowArtifactTraceView extends Component { ++ iframeRef: any; ++ constructor(props: ShowArtifactTraceViewProps) { ++ super(props); ++ this.fetchArtifacts = this.fetchArtifacts.bind(this); ++ this.traceViewDataHandler = this.traceViewDataHandler.bind(this) ++ this.iframeRef = React.createRef(); ++ } ++ ++ static defaultProps = { ++ getArtifact: getArtifactContent, ++ }; ++ ++ state = { ++ loading: true, ++ error: undefined, ++ path: '', ++ tracedata: '', ++ }; ++ ++ componentDidMount() { ++ this.fetchArtifacts(); ++ window.addEventListener('message', this.traceViewDataHandler, true); ++ } ++ ++ componentDidUpdate(prevProps: ShowArtifactTraceViewProps) { ++ if (this.props.path !== prevProps.path || this.props.runUuid !== prevProps.runUuid) { ++ this.fetchArtifacts(); ++ window.addEventListener('message', this.traceViewDataHandler, true); ++ } ++ } ++ ++ componentWillUnmount() { ++ // Avoid registering `traceViewDataHandler` every time this component mounts ++ window.removeEventListener('message', this.traceViewDataHandler, true); ++ } ++ ++ render() { ++ if (this.state.loading || this.state.path !== this.props.path) { ++ return ; ++ } ++ if (this.state.error) { ++ console.error('Unable to load Trace artifact, got error ' + this.state.error); ++ return
Oops we couldn't load your file because of an error.
; ++ } else { ++ return ( ++
++ ++ ++
++ ); ++ } ++ } ++ ++ /** Fetches artifacts and updates component state with the result */ ++ fetchArtifacts() { ++ const artifactLocation = getArtifactLocationUrl(this.props.path, this.props.runUuid); ++ this.props ++ .getArtifact(artifactLocation, true) ++ .then((tracebindata: ArrayBufferLike) => { ++ const uint8Array = new Uint8Array(tracebindata); ++ let data = ''; ++ // Gzip files start with the magic number 0x1f 0x8b ++ if (uint8Array[0] === 0x1f && uint8Array[1] === 0x8b) { ++ try { ++ data = pako.ungzip(uint8Array, { to: 'string' }); ++ } catch (error) { ++ console.error('Decompression error:', error); ++ } ++ } else { ++ data = new TextDecoder().decode(uint8Array); ++ } ++ this.setState({ tracedata: data, loading: false, path: this.props.path }); ++ }) ++ .catch((error: Error) => { ++ this.setState({ error: error, loading: false, path: this.props.path }); ++ }); ++ } ++ ++ traceViewDataHandler(event: MessageEvent) { ++ const data = event.data || {} ++ if (data.msg === 'ready') { ++ if (this.iframeRef.current && this.iframeRef.current.contentWindow) { ++ this.iframeRef.current.focus(); ++ this.iframeRef.current.contentWindow.postMessage( ++ { msg: 'data', data: this.state.tracedata }, ++ '*' ++ ); ++ } ++ } ++ } ++} ++ ++export default ShowArtifactTraceView; +diff --git a/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/PromptLabOnboarding.tsx b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/PromptLabOnboarding.tsx +index e6b01c8d6..e8b381ac2 100644 +--- a/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/PromptLabOnboarding.tsx ++++ b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/PromptLabOnboarding.tsx +@@ -35,6 +35,7 @@ export const PromptLabOnboarding = ({ onDismissed }: { onDismissed?: () => void + + return ( + void + description='"Learn more" button in the modal for the prompt engineering onboarding' + /> + */} +- */} ++- */} +++- */} ++- */} -+-