From 7d687516884af42a90bdb6429deba95940eb0db3 Mon Sep 17 00:00:00 2001 From: hsoni92 Date: Wed, 4 Jun 2025 12:26:31 +0530 Subject: [PATCH 01/16] meld-changes --- mlflow/server/js/.npmrc | 2 + mlflow/server/js/package.json | 1 + .../trace_embedding.html | 104 + .../trace_viewer_full.html | 10174 ++++++++++++++++ .../server/js/src/common/utils/FetchUtils.ts | 4 + .../server/js/src/common/utils/FileUtils.ts | 12 +- mlflow/server/js/src/common/utils/Utils.tsx | 3 +- .../components/ArtifactView.css | 3 + .../components/ExperimentListView.tsx | 38 +- .../components/HomePage.tsx | 10 +- .../components/MetricsPlotPanel.tsx | 2 +- .../components/ProjectListView.tsx | 96 + .../ShowArtifactPage.tsx | 7 +- .../ShowArtifactTraceView.css | 9 + .../ShowArtifactTraceView.tsx | 129 + .../run-page/RunViewMetricChart.tsx | 5 + .../run-page/RunViewMetricCharts.tsx | 14 +- .../run-page/overview/RunViewMetricsTable.tsx | 5 +- .../hooks/useSampledMetricHistory.tsx | 2 +- .../sdk/SampledMetricHistoryService.ts | 2 +- .../experiment-tracking/utils/MetricsUtils.ts | 3 + 21 files changed, 10602 insertions(+), 23 deletions(-) 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 diff --git a/mlflow/server/js/.npmrc b/mlflow/server/js/.npmrc new file mode 100644 index 0000000000..411a14d899 --- /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/package.json b/mlflow/server/js/package.json index 9f743a70e5..b57dfbce04 100644 --- a/mlflow/server/js/package.json +++ b/mlflow/server/js/package.json @@ -1,6 +1,7 @@ { "name": "@mlflow/mlflow", "version": "0.1.0", + "proxy": "http://192.168.200.105:32443", "scripts": { "start": "craco start", "build": "craco --max_old_space_size=8192 build", 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 0000000000..120a6380e2 --- /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 0000000000..15169a4572 --- /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/utils/FetchUtils.ts b/mlflow/server/js/src/common/utils/FetchUtils.ts index d0b09493d7..a1e292c013 100644 --- a/mlflow/server/js/src/common/utils/FetchUtils.ts +++ b/mlflow/server/js/src/common/utils/FetchUtils.ts @@ -52,11 +52,15 @@ export const getDefaultHeaders = (cookieStr: any) => { }; export const getAjaxUrl = (relativeUrl: any) => { +<<<<<<< Updated upstream // @ts-expect-error TS(4111): Property 'MLFLOW_USE_ABSOLUTE_AJAX_URLS' comes from an in... Remove this comment to see the full error message if (process.env.MLFLOW_USE_ABSOLUTE_AJAX_URLS === 'true' && !relativeUrl.startsWith('/')) { return '/' + relativeUrl; } return relativeUrl; +======= + return '/mlflow/' + relativeUrl; +>>>>>>> Stashed changes }; // return response json by default, if response is not parsable to json, diff --git a/mlflow/server/js/src/common/utils/FileUtils.ts b/mlflow/server/js/src/common/utils/FileUtils.ts index c30d8ebdda..bbdc1cdc39 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/Utils.tsx b/mlflow/server/js/src/common/utils/Utils.tsx index 0682fa328a..2c82f55970 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/ArtifactView.css b/mlflow/server/js/src/experiment-tracking/components/ArtifactView.css index 5604863c9d..f552536bcd 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 51be7f69ee..276be434f5 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, @@ -144,6 +158,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 +300,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 54bfe5cf69..6d000f73d0 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 0000000000..5830b5e6d8 --- /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 ff26e64a34..ecb410e8d4 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,7 +14,7 @@ import { HTML_EXTENSIONS, PDF_EXTENSIONS, DATA_EXTENSIONS, - AUDIO_EXTENSIONS, + TRACE_EXTENSIONS, } from '../../../common/utils/FileUtils'; import { getLoggedModelPathsFromTags, getLoggedTablesFromTags } from '../../../common/utils/TagUtils'; import { ONE_MB } from '../../constants'; @@ -22,6 +22,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 +91,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 0000000000..de635ffa42 --- /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 0000000000..9fa1956d5b --- /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/run-page/RunViewMetricChart.tsx b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx index fe61519927..6b11d73833 100644 --- a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx @@ -12,6 +12,8 @@ export interface RunViewMetricChartProps { runInfo: RunInfoEntity; metricsHistory: MetricHistoryByName; isLoading: boolean; + maxResults: number; + showPoint: boolean; } /** @@ -102,6 +104,8 @@ export const RunViewMetricChart = ({ runInfo, metricsHistory, isLoading, + maxResults, + showPoint }: RunViewMetricChartProps) => { const loaded = metricsHistory?.[metricKey] && !isLoading; const isSingleMetricEntry = loaded && metricsHistory?.[metricKey].length === 1; @@ -148,6 +152,7 @@ export const RunViewMetricChart = ({ metricKey={metricKey} metricsHistory={metricsHistory} runInfo={runInfo} + showPoint={showPoint} /> ); }; diff --git a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx index f5688eed3a..69ec0aeb74 100644 --- a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx @@ -1,4 +1,16 @@ -import { TableSkeleton, ToggleButton, useDesignSystemTheme } from '@databricks/design-system'; +import { + TableSkeleton, + ToggleButton, + useDesignSystemTheme, + DialogCombobox, + DialogComboboxContent, + DialogComboboxOptionList, + DialogComboboxOptionListCheckboxItem, + DialogComboboxOptionListSelectItem, + DialogComboboxOptionListSearch, + DialogComboboxTrigger, + Switch, +} from '@databricks/design-system'; import { compact, mapValues, values } from 'lodash'; import { ReactNode, useEffect, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; diff --git a/mlflow/server/js/src/experiment-tracking/components/run-page/overview/RunViewMetricsTable.tsx b/mlflow/server/js/src/experiment-tracking/components/run-page/overview/RunViewMetricsTable.tsx index c034e398f5..087e4685a6 100644 --- a/mlflow/server/js/src/experiment-tracking/components/run-page/overview/RunViewMetricsTable.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/run-page/overview/RunViewMetricsTable.tsx @@ -15,7 +15,7 @@ import { useMemo, useState } from 'react'; import { Link } from '../../../../common/utils/RoutingUtils'; import Routes from '../../../routes'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; -import { isSystemMetricKey } from '../../../utils/MetricsUtils'; +import { isSystemMetricKey, normalizeChartMetricKey } from '../../../utils/MetricsUtils'; import { Table as TableDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import type { UseGetRunQueryResponseRunInfo } from '../hooks/useGetRunQuery'; @@ -33,7 +33,8 @@ const { systemMetricsLabel, modelMetricsLabel } = defineMessages({ const metricKeyMatchesFilter = (filter: string) => ({ key }: MetricEntity) => - key.toLowerCase().includes(filter.toLowerCase()); + key.toLowerCase().includes(filter.toLowerCase()) || + normalizeChartMetricKey(key).toLowerCase().includes(filter.toLowerCase()); const RunViewMetricsTableSection = ({ metricsList, diff --git a/mlflow/server/js/src/experiment-tracking/components/runs-charts/hooks/useSampledMetricHistory.tsx b/mlflow/server/js/src/experiment-tracking/components/runs-charts/hooks/useSampledMetricHistory.tsx index 0b23cd9f56..d433a9c57d 100644 --- a/mlflow/server/js/src/experiment-tracking/components/runs-charts/hooks/useSampledMetricHistory.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/runs-charts/hooks/useSampledMetricHistory.tsx @@ -84,7 +84,7 @@ const useSampledMetricHistoryREST = (params: { const refreshFn = useCallback(() => { metricKeys.forEach((metricKey) => { chunk(runUuids, SAMPLED_METRIC_HISTORY_API_RUN_LIMIT).forEach((runUuidsChunk) => { - const action = getSampledMetricHistoryBulkAction(runUuidsChunk, metricKey, maxResults, range, 'all'); + const action = getSampledMetricHistoryBulkAction(runUuidsChunk, metricKey, maxResults, range, 'auto'); dispatch(action); }); }); diff --git a/mlflow/server/js/src/experiment-tracking/sdk/SampledMetricHistoryService.ts b/mlflow/server/js/src/experiment-tracking/sdk/SampledMetricHistoryService.ts index 88dd9f2711..6f846f0455 100644 --- a/mlflow/server/js/src/experiment-tracking/sdk/SampledMetricHistoryService.ts +++ b/mlflow/server/js/src/experiment-tracking/sdk/SampledMetricHistoryService.ts @@ -64,7 +64,7 @@ export const getSampledMetricHistoryBulkAction = // - were not initialized before // - have fresh data // - are being loaded already - return !isInitialized || isFresh || isLoadingOrRefreshing; + return false; } // If refresh mode is unset, skip runs that we already have data for diff --git a/mlflow/server/js/src/experiment-tracking/utils/MetricsUtils.ts b/mlflow/server/js/src/experiment-tracking/utils/MetricsUtils.ts index b2deeadf8c..91f412ff32 100644 --- a/mlflow/server/js/src/experiment-tracking/utils/MetricsUtils.ts +++ b/mlflow/server/js/src/experiment-tracking/utils/MetricsUtils.ts @@ -67,6 +67,8 @@ export const normalizeMetricsHistoryEntry = ({ key, timestamp, value, step }: Me timestamp: timestamp, }); + + /** * Compares two numbers, being able to return "true" if * two NaN values are given. @@ -352,6 +354,7 @@ export const truncateChartMetricString = (fullStr: string, strLen: number) => { }; const systemMetricPrefix = new RegExp(`^${MLFLOW_SYSTEM_METRIC_PREFIX}`); +export const normalizeChartMetricKey = (metricKey: string) => metricKey.replace(systemMetricPrefix, ''); export const isSystemMetricKey = (metricKey: string) => metricKey.match(systemMetricPrefix); From dcc8f23d7151520cdab435d158810e2778c55946 Mon Sep 17 00:00:00 2001 From: hsoni92 Date: Wed, 4 Jun 2025 15:06:26 +0530 Subject: [PATCH 02/16] reverting-incorrect-changes --- mlflow/server/js/package.json | 1 - mlflow/server/js/src/common/utils/FetchUtils.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/mlflow/server/js/package.json b/mlflow/server/js/package.json index b57dfbce04..9f743a70e5 100644 --- a/mlflow/server/js/package.json +++ b/mlflow/server/js/package.json @@ -1,7 +1,6 @@ { "name": "@mlflow/mlflow", "version": "0.1.0", - "proxy": "http://192.168.200.105:32443", "scripts": { "start": "craco start", "build": "craco --max_old_space_size=8192 build", diff --git a/mlflow/server/js/src/common/utils/FetchUtils.ts b/mlflow/server/js/src/common/utils/FetchUtils.ts index a1e292c013..d0b09493d7 100644 --- a/mlflow/server/js/src/common/utils/FetchUtils.ts +++ b/mlflow/server/js/src/common/utils/FetchUtils.ts @@ -52,15 +52,11 @@ export const getDefaultHeaders = (cookieStr: any) => { }; export const getAjaxUrl = (relativeUrl: any) => { -<<<<<<< Updated upstream // @ts-expect-error TS(4111): Property 'MLFLOW_USE_ABSOLUTE_AJAX_URLS' comes from an in... Remove this comment to see the full error message if (process.env.MLFLOW_USE_ABSOLUTE_AJAX_URLS === 'true' && !relativeUrl.startsWith('/')) { return '/' + relativeUrl; } return relativeUrl; -======= - return '/mlflow/' + relativeUrl; ->>>>>>> Stashed changes }; // return response json by default, if response is not parsable to json, From 76e22cd622b204e0d76d1d4cf355fb272879d51b Mon Sep 17 00:00:00 2001 From: hsoni92 Date: Wed, 4 Jun 2025 16:48:20 +0530 Subject: [PATCH 03/16] working-state --- mlflow/server/js/src/common/utils/FeatureUtils.ts | 2 ++ .../src/experiment-tracking/components/ExperimentListView.tsx | 4 ++++ mlflow/server/js/yarn.lock | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mlflow/server/js/src/common/utils/FeatureUtils.ts b/mlflow/server/js/src/common/utils/FeatureUtils.ts index 9ff5205195..ee310d351c 100644 --- a/mlflow/server/js/src/common/utils/FeatureUtils.ts +++ b/mlflow/server/js/src/common/utils/FeatureUtils.ts @@ -63,6 +63,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/experiment-tracking/components/ExperimentListView.tsx b/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx index 276be434f5..1fd477ac21 100644 --- a/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx @@ -142,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) => { diff --git a/mlflow/server/js/yarn.lock b/mlflow/server/js/yarn.lock index 21d2f6f919..f2ddb26796 100644 --- a/mlflow/server/js/yarn.lock +++ b/mlflow/server/js/yarn.lock @@ -2557,7 +2557,7 @@ __metadata: "@databricks/design-system@file:./vendor/design-system::locator=%40mlflow%2Fmlflow%40workspace%3A.": version: 1.0.0-b5d111bf - resolution: "@databricks/design-system@file:./vendor/design-system#./vendor/design-system::hash=25c74b&locator=%40mlflow%2Fmlflow%40workspace%3A." + resolution: "@databricks/design-system@file:./vendor/design-system#./vendor/design-system::hash=4a7192&locator=%40mlflow%2Fmlflow%40workspace%3A." dependencies: "@ant-design/icons": "npm:^4.7.0" "@babel/runtime": "npm:^7.26.0" @@ -2599,7 +2599,7 @@ __metadata: moment: ^2.25.3 react: "*" react-dom: "*" - checksum: 10c0/970821a05fb0cd3800df49cdd1da165885d378ad6257ede06dcfc3893f2d15d3f8f1a4cdcea35349bcc3575f5e4a8537199b3a2e81505f5fa9cef23a069a7f40 + checksum: 10c0/f57b43a5acfc1f7b3c392898289dcd8fec9ff3e007323c91c574d6821c3394c0d6c1af1dceaa09d1f2d1f97e39a8213de543bf8d641e5f51c7d48441338bc096 languageName: node linkType: hard From 925a3049a66b59a90c9e96c61d500860cfd7f889 Mon Sep 17 00:00:00 2001 From: hsoni92 Date: Thu, 5 Jun 2025 19:35:21 +0530 Subject: [PATCH 04/16] final-changes --- .../components/run-page/RunViewMetricChart.tsx | 5 ++++- .../components/run-page/RunViewMetricCharts.tsx | 11 +++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx index 6b11d73833..8629523d8c 100644 --- a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx @@ -143,6 +143,8 @@ export const RunViewMetricChart = ({ ); @@ -152,7 +154,8 @@ export const RunViewMetricChart = ({ metricKey={metricKey} metricsHistory={metricsHistory} runInfo={runInfo} - showPoint={showPoint} + showPoint={showPoint} + maxResults={maxResults} /> ); }; diff --git a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx index 69ec0aeb74..8b8cc4740a 100644 --- a/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx @@ -2,14 +2,6 @@ import { TableSkeleton, ToggleButton, useDesignSystemTheme, - DialogCombobox, - DialogComboboxContent, - DialogComboboxOptionList, - DialogComboboxOptionListCheckboxItem, - DialogComboboxOptionListSelectItem, - DialogComboboxOptionListSearch, - DialogComboboxTrigger, - Switch, } from '@databricks/design-system'; import { compact, mapValues, values } from 'lodash'; import { ReactNode, useEffect, useMemo, useState } from 'react'; @@ -82,6 +74,9 @@ const RunViewMetricChartsImpl = ({ }) => { const { theme } = useDesignSystemTheme(); const [search, setSearch] = useState(''); + const prevSample = localStorage.getItem('mlflow-run-chart-default-samples') || '320' + const [maxSteps, setMaxSteps] = useState(parseInt(prevSample)); + const [showPoint, setShowPoint] = useState(false); const { formatMessage } = useIntl(); const { compareRunCharts, compareRunSections, chartsSearchFilter } = chartUIState; From ec8588a5a2bd0eaf6dbb1031166e179f63c813dd Mon Sep 17 00:00:00 2001 From: "himanshu.soni" Date: Wed, 18 Jun 2025 15:41:20 +0530 Subject: [PATCH 05/16] ts-disabled --- .../common/components/CollapsibleTagsCell.tsx | 2 +- .../js/src/common/components/SearchTree.tsx | 1 + .../src/common/components/StyledDropdown.tsx | 2 +- .../js/src/common/utils/FeatureUtils.ts | 2 + .../js/src/common/utils/RoutingUtils.tsx | 2 +- .../experiment-tracking/components/App.tsx | 229 +++++++++--------- .../ShowArtifactPage.tsx | 1 + .../components/PromptLabOnboarding.tsx | 3 +- .../runs/ExperimentViewRunsSortSelector.tsx | 2 +- .../runs/ExperimentViewRunsTable.tsx | 5 +- .../runs/ExperimentViewRunsTableCollapse.tsx | 3 +- .../utils/experimentPage.fetch-utils.ts | 2 + .../js/src/experiment-tracking/types.ts | 2 + mlflow/server/js/tsconfig.json | 4 +- 14 files changed, 133 insertions(+), 127 deletions(-) diff --git a/mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx b/mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx index f35852e73a..edd7b151b7 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 610313d483..67b0c05810 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 ee310d351c..575add9f2c 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 */ diff --git a/mlflow/server/js/src/common/utils/RoutingUtils.tsx b/mlflow/server/js/src/common/utils/RoutingUtils.tsx index 78294eab29..f379f7be73 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/experiment-tracking/components/App.tsx b/mlflow/server/js/src/experiment-tracking/components/App.tsx index eb743c4f3d..c11d7c9dd6 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/artifact-view-components/ShowArtifactPage.tsx b/mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactPage.tsx index ecb410e8d4..9b9ccb4a25 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 @@ -15,6 +15,7 @@ import { PDF_EXTENSIONS, DATA_EXTENSIONS, TRACE_EXTENSIONS, + AUDIO_EXTENSIONS, } from '../../../common/utils/FileUtils'; import { getLoggedModelPathsFromTags, getLoggedTablesFromTags } from '../../../common/utils/TagUtils'; import { ONE_MB } from '../../constants'; 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 e6b01c8d69..e8b381ac2b 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' /> */} -
++ ); ++ } ++ } ++ ++ /** 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' + /> + */} +-