Skip to content

Commit be8c55c

Browse files
committed
feat(FR-1673): implement StartFromURL modal component for repository imports (#4627)
1 parent 95898aa commit be8c55c

File tree

11 files changed

+296
-61
lines changed

11 files changed

+296
-61
lines changed

packages/backend.ai-ui/src/components/BAIModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ const BAIModal: React.FC<BAIModalProps> = ({ className, ...modalProps }) => {
6161
};
6262
return (
6363
<Modal
64-
keyboard={false}
6564
{...modalProps}
6665
centered={modalProps.centered ?? true}
6766
className={classNames(`bai-modal ${className ?? ''}`, styles.modal)}

packages/backend.ai-ui/src/icons/BAIURLStartIcon.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@ interface BAIURLStartIconProps
66
extends Omit<CustomIconComponentProps, 'width' | 'height' | 'fill'> {}
77

88
const BAIURLStartIcon: React.FC<BAIURLStartIconProps> = (props) => {
9-
return <Icon component={logo} {...props} />;
9+
return (
10+
<Icon
11+
component={logo}
12+
{...props}
13+
style={{
14+
...props.style,
15+
fill: 'none',
16+
stroke: 'currentColor',
17+
}}
18+
/>
19+
);
1020
};
1121

1222
export default BAIURLStartIcon;
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 3 additions & 3 deletions
Loading
Lines changed: 3 additions & 6 deletions
Loading

react/src/App.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ const ServiceLauncherUpdatePage = React.lazy(
6868
const InteractiveLoginPage = React.lazy(
6969
() => import('./pages/InteractiveLoginPage'),
7070
);
71-
const ImportAndRunPage = React.lazy(() => import('./pages/ImportAndRunPage'));
7271
const UserCredentialsPage = React.lazy(
7372
() => import('./pages/UserCredentialsPage'),
7473
);
@@ -356,15 +355,20 @@ const router = createBrowserRouter([
356355
</Suspense>
357356
),
358357
},
358+
// Redirect paths for backward compatibility
359359
{
360360
path: '/import',
361-
handle: { labelKey: 'webui.menu.Import&Run' },
362361
Component: () => {
363-
return (
364-
<>
365-
<ImportAndRunPage />
366-
</>
367-
);
362+
const location = useLocation();
363+
return <WebUINavigate to={'/start' + location.search} replace />;
364+
},
365+
},
366+
// Redirect paths for legacy support
367+
{
368+
path: '/github',
369+
Component: () => {
370+
const location = useLocation();
371+
return <WebUINavigate to={'/start' + location.search} replace />;
368372
},
369373
},
370374
{

react/src/components/ImportNotebookForm.tsx

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
generateRandomString,
1818
useErrorMessageResolver,
1919
} from 'backend.ai-ui';
20-
import { useRef } from 'react';
20+
import { useImperativeHandle, useRef } from 'react';
2121
import { useTranslation } from 'react-i18next';
2222
import {
2323
StartSessionWithDefaultValue,
@@ -32,10 +32,23 @@ const regularizeGithubURL = (url: string) => {
3232

3333
const notebookURLPattern = new RegExp('^(https?)://([\\w./-]{1,}).ipynb$');
3434

35-
const ImportNotebookForm: React.FC<FormProps> = (props) => {
35+
interface ImportNotebookFormProps extends FormProps {
36+
ref?: React.Ref<FormInstance<{ url: string }>>;
37+
initialUrl?: string;
38+
}
39+
const ImportNotebookForm: React.FC<ImportNotebookFormProps> = ({
40+
ref,
41+
initialUrl,
42+
...props
43+
}) => {
44+
'use memo';
3645
const formRef = useRef<FormInstance<{
3746
url: string;
3847
}> | null>(null);
48+
49+
// Expose the internal formRef through the forwarded ref
50+
useImperativeHandle(ref, () => formRef.current!, []);
51+
3952
const { t } = useTranslation();
4053
const app = App.useApp();
4154
const webuiNavigate = useWebUINavigate();
@@ -80,7 +93,15 @@ const ImportNotebookForm: React.FC<FormProps> = (props) => {
8093
};
8194

8295
return (
83-
<Form ref={formRef} layout="vertical" {...props}>
96+
<Form
97+
ref={formRef}
98+
layout="vertical"
99+
initialValues={{ url: initialUrl }}
100+
onFinish={async (values) => {
101+
await handleNotebookImport(values.url);
102+
}}
103+
{...props}
104+
>
84105
<Form.Item
85106
name="url"
86107
label={t('import.NotebookURL')}
@@ -108,12 +129,7 @@ const ImportNotebookForm: React.FC<FormProps> = (props) => {
108129
type="primary"
109130
block
110131
action={async () => {
111-
const values = await formRef.current
112-
?.validateFields()
113-
.catch(() => undefined);
114-
if (values) {
115-
await handleNotebookImport(values.url);
116-
}
132+
formRef.current?.submit();
117133
}}
118134
>
119135
{t('import.GetAndRunNotebook')}
@@ -130,29 +146,26 @@ const ImportNotebookForm: React.FC<FormProps> = (props) => {
130146
<Form.Item dependencies={[['url']]}>
131147
{({ getFieldValue }) => {
132148
const url = getFieldValue('url') || '';
133-
const rawURL = regularizeGithubURL(url);
134-
const badgeURL = rawURL.replace(
135-
'https://raw.githubusercontent.com/',
136-
'',
137-
);
138-
let baseURL = '';
139149

150+
// Create the new format URL with encoded JSON data
151+
const importData = { url };
152+
const encodedData = encodeURIComponent(JSON.stringify(importData));
153+
154+
let baseURL = '';
140155
if (globalThis.isElectron) {
141-
baseURL = 'https://cloud.backend.ai/github?';
156+
baseURL = 'https://cloud.backend.ai';
142157
} else {
143158
baseURL =
144159
window.location.protocol + '//' + window.location.hostname;
145160
if (window.location.port) {
146161
baseURL = baseURL + ':' + window.location.port;
147162
}
148-
baseURL = baseURL + '/github?';
149163
}
150-
const fullText = `<a href="${
151-
baseURL + badgeURL
152-
}"><img src="https://www.backend.ai/assets/badge.svg" /></a>`;
153-
const fullTextMarkdown = `[![Run on Backend.AI](https://www.backend.ai/assets/badge.svg)](${
154-
baseURL + badgeURL
155-
})`;
164+
165+
const badgeURL = `${baseURL}/start?type=url&data=${encodedData}`;
166+
167+
const fullText = `<a href="${badgeURL}"><img src="https://www.backend.ai/assets/badge.svg" /></a>`;
168+
const fullTextMarkdown = `[![Run on Backend.AI](https://www.backend.ai/assets/badge.svg)](${badgeURL})`;
156169

157170
const isValidURL =
158171
notebookURLPattern.test(url) && url.length <= 2048;
@@ -194,4 +207,7 @@ const ImportNotebookForm: React.FC<FormProps> = (props) => {
194207
);
195208
};
196209

210+
// Add display name for debugging
211+
ImportNotebookForm.displayName = 'ImportNotebookForm';
212+
197213
export default ImportNotebookForm;

react/src/components/ImportRepoForm.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface ImportFromURLFormValues {
3535

3636
interface ImportFromURLFormProps extends FormProps {
3737
urlType: URLType;
38+
initialUrl?: string;
39+
initialBranch?: string;
3840
}
3941

4042
const createRepoBootstrapScript = (
@@ -71,6 +73,8 @@ const createRepoBootstrapScript = (
7173

7274
const ImportRepoForm: React.FC<ImportFromURLFormProps> = ({
7375
urlType,
76+
initialUrl,
77+
initialBranch,
7478
...formProps
7579
}) => {
7680
'use memo';
@@ -268,6 +272,8 @@ const ImportRepoForm: React.FC<ImportFromURLFormProps> = ({
268272
initialValues={
269273
{
270274
vfolder_usage_mode: 'general',
275+
url: initialUrl,
276+
gitlabBranch: initialBranch,
271277
} as ImportFromURLFormValues
272278
}
273279
>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import BAITabs from './BAITabs';
2+
import ImportNotebookForm from './ImportNotebookForm';
3+
import ImportRepoForm from './ImportRepoForm';
4+
import { GithubOutlined, GitlabOutlined } from '@ant-design/icons';
5+
import {
6+
BAIFlex,
7+
BAIJupyterIcon,
8+
BAIModal,
9+
BAIModalProps,
10+
} from 'backend.ai-ui';
11+
import React from 'react';
12+
import { useTranslation } from 'react-i18next';
13+
14+
interface StartFromURLModalProps extends Omit<BAIModalProps, 'children'> {
15+
initialTab?: 'notebook' | 'github' | 'gitlab';
16+
initialData?: {
17+
url?: string;
18+
branch?: string;
19+
};
20+
}
21+
22+
const StartFromURLModal: React.FC<StartFromURLModalProps> = ({
23+
initialTab,
24+
initialData,
25+
...modalProps
26+
}) => {
27+
'use memo';
28+
const { t } = useTranslation();
29+
30+
return (
31+
<BAIModal
32+
title={t('start.StartFromURL')}
33+
width={800}
34+
footer={null}
35+
{...modalProps}
36+
>
37+
<BAITabs
38+
defaultActiveKey={initialTab}
39+
items={[
40+
{
41+
key: 'notebook',
42+
children: <ImportNotebookForm initialUrl={initialData?.url} />,
43+
label: (
44+
<BAIFlex gap={'xs'}>
45+
<BAIJupyterIcon /> {t('import.ImportNotebook')}
46+
</BAIFlex>
47+
),
48+
},
49+
{
50+
key: 'github',
51+
children: (
52+
<ImportRepoForm
53+
urlType="github"
54+
initialUrl={initialData?.url}
55+
initialBranch={initialData?.branch}
56+
/>
57+
),
58+
label: (
59+
<BAIFlex gap="xs">
60+
<GithubOutlined style={{ display: 'inline' }} />
61+
{t('import.ImportGithubRepo')}
62+
</BAIFlex>
63+
),
64+
},
65+
{
66+
key: 'gitlab',
67+
children: (
68+
<ImportRepoForm
69+
urlType="gitlab"
70+
initialUrl={initialData?.url}
71+
initialBranch={initialData?.branch}
72+
/>
73+
),
74+
label: (
75+
<BAIFlex gap="xs">
76+
<GitlabOutlined style={{ display: 'inline' }} />
77+
{t('import.ImportGitlabRepo')}
78+
</BAIFlex>
79+
),
80+
},
81+
]}
82+
></BAITabs>
83+
</BAIModal>
84+
);
85+
};
86+
87+
export default StartFromURLModal;

0 commit comments

Comments
 (0)