Skip to content

Commit

Permalink
Link Run from Aim UI to MLFlow UI (#55)
Browse files Browse the repository at this point in the history
* Create link for experiment to classic ui

* Link run from Aim UI to MLFlow UI

* use icon

* add icon

---------

Co-authored-by: fabio vincenzi <[email protected]>
Co-authored-by: Geoffrey Wilson <[email protected]>
  • Loading branch information
3 people authored Apr 1, 2024
1 parent e6946ab commit a6752c2
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 47 deletions.
19 changes: 11 additions & 8 deletions src/src/components/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DOCUMENTATIONS } from 'config/references';

import routes, { IRoute } from 'routes/routes';

import namespacesService from 'services/api/namespaces/namespacesService';
import { trackEvent } from 'services/analytics';

import { getItem } from 'utils/storage';
Expand Down Expand Up @@ -51,18 +52,20 @@ function SideBar(): React.FunctionComponentElement<React.ReactNode> {
}, []);

useEffect(() => {
fetch(`${getBaseHost()}/chooser/namespaces`)
.then((response) => response.json())
.then((data) =>
setNamespaces(data.map((item: { code: any }) => item.code)),
);

fetch(`${getBaseHost()}${getPrefix()}chooser/namespaces/current`)
.then((response) => response.json())
namespacesService
.fetchCurrentNamespace()
.call()
.then((data) => {
const selected = data.code;
setSelectedNamespace(selected);
});

namespacesService
.fetchNamespacesList()
.call()
.then((data) =>
setNamespaces(data.map((item: { code: any }) => item.code)),
);
}, []);

