diff --git a/DKUBEX-CHANGES.md b/DKUBEX-CHANGES.md new file mode 100644 index 0000000000..daa01c3e6f --- /dev/null +++ b/DKUBEX-CHANGES.md @@ -0,0 +1,247 @@ +# DKUBEX Changes Summary + +## Table of Contents + +### New Features +- [1. PyTorch Trace Viewer](#1-pytorch-trace-viewer) +- [2. Grafana Logs Integration](#2-grafana-logs-integration) +- [3. Project-Based Experiment Filtering](#3-project-based-experiment-filtering) +- [4. Enhanced Metric Charts](#4-enhanced-metric-charts) + +### Feature Flags & Configuration +- [Disabled Features](#disabled-features) + +### UI/UX Improvements +- [Component IDs for Testing/Accessibility](#component-ids-for-testingaccessibility) +- [Performance & Limits](#performance--limits) +- [Metric Filtering Enhancement](#metric-filtering-enhancement) + +### Routing & Navigation +- [React Router Updates](#react-router-updates) + +### File Handling +- [Trace File Support](#trace-file-support) +- [Artifact View Styling](#artifact-view-styling) + +### Bug Fixes & Improvements +- [Plot Layout Parsing](#plot-layout-parsing) +- [Preview Pane Toggle](#preview-pane-toggle) +- [Experiment List State Management](#experiment-list-state-management) +- [Metric History Sampling](#metric-history-sampling) +- [Run View Metric Charts UI Enhancements](#run-view-metric-charts-ui-enhancements) + +### Build & Configuration +- [NPM Configuration](#npm-configuration) +- [TypeScript Configuration](#typescript-configuration) +- [Design System Update](#design-system-update) +- [Setup Script Removal](#setup-script-removal) +- [Type Updates](#type-updates) + +### Docker Image Build +- [Image Build](#image-build) +--- + +## New Features + +### 1. PyTorch Trace Viewer +- **Files**: + - `mlflow/server/js/public/lib/artifact-trace-viewer/trace_embedding.html` (new) + - `mlflow/server/js/public/lib/artifact-trace-viewer/trace_viewer_full.html` (new) + - `mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.tsx` (new) + - `mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactTraceView.css` (new) +- **Description**: Added support for viewing PyTorch trace files (`.pt.trace.json`, `.pt.trace.json.gz`) as artifacts with an embedded trace viewer interface. + +### 2. Grafana Logs Integration +- **Files**: + - `mlflow/server/js/src/experiment-tracking/components/run-page/GrafanaIframe.tsx` (new) + - `mlflow/server/js/src/experiment-tracking/components/run-page/RunPage.tsx` + - `mlflow/server/js/src/experiment-tracking/components/run-page/RunViewModeSwitch.tsx` + - `mlflow/server/js/src/experiment-tracking/components/run-page/useRunViewActiveTab.tsx` + - `mlflow/server/js/src/experiment-tracking/constants.ts` +- **Description**: Added a new "Logs" tab in the run page that embeds Grafana dashboards for viewing run logs. Includes tab selector integration. + +### 3. Project-Based Experiment Filtering +- **Files**: + - `mlflow/server/js/src/experiment-tracking/components/ProjectListView.tsx` (new) + - `mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx` + - `mlflow/server/js/src/experiment-tracking/components/HomePage.tsx` +- **Description**: Added project filtering functionality allowing users to filter experiments by project tags (All, Default, or specific project names). Includes localStorage persistence for selected experiments and project preferences. + +### 4. Enhanced Metric Charts +- **Files**: + - `mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricChart.tsx` + - `mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx` + - `mlflow/server/js/src/experiment-tracking/components/runs-charts/components/cards/RunsChartsLineChartCard.tsx` + - `mlflow/server/js/src/experiment-tracking/components/runs-charts/components/hooks/useLineChartGlobalConfig.tsx` + - `mlflow/server/js/src/experiment-tracking/components/experiment-page/models/ExperimentPageUIState.tsx` +- **Description**: Added `maxResults` and `showPoint` configuration options for metric charts, allowing customizable data point limits and point display. Includes UI controls (samples dropdown and points toggle switch) with localStorage persistence for user preferences. + +## Feature Flags & Configuration + +### Disabled Features +- **Files**: + - `mlflow/server/js/src/common/utils/FeatureUtils.ts` + - `mlflow/server/js/src/experiment-tracking/components/experiment-page/utils/experimentPage.fetch-utils.ts` +- **Changes**: + - `shouldEnableDeepLearningUI()` returns `false` + - `shouldEnableExperimentDatasetTracking()` returns `false` + - `shouldRefetchRuns()` returns `true` (added in `experimentPage.fetch-utils.ts`) + +## UI/UX Improvements + +### Component IDs for Testing/Accessibility +- **Files**: + - `mlflow/server/js/src/common/components/CollapsibleTagsCell.tsx` + - `mlflow/server/js/src/common/components/SearchTree.tsx` + - `mlflow/server/js/src/common/components/StyledDropdown.tsx` + - `mlflow/server/js/src/experiment-tracking/components/experiment-page/components/PromptLabOnboarding.tsx` + - `mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsSortSelector.tsx` + - `mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTableCollapse.tsx` + - `mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx` + - `mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx` +- **Description**: Added `componentId` props to various UI components for improved testability and accessibility. + +### Performance & Limits +- **Files**: `mlflow/server/js/src/experiment-tracking/components/MetricsPlotPanel.tsx` +- **Changes**: Increased `MAXIMUM_METRIC_DATA_POINTS` from 100,000 to 1,000,000 + +### Metric Filtering Enhancement +- **Files**: + - `mlflow/server/js/src/experiment-tracking/components/run-page/overview/RunViewMetricsTable.tsx` + - `mlflow/server/js/src/experiment-tracking/utils/MetricsUtils.ts` +- **Description**: Enhanced metric filtering to support normalized metric keys (stripping system metric prefixes) for better search functionality. Added `normalizeChartMetricKey()` utility function. + +## Routing & Navigation + +### React Router Updates +- **Files**: + - `mlflow/server/js/src/common/utils/RoutingUtils.tsx` + - `mlflow/server/js/src/experiment-tracking/components/App.tsx` +- **Changes**: + - Removed `CompatRouter` wrapper + - Added `NavLink` export + - Updated imports to use direct `HashRouter`, `Link`, and `NavLink` from routing utils + - Commented out `hashType` prop (React Router v6 compatibility) + - Changed `HomePage` import from named to default export + - Commented out NavLink props (`strict`, `activeStyle`, `isActive`) for React Router v6 compatibility + - Restructured route definitions (moved Routes outside CompatRouter) + +## File Handling + +### Trace File Support +- **Files**: + - `mlflow/server/js/src/common/utils/FileUtils.ts` + - `mlflow/server/js/src/experiment-tracking/components/artifact-view-components/ShowArtifactPage.tsx` +- **Changes**: + - Added `TRACE_EXTENSIONS` constant for `.pt.trace.json` and `.pt.trace.json.gz` files + - Enhanced `getExtension()` function to properly detect trace file extensions with compression suffixes + +### Artifact View Styling +- **Files**: `mlflow/server/js/src/experiment-tracking/components/ArtifactView.css` +- **Changes**: Added `height: 100%`, `width: 100%`, and `position: relative` to `.artifact-view` for proper layout. + +## Bug Fixes & Improvements + +### Plot Layout Parsing +- **Files**: `mlflow/server/js/src/common/utils/Utils.tsx` +- **Description**: Fixed JSON parsing of plot layout by removing whitespace before parsing to prevent parsing errors. + +### Preview Pane Toggle +- **Files**: `mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTableCollapse.tsx` +- **Changes**: Fixed toggle behavior to use `previewPaneVisible` instead of `runListHidden` in state update. Added componentId for testing. + +### Experiment List State Management +- **Files**: `mlflow/server/js/src/experiment-tracking/components/ExperimentListView.tsx` +- **Changes**: + - Added localStorage persistence for selected experiments (`selected-experiments`) + - Changed `list` property type from `VList | undefined` to `any` + - Added project state initialization from localStorage (`mlflow-exp-project`) + - Updated `componentDidUpdate` to sync with localStorage changes and trigger route updates + - Added `persistSelectedExperiemnts` method for state persistence + - Integrated `ProjectListView` component into experiment list UI + +### Metric History Sampling +- **Files**: `mlflow/server/js/src/experiment-tracking/components/runs-charts/hooks/useSampledMetricHistory.tsx` +- **Changes**: Changed sampling mode from `'all'` to `'auto'` for better performance. + +### Run View Metric Charts UI Enhancements +- **Files**: `mlflow/server/js/src/experiment-tracking/components/run-page/RunViewMetricCharts.tsx` +- **Changes**: + - Added UI controls for samples selection (dropdown with options: 320, 500, 1000, 2500) + - Added toggle switch for showing/hiding data points + - Added localStorage persistence for default samples preference (`mlflow-run-chart-default-samples`) + - Added `useEffect` hook to sync `maxSteps` and `showPoint` state with global line chart config + - Added additional imports: `Empty`, `Input`, `SearchIcon`, `Spacer`, `DialogCombobox` components, `Switch`, `FormattedMessage` + +## Build & Configuration + +### NPM Configuration +- **Files**: `mlflow/server/js/.npmrc` (new) +- **Description**: Added NPM registry configuration file. + +### TypeScript Configuration +- **Files**: `mlflow/server/js/tsconfig.json` +- **Changes**: Removed `include` array (empty array), likely for build optimization. + +### Design System Update +- **Files**: `mlflow/server/js/yarn.lock` +- **Description**: Updated `@databricks/design-system` dependency hash. + +### Setup Script Removal +- **Files**: `setup.py` (deleted) +- **Description**: Removed legacy `setup.py` file (migration to `pyproject.toml`). + +### Type Updates +- **Files**: + - `mlflow/server/js/src/experiment-tracking/types.ts` + - `mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTable.tsx` +- **Changes**: + - Added `UpdateExperimentSearchFacetsFn` type export + - Added import for `RUNS_VISIBILITY_MODE` enum + +## Docker Image Build +### Image Build +1. **Clone the Git Repository** + - Repository URL: [MLFlow GitHub Repo](https://github.com/oneconvergence/mlflow) + - Branch: `d3x-tag-v2.21.2` +2. **Build** + ```bash + sudo docker build -t : . + ``` +--- + +## Running MLFLOW Locally + +### Steps + +1. **Clone the Git Repository** + - Repository URL: [MLFlow GitHub Repo](https://github.com/oneconvergence/mlflow) + - Branch: `d3x-tag-v2.21.2` + +2. **Navigate to the Project Directory** + ```bash + cd mlflow/server/js + ``` + +3. **Install Dependencies** + ```bash + yarn install + ``` + +4. **Add Proxy in package.json** + ```json + "proxy": "your.domain.and.port" + ``` + +5. **Login to dkubex and add _oauth2_proxy in cookies** + - `_oauth2_proxy`: <_oauth2_proxy cookie from dkubex user> + +6. **Modify FetchUtils.ts** + - Navigate to `mlflow/server/js/src/common/utils/FetchUtils.ts` + - Update the `getAjaxUrl` function as follows: + ```typescript + export const getAjaxUrl = (relativeUrl: any) => { + return '/mlflow/' + relativeUrl; + }; + ``` + diff --git a/Dockerfile b/Dockerfile index c97d337ef6..bdb3a5bf5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,18 @@ -FROM python:3.9-bullseye +FROM python:3.10.10-slim-bullseye -WORKDIR /home/mlflow +RUN apt update && apt install -y build-essential --no-install-recommends -RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - # java - openjdk-11-jre-headless \ - # yarn - && npm install --global yarn \ - # protoc - && wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip -O /tmp/protoc.zip \ - && mkdir -p /home/mlflow/.local \ - && unzip /tmp/protoc.zip -d /home/mlflow/.local/protoc \ - && rm /tmp/protoc.zip \ - && chmod -R +x /home/mlflow/.local/protoc \ - # adding an unprivileged user - && groupadd --gid 10001 mlflow \ - && useradd --uid 10001 --gid mlflow --shell /bin/bash --create-home mlflow +RUN apt install -y git gnupg2 curl +RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 1646B01B86E50310 && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && apt update && apt install yarn -y ca-certificates curl gnupg +RUN pip install --upgrade pip psycopg2-binary PyMySQL boto3 setuptools wheel fastapi[all]==0.103.1 uvicorn==0.23.2 +RUN mkdir -p /etc/apt/keyrings && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y -ENV PATH="/home/mlflow/.local/protoc/bin:$PATH" +COPY . /mlflow -# the "mlflow" user created above, represented numerically for optimal compatibility with Kubernetes security policies -USER 10001 - -CMD ["bash"] +WORKDIR /mlflow/mlflow/server/js +RUN yarn install +RUN yarn build +WORKDIR /mlflow +RUN pip install .[auth] +WORKDIR / +ENTRYPOINT ["mlflow", "server", "--host", "0.0.0.0"] diff --git a/mlflow-changes.patch b/mlflow-changes.patch new file mode 100644 index 0000000000..374fc01c86 --- /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' + /> + */} +- */} + + + } + size='wide' + title={ + + } + > +
+
{promptlabDescription}
+
+ {promptlabDescription} +
+
+
+ ); +}; + +const styles = { + label: (theme: Theme) => ({ + marginTop: theme.spacing.sm, + }), + wrapper: (theme: Theme) => ({ + display: 'flex', + flexDirection: 'column' as const, + gap: theme.spacing.sm, + }), + gifContainer: () => ({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + minHeight: 200, + }), +}; diff --git a/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsSortSelector.tsx b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsSortSelector.tsx new file mode 100644 index 0000000000..9e68b5f558 --- /dev/null +++ b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsSortSelector.tsx @@ -0,0 +1,120 @@ +import { + DialogCombobox, + DialogComboboxContent, + DialogComboboxOptionList, + DialogComboboxOptionListSearch, + DialogComboboxOptionListSelectItem, + DialogComboboxTrigger, + ArrowDownIcon, + ArrowUpIcon, + SortAscendingIcon, + SortDescendingIcon, + useDesignSystemTheme, +} from '@databricks/design-system'; +import React, { useMemo } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { middleTruncateStr } from '../../../../../common/utils/StringUtils'; +import { + COLUMN_SORT_BY_ASC, + COLUMN_SORT_BY_DESC, + SORT_DELIMITER_SYMBOL, +} from '../../../../constants'; +import { ExperimentRunSortOption } from '../../hooks/useRunSortOptions'; +import { SearchExperimentRunsFacetsState } from '../../models/SearchExperimentRunsFacetsState'; + +export const ExperimentViewRunsSortSelector = React.memo( + ({ + orderByKey, + orderByAsc, + sortOptions, + onSortKeyChanged, + }: { + orderByKey: string; + orderByAsc: boolean; + sortOptions: ExperimentRunSortOption[]; + onSortKeyChanged: (valueContainer: any) => void; + }) => { + const { theme } = useDesignSystemTheme(); + + // Currently used canonical "sort by" value in form of "COLUMN_NAME***DIRECTION", e.g. "metrics.`metric`***DESCENDING" + const currentSortSelectValue = useMemo( + () => + `${orderByKey}${SORT_DELIMITER_SYMBOL}${ + orderByAsc ? COLUMN_SORT_BY_ASC : COLUMN_SORT_BY_DESC + }`, + [orderByAsc, orderByKey], + ); + + /** + * Calculate and memoize a label displayed in the "sort by" select. + * + * If full metrics and params list is populated by runs from the API, use the + * value corresponding to the calculated sort option list. + * + * If the sort option list is incomplete (e.g. because fetched run set is empty) while the + * order key is given (e.g. because URL state says so), use it to extract the key name. + */ + const currentSortSelectLabel = useMemo(() => { + // Search through all sort options generated basing on the fetched runs + const sortOption = sortOptions.find((option) => option.value === currentSortSelectValue); + + let sortOptionLabel = sortOption?.label; + + // If the actually chosen sort value is not found in the sort option list (e.g. because the list of fetched runs is empty), + // use it to generate the label + if (!sortOptionLabel) { + // The following regex extracts plain sort key name from its canonical form, i.e. + // metrics.`metric_key_name` => metric_key_name + const extractedKeyName = orderByKey.match(/^.+\.`(.+)`$/); + if (extractedKeyName) { + // eslint-disable-next-line prefer-destructuring + sortOptionLabel = extractedKeyName[1]; + } + } + return ( + + {orderByAsc ? : }{' '} + + : {sortOptionLabel} + + ); + }, [currentSortSelectValue, orderByAsc, orderByKey, sortOptions]); + + const handleChange = (updatedValue: string) => { + onSortKeyChanged({ value: updatedValue }); + }; + + const handleClear = () => { + onSortKeyChanged({ value: '' }); + }; + + return ( + + + + + + {sortOptions.map((sortOption) => ( + + + {sortOption.order === COLUMN_SORT_BY_ASC ? : } + {middleTruncateStr(sortOption.label, 50)} + + + ))} + + + + + ); + }, +); diff --git a/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTable.tsx b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTable.tsx index e7a3c03257..1d7909e374 100644 --- a/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTable.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTable.tsx @@ -42,6 +42,7 @@ import { createExperimentPageSearchFacetsState, ExperimentPageSearchFacetsState, } from '../../models/ExperimentPageSearchFacetsState'; +import { RUNS_VISIBILITY_MODE } from '../../../experiment-page/models/ExperimentPageUIState'; import { useExperimentTableSelectRowHandler } from '../../hooks/useExperimentTableSelectRowHandler'; import { useToggleRowVisibilityCallback } from '../../hooks/useToggleRowVisibilityCallback'; import { ExperimentViewRunsTableHeaderContextProvider } from './ExperimentViewRunsTableHeaderContext'; diff --git a/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTableCollapse.tsx b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTableCollapse.tsx new file mode 100644 index 0000000000..1325b9020c --- /dev/null +++ b/mlflow/server/js/src/experiment-tracking/components/experiment-page/components/runs/ExperimentViewRunsTableCollapse.tsx @@ -0,0 +1,84 @@ +import { + Button, + ChevronLeftIcon, + ChevronRightIcon, + useDesignSystemTheme, +} from '@databricks/design-system'; +import { UpdateExperimentViewStateFn } from '../../../../types'; +import { SearchExperimentRunsViewState } from '../../models/SearchExperimentRunsViewState'; + +/** + * Component used to expand/collapse runs list (table) when in compare runs mode. + */ +export const ExperimentViewRunsTableCollapse = ({ + updateViewState, + runListHidden, +}: { + updateViewState: UpdateExperimentViewStateFn; + runListHidden: SearchExperimentRunsViewState['runListHidden']; +}) => { + const { theme } = useDesignSystemTheme(); + return ( +
+
+
+
+
+ ); +}; diff --git a/mlflow/server/js/src/experiment-tracking/components/experiment-page/models/ExperimentPageUIState.tsx b/mlflow/server/js/src/experiment-tracking/components/experiment-page/models/ExperimentPageUIState.tsx index 9eb30c3cd5..afde9b5678 100644 --- a/mlflow/server/js/src/experiment-tracking/components/experiment-page/models/ExperimentPageUIState.tsx +++ b/mlflow/server/js/src/experiment-tracking/components/experiment-page/models/ExperimentPageUIState.tsx @@ -47,7 +47,7 @@ export enum RUNS_VISIBILITY_MODE { } export type RunsChartsGlobalLineChartConfig = Partial< - Pick + Pick & { maxResults?: number } >; /** diff --git a/mlflow/server/js/src/experiment-tracking/components/experiment-page/utils/experimentPage.fetch-utils.ts b/mlflow/server/js/src/experiment-tracking/components/experiment-page/utils/experimentPage.fetch-utils.ts index 99ba86fea7..c062cb0dc8 100644 --- a/mlflow/server/js/src/experiment-tracking/components/experiment-page/utils/experimentPage.fetch-utils.ts +++ b/mlflow/server/js/src/experiment-tracking/components/experiment-page/utils/experimentPage.fetch-utils.ts @@ -223,3 +223,5 @@ export const isSearchFacetsFilterUsed = (currentSearchFacetsState: ExperimentPag startTime !== DEFAULT_START_TIME, ); }; + +export const shouldRefetchRuns = () => true; diff --git a/mlflow/server/js/src/experiment-tracking/components/run-page/GrafanaIframe.tsx b/mlflow/server/js/src/experiment-tracking/components/run-page/GrafanaIframe.tsx new file mode 100644 index 0000000000..9e5449858c --- /dev/null +++ b/mlflow/server/js/src/experiment-tracking/components/run-page/GrafanaIframe.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface IframeProps { + runUuid?: string; + width?: string; + height?: string; +} + +const GrafanaIframe: React.FC = ({ runUuid, width = '100%', height = '100%' }) => { + const baseUrl = `${window.location.protocol}//${window.location.host}`; + const src = `${baseUrl}/grafana/d/depiaxha35ds0e/mlflow-logger?orgId=1&from=now-2d&to=now&timezone=browser&var-runid=${runUuid}&var-level=$__all&var-search=&theme=dark`; + return ( +