Skip to content

Commit 05d1ad1

Browse files
[UX] Improved UX of the project settings CLI section (#3183)
* [UX] Improved UX of the project settings CLI section * [UI] Linter * [UX] Improved UX of the project settings CLI section * [UX] Improved UX of the project settings CLI section --------- Co-authored-by: Oleg Vavilov <vavilovolegik@gmail.com>
1 parent 1a52617 commit 05d1ad1

7 files changed

Lines changed: 188 additions & 42 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ repos:
2121
hooks:
2222
- id: frontend-pre-commit
2323
name: frontend-pre-commit
24-
entry: bash -c "cd frontend && npm install && npm run precommit"
24+
entry: bash -c "cd frontend && npm install && npm run pre-commit"
2525
language: system
2626
pass_filenames: false

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"test": "jest",
1717
"test:update-snapshots": "jest -u",
1818
"generate-api": "npx @rtk-query/codegen-openapi openapi-config.ts",
19-
"precommit": "lint-staged"
19+
"pre-commit": "lint-staged"
2020
},
2121
"devDependencies": {
2222
"@babel/cli": "^7.25.9",

frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const overlayI18nStrings: AnnotationContextProps.I18nStrings = {
4343
export enum HotspotIds {
4444
ADD_TOP_UP_BALANCE = 'billing-top-up-balance',
4545
PAYMENT_CONTINUE_BUTTON = 'billing-payment-continue-button',
46+
INSTALL_CLI_COMMAND = 'install-cli-command',
4647
CONFIGURE_CLI_COMMAND = 'configure-cli-command',
4748
CREATE_FIRST_PROJECT = 'create-first-project',
4849
}
@@ -80,6 +81,7 @@ export const BILLING_TUTORIAL: TutorialPanelProps.Tutorial = {
8081
export const CONFIGURE_CLI_TUTORIAL: TutorialPanelProps.Tutorial = {
8182
completed: false,
8283
title: 'Set up the CLI',
84+
prerequisitesAlert: 'Please, create a project before set up the CLI',
8385
description: (
8486
<>
8587
<Box variant="p" color="text-body-secondary" padding={{ top: 'n' }}>
@@ -92,6 +94,11 @@ export const CONFIGURE_CLI_TUTORIAL: TutorialPanelProps.Tutorial = {
9294
{
9395
title: 'Configure the CLI',
9496
steps: [
97+
{
98+
title: 'Run the CLI install command',
99+
content: 'Run this command on your local machine to install the CLI.',
100+
hotspotId: HotspotIds.INSTALL_CLI_COMMAND,
101+
},
95102
{
96103
title: 'Run the dstack project add command',
97104
content: 'Run this command on your local machine to configure the dstack CLI.',

frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ export const useTutorials = () => {
113113
dispatch(updateTutorialPanelState({ createProjectCompleted: true }));
114114
}, []);
115115

116-
const startConfigCliTutorial = useCallback(() => {}, [billingUrl]);
116+
const startConfigCliTutorial = useCallback(() => {
117+
if (projectData?.length) {
118+
navigate(ROUTES.PROJECT.DETAILS.SETTINGS.FORMAT(projectData[0].project_name));
119+
}
120+
}, [projectData]);
117121

118122
const finishConfigCliTutorial = useCallback(() => {
119123
dispatch(updateTutorialPanelState({ configureCLICompleted: true }));
@@ -160,6 +164,7 @@ export const useTutorials = () => {
160164
completed: configureCLICompleted,
161165
startCallback: startConfigCliTutorial,
162166
finishCallback: finishConfigCliTutorial,
167+
prerequisitesNeeded: !createProjectCompleted,
163168
},
164169

165170
{

frontend/src/pages/Project/Details/Settings/constants.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const CLI_INFO = {
1313
</p>
1414
<p>
1515
To learn how to install the CLI, refer to the{' '}
16-
<a href={'https://dstack.ai/docs/cli/installation'} target="_blank">
16+
<a href={'https://dstack.ai/docs/installation#set-up-the-cli'} target="_blank">
1717
installation
1818
</a>{' '}
1919
guide.

frontend/src/pages/Project/Details/Settings/index.tsx

Lines changed: 171 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useNavigate, useParams } from 'react-router-dom';
44
import { debounce } from 'lodash';
5+
import { ExpandableSection, Tabs } from '@cloudscape-design/components';
6+
import Wizard from '@cloudscape-design/components/wizard';
57

68
import {
79
Box,
@@ -11,7 +13,6 @@ import {
1113
Container,
1214
Header,
1315
Hotspot,
14-
InfoLink,
1516
Loader,
1617
Popover,
1718
SelectCSD,
@@ -20,10 +21,12 @@ import {
2021
} from 'components';
2122
import { HotspotIds } from 'layouts/AppLayout/TutorialPanel/constants';
2223

23-
import { useBreadcrumbs, useHelpPanel, useNotifications } from 'hooks';
24+
import { useBreadcrumbs, useNotifications } from 'hooks';
2425
import { riseRouterException } from 'libs';
26+
import { copyToClipboard } from 'libs';
2527
import { ROUTES } from 'routes';
2628
import { useGetProjectQuery, useUpdateProjectMembersMutation, useUpdateProjectMutation } from 'services/project';
29+
import { useGetRunsQuery } from 'services/run';
2730
import { useGetUserDataQuery } from 'services/user';
2831

2932
import { useCheckAvailableProjectPermission } from 'pages/Project/hooks/useCheckAvailableProjectPermission';
@@ -37,7 +40,6 @@ import { BackendsTable } from '../../Backends/Table';
3740
import { GatewaysTable } from '../../Gateways';
3841
import { useGatewaysTable } from '../../Gateways/hooks';
3942
import { ProjectSecrets } from '../../Secrets';
40-
import { CLI_INFO } from './constants';
4143

4244
import styles from './styles.module.scss';
4345

@@ -46,7 +48,7 @@ export const ProjectSettings: React.FC = () => {
4648
const params = useParams();
4749
const navigate = useNavigate();
4850
const paramProjectName = params.projectName ?? '';
49-
const [openHelpPanel] = useHelpPanel();
51+
const [isExpandedCliSection, setIsExpandedCliSection] = React.useState(false);
5052
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: paramProjectName });
5153

5254
const { isAvailableDeletingPermission, isProjectManager, isProjectAdmin, isAvailableProjectManaging } =
@@ -60,6 +62,15 @@ export const ProjectSettings: React.FC = () => {
6062

6163
const { data, isLoading, error } = useGetProjectQuery({ name: paramProjectName });
6264

65+
const { data: runsData } = useGetRunsQuery({
66+
project_name: paramProjectName,
67+
limit: 1,
68+
});
69+
70+
useEffect(() => {
71+
setIsExpandedCliSection(!runsData || runsData.length === 0);
72+
}, [runsData]);
73+
6374
useEffect(() => {
6475
if (error && 'status' in error && error.status === 404) {
6576
riseRouterException();
@@ -167,6 +178,8 @@ export const ProjectSettings: React.FC = () => {
167178
});
168179
};
169180

181+
const [activeStepIndex, setActiveStepIndex] = React.useState(0);
182+
170183
if (isLoadingPage)
171184
return (
172185
<Container>
@@ -179,42 +192,163 @@ export const ProjectSettings: React.FC = () => {
179192
{data && backendsData && gatewaysData && (
180193
<SpaceBetween size="l">
181194
{isProjectMember && (
182-
<Container
183-
header={
184-
<Header variant="h2" info={<InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />}>
185-
{t('projects.edit.cli')}
186-
</Header>
195+
<ExpandableSection
196+
variant="container"
197+
headerText="CLI"
198+
expanded={isExpandedCliSection}
199+
onChange={({ detail }) => setIsExpandedCliSection(detail.expanded)}
200+
headerActions={
201+
<Button
202+
iconName="script"
203+
variant={isExpandedCliSection ? 'normal' : 'primary'}
204+
onClick={() => setIsExpandedCliSection((prev) => !prev)}
205+
/>
187206
}
207+
// headerInfo={<InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />}
188208
>
189-
<SpaceBetween size="s">
190-
<Box variant="p" color="text-body-secondary">
191-
Run the following commands to set up the CLI for this project
192-
</Box>
193-
194-
<div className={styles.codeWrapper}>
195-
<Hotspot hotspotId={HotspotIds.CONFIGURE_CLI_COMMAND}>
196-
<Code className={styles.code}>{configCliCommand}</Code>
197-
198-
<div className={styles.copy}>
199-
<Popover
200-
dismissButton={false}
201-
position="top"
202-
size="small"
203-
triggerType="custom"
204-
content={<StatusIndicator type="success">{t('common.copied')}</StatusIndicator>}
205-
>
206-
<Button
207-
formAction="none"
208-
iconName="copy"
209-
variant="normal"
210-
onClick={copyCliCommand}
209+
<Wizard
210+
i18nStrings={{
211+
stepNumberLabel: (stepNumber) => `Step ${stepNumber}`,
212+
collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`,
213+
skipToButtonLabel: (step) => `Skip to ${step.title}`,
214+
navigationAriaLabel: 'Steps',
215+
// cancelButton: "Cancel",
216+
previousButton: 'Previous',
217+
nextButton: 'Next',
218+
optional: 'required',
219+
}}
220+
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
221+
activeStepIndex={activeStepIndex}
222+
onSubmit={() => setIsExpandedCliSection(false)}
223+
submitButtonText="Dismiss"
224+
allowSkipTo={true}
225+
steps={[
226+
{
227+
title: 'Install CLI',
228+
// info: <InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />,
229+
description: 'To use dstack, install the CLI on your local machine.',
230+
content: (
231+
<Hotspot hotspotId={HotspotIds.INSTALL_CLI_COMMAND}>
232+
<Tabs
233+
variant="stacked"
234+
tabs={[
235+
{
236+
label: 'uv',
237+
id: 'uv',
238+
content: (
239+
<>
240+
<div className={styles.codeWrapper}>
241+
<Code className={styles.code}>
242+
uv tool install dstack -U
243+
</Code>
244+
245+
<div className={styles.copy}>
246+
<Popover
247+
dismissButton={false}
248+
position="top"
249+
size="small"
250+
triggerType="custom"
251+
content={
252+
<StatusIndicator type="success">
253+
{t('common.copied')}
254+
</StatusIndicator>
255+
}
256+
>
257+
<Button
258+
formAction="none"
259+
iconName="copy"
260+
variant="normal"
261+
onClick={() =>
262+
copyToClipboard(
263+
'uv tool install dstack -U',
264+
)
265+
}
266+
/>
267+
</Popover>
268+
</div>
269+
</div>
270+
</>
271+
),
272+
},
273+
{
274+
label: 'pip',
275+
id: 'pip',
276+
content: (
277+
<>
278+
<div className={styles.codeWrapper}>
279+
<Code className={styles.code}>
280+
pip install dstack -U
281+
</Code>
282+
283+
<div className={styles.copy}>
284+
<Popover
285+
dismissButton={false}
286+
position="top"
287+
size="small"
288+
triggerType="custom"
289+
content={
290+
<StatusIndicator type="success">
291+
{t('common.copied')}
292+
</StatusIndicator>
293+
}
294+
>
295+
<Button
296+
formAction="none"
297+
iconName="copy"
298+
variant="normal"
299+
onClick={() =>
300+
copyToClipboard('pip install dstack -U')
301+
}
302+
/>
303+
</Popover>
304+
</div>
305+
</div>
306+
</>
307+
),
308+
},
309+
]}
211310
/>
212-
</Popover>
213-
</div>
214-
</Hotspot>
215-
</div>
216-
</SpaceBetween>
217-
</Container>
311+
</Hotspot>
312+
),
313+
isOptional: true,
314+
},
315+
{
316+
title: 'Add project',
317+
// info: <InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />,
318+
description: 'To use dstack with this project, run the following command.',
319+
content: (
320+
<div className={styles.codeWrapper}>
321+
<Hotspot hotspotId={HotspotIds.CONFIGURE_CLI_COMMAND}>
322+
<Code className={styles.code}>{configCliCommand}</Code>
323+
324+
<div className={styles.copy}>
325+
<Popover
326+
dismissButton={false}
327+
position="top"
328+
size="small"
329+
triggerType="custom"
330+
content={
331+
<StatusIndicator type="success">
332+
{t('common.copied')}
333+
</StatusIndicator>
334+
}
335+
>
336+
<Button
337+
formAction="none"
338+
iconName="copy"
339+
variant="normal"
340+
onClick={copyCliCommand}
341+
/>
342+
</Popover>
343+
</div>
344+
</Hotspot>
345+
</div>
346+
),
347+
isOptional: true,
348+
},
349+
]}
350+
/>
351+
</ExpandableSection>
218352
)}
219353

220354
<BackendsTable

0 commit comments

Comments
 (0)