function selectNamespace(event: React.ChangeEvent<{ value: unknown }>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import classNames from 'classnames';
import { Link as RouteLink } from 'react-router-dom';
import { NavLink, useRouteMatch } from 'react-router-dom';
import moment from 'moment';

import { Skeleton } from '@material-ui/lab';
import { Tooltip } from '@material-ui/core';
import { Link, Tooltip } from '@material-ui/core';

import { Button, Icon, Text } from 'components/kit';
import ControlPopover from 'components/ControlPopover/ControlPopover';
import ErrorBoundary from 'components/ErrorBoundary/ErrorBoundary';

import { DATE_WITH_SECONDS } from 'config/dates/dates';
import { getBaseHost } from 'config/config';

import namespacesService from 'services/api/namespaces/namespacesService';

import ExperimentNavigationPopover from '../ExperimentNavigationPopover';

Expand All @@ -27,6 +31,12 @@ function ExperimentHeader({
getExperimentsData,
}: IExperimentHeaderProps): React.FunctionComponentElement<React.ReactNode> {
const { url } = useRouteMatch();
const [selectedNamespace, setSelectedNamespace] = useState<string>('');
useEffect(() => {
namespacesService.fetchCurrentNamespacePath().then((data) => {
setSelectedNamespace(data);
});
}, []);

return (
<ErrorBoundary>
Expand Down Expand Up @@ -108,6 +118,24 @@ function ExperimentHeader({
(experimentData?.creation_time || 0) * 1000,
).format(DATE_WITH_SECONDS)}`}
</Text>
<Link
href={
getBaseHost() +
selectedNamespace +
'/mlflow/#/experiments/' +
experimentId
}
target='_blank'
style={{ marginLeft: '2rem' }}
>
<div style={{ fontSize: '0.8rem' }}>
<Icon
name='live-demo'
style={{ marginRight: '0.3rem' }}
/>
Open in Classic UI
</div>
</Link>
</>
) : (
<Skeleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import classNames from 'classnames';
import moment from 'moment';

import ToggleButton from '@material-ui/lab/ToggleButton';
import { Button, Checkbox, InputBase, Tooltip } from '@material-ui/core';
import { Button, Checkbox, InputBase, Link, Tooltip } from '@material-ui/core';
import CheckBoxOutlineBlank from '@material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '@material-ui/icons/CheckBox';

import ErrorBoundary from 'components/ErrorBoundary/ErrorBoundary';
import { Icon, Spinner, Text } from 'components/kit';

import { DATE_WITH_SECONDS } from 'config/dates/dates';
import { getBaseHost } from 'config/config';

import { IExperimentData } from 'modules/core/api/experimentsApi';

import namespacesService from 'services/api/namespaces/namespacesService';

import { IExperimentSelectionPopoverProps } from '.';

import './ExperimentSelectionPopover.scss';
Expand Down Expand Up @@ -43,6 +46,12 @@ function ExperimentSelectionPopover({
const [visibleExperiments, setVisibleExperiments] = React.useState<
IExperimentData[]
>([]);
const [selectedNamespace, setSelectedNamespace] = React.useState<string>('');
React.useEffect(() => {
namespacesService.fetchCurrentNamespacePath().then((data) => {
setSelectedNamespace(data);
});
}, []);

React.useEffect(() => {
setVisibleExperiments(experimentsData || []);
Expand Down Expand Up @@ -236,35 +245,64 @@ function ExperimentSelectionPopover({
>
{shortenExperimentName(experiment?.name)}
</Text>
<div className='experimentBox__date'>
<Icon
name='calendar'
color={
experimentInList(
experiment.name,
selectedExperimentNames,
)
? '#414B6D'
: '#606986'
}
fontSize={12}
/>
<Text
size={14}
tint={
experimentInList(
experiment.name,
selectedExperimentNames,
)
? 80
: 70
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}
>
<div className='experimentBox__date'>
<Icon
name='calendar'
color={
experimentInList(
experiment.name,
selectedExperimentNames,
)
? '#414B6D'
: '#606986'
}
fontSize={12}
/>
<Text
size={14}
tint={
experimentInList(
experiment.name,
selectedExperimentNames,
)
? 80
: 70
}
weight={500}
>
{`${moment(experiment.creation_time * 1000).format(
DATE_WITH_SECONDS,
)}`}
</Text>
</div>
<Link
href={
getBaseHost() +
selectedNamespace +
'/mlflow/#/experiments/' +
experiment.id
}
weight={500}
target='_blank'
onClick={(e) => e.stopPropagation()}
style={{ marginLeft: '1rem' }}
>
{`${moment(experiment.creation_time * 1000).format(
DATE_WITH_SECONDS,
)}`}
</Text>
<div style={{ fontSize: '0.8rem' }}>
<Icon
name='live-demo'
style={{ marginRight: '0.3rem' }}
/>
Open in Classic UI
</div>
</Link>
</div>
</div>
</Button>
Expand Down
36 changes: 35 additions & 1 deletion src/src/pages/RunDetail/RunDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import {
} from 'react-router-dom';
import { useModel } from 'hooks';

import { Paper, Tab, Tabs, Tooltip } from '@material-ui/core';
import {
Paper,
Tab,
Tabs,
Tooltip,
Link as MaterialLink,
} from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';

import { Button, Icon, Text } from 'components/kit';
Expand All @@ -27,13 +33,15 @@ import Spinner from 'components/kit/Spinner';
import { ANALYTICS_EVENT_KEYS } from 'config/analytics/analyticsKeysMap';
import { DATE_WITH_SECONDS } from 'config/dates/dates';
import { PathEnum } from 'config/enums/routesEnum';
import { getBaseHost } from 'config/config';

import CompareSelectedRunsPopover from 'pages/Metrics/components/Table/CompareSelectedRunsPopover';

import runDetailAppModel from 'services/models/runs/runDetailAppModel';
import * as analytics from 'services/analytics';
import notesModel from 'services/models/notes/notesModel';
import { AppNameEnum } from 'services/models/explorer';
import namespacesService from 'services/api/namespaces/namespacesService';

import { setDocumentTitle } from 'utils/document/documentTitle';
import { processDurationTime } from 'utils/processDurationTime';
Expand Down Expand Up @@ -83,6 +91,12 @@ function RunDetail(): React.FunctionComponentElement<React.ReactNode> {
const [isRunSelectDropdownOpen, setIsRunSelectDropdownOpen] =
React.useState(false);
const [activeTab, setActiveTab] = React.useState(pathname);
const [selectedNamespace, setSelectedNamespace] = React.useState<string>('');
React.useEffect(() => {
namespacesService.fetchCurrentNamespacePath().then((data) => {
setSelectedNamespace(data);
});
}, []);

function redirect(): void {
const splitPathname: string[] = pathname.split('/');
Expand Down Expand Up @@ -320,6 +334,26 @@ function RunDetail(): React.FunctionComponentElement<React.ReactNode> {
: dateNow,
)}`}
</Text>
<MaterialLink
href={
getBaseHost() +
selectedNamespace +
'/mlflow/#/experiments/' +
runData?.runInfo?.experiment?.id +
'/runs/' +
runHash
}
target='_blank'
style={{ marginLeft: '2rem' }}
>
<div style={{ fontSize: '0.8rem' }}>
<Icon
name='live-demo'
style={{ marginRight: '0.3rem' }}
/>
Open in Classic UI
</div>
</MaterialLink>
</>
) : (
<Skeleton
Expand Down
36 changes: 28 additions & 8 deletions src/src/services/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAPIHost } from 'config/config';
import { getAPIHost, getBaseHost } from 'config/config';

function createAPIRequestWrapper<ResponseDataType>(
url: string,
Expand All @@ -11,7 +11,7 @@ function createAPIRequestWrapper<ResponseDataType>(
return {
call: (exceptionHandler?: (error: ResponseDataType) => any) =>
new Promise((resolve: (data: ResponseDataType) => void, reject) => {
fetch(`${getAPIHost()}/${url}`, { ...options, signal })
fetch(`${url}`, { ...options, signal })
.then(async (response) => {
try {
if (response.status >= 400) {
Expand Down Expand Up @@ -65,7 +65,7 @@ function getStream<ResponseDataType>(
.join('&');

return createAPIRequestWrapper<ResponseDataType>(
`${url}${
`${getAPIHost()}/${url}${
options?.method === 'POST' ? '' : queryString ? '?' + queryString : ''
}`,
{
Expand All @@ -86,7 +86,7 @@ function getStream1<ResponseDataType>(
options?: RequestInit,
) {
return createAPIRequestWrapper<ResponseDataType>(
`${url}${
`${getAPIHost()}/${url}${
options?.method === 'POST' && params
? '?' + new URLSearchParams(params).toString()
: ''
Expand All @@ -103,13 +103,32 @@ function getStream1<ResponseDataType>(
);
}

function getFromBaseHost<ResponseDataType>(
url: string,
params?: {},
options?: RequestInit,
) {
return createAPIRequestWrapper<ResponseDataType>(
`${getBaseHost()}${url}${
params ? '?' + new URLSearchParams(params).toString() : ''
}`,
{
method: 'GET',
...options,
headers: getRequestHeaders(),
},
);
}

function get<ResponseDataType>(
url: string,
params?: {},
options?: RequestInit,
) {
return createAPIRequestWrapper<ResponseDataType>(
`${url}${params ? '?' + new URLSearchParams(params).toString() : ''}`,
`${getAPIHost()}/${url}${
params ? '?' + new URLSearchParams(params).toString() : ''
}`,
{
method: 'GET',
...options,
Expand All @@ -123,7 +142,7 @@ function post<ResponseDataType>(
data: object,
options?: RequestInit,
) {
return createAPIRequestWrapper<ResponseDataType>(url, {
return createAPIRequestWrapper<ResponseDataType>(`${getAPIHost()}/${url}`, {
method: 'POST',
...options,
headers: getRequestHeaders(),
Expand All @@ -136,7 +155,7 @@ function put<ResponseDataType>(
data: object,
options?: RequestInit,
) {
return createAPIRequestWrapper<ResponseDataType>(url, {
return createAPIRequestWrapper<ResponseDataType>(`${getAPIHost()}/${url}`, {
method: 'PUT',
...options,
headers: getRequestHeaders(),
Expand All @@ -145,7 +164,7 @@ function put<ResponseDataType>(
}

function remove<ResponseDataType>(url: string, options?: RequestInit) {
return createAPIRequestWrapper<ResponseDataType>(url, {
return createAPIRequestWrapper<ResponseDataType>(`${getAPIHost()}/${url}`, {
method: 'DELETE',
...options,
});
Expand All @@ -164,6 +183,7 @@ function getRequestHeaders() {

const API = {
get,
getFromBaseHost,
getStream,
getStream1,
post,
Expand Down
Loading

0 comments on commit a6752c2

Please sign in to comment.