From 4114045bcd287c5fd247e4f9c77bf3b0c01c8a5f Mon Sep 17 00:00:00 2001 From: julieg18 Date: Fri, 8 Dec 2023 11:43:13 -0600 Subject: [PATCH 1/7] Move dvc table into reusable component --- .../setup/components/dvc/DvcEnvCommandRow.tsx | 42 ---------------- .../setup/components/dvc/DvcEnvDetails.tsx | 48 ++++++++++++++----- .../setup/components/dvc/DvcEnvInfoRow.tsx | 12 ----- .../setup/components/dvc/styles.module.scss | 37 -------------- .../setup/components/shared/DetailsTable.tsx | 14 ++++++ .../components/shared/DetailsTableRow.tsx | 37 ++++++++++++++ .../components/shared/styles.module.scss | 33 +++++++++++++ 7 files changed, 119 insertions(+), 104 deletions(-) delete mode 100644 webview/src/setup/components/dvc/DvcEnvCommandRow.tsx delete mode 100644 webview/src/setup/components/dvc/DvcEnvInfoRow.tsx create mode 100644 webview/src/setup/components/shared/DetailsTable.tsx create mode 100644 webview/src/setup/components/shared/DetailsTableRow.tsx diff --git a/webview/src/setup/components/dvc/DvcEnvCommandRow.tsx b/webview/src/setup/components/dvc/DvcEnvCommandRow.tsx deleted file mode 100644 index 16efc6747b..0000000000 --- a/webview/src/setup/components/dvc/DvcEnvCommandRow.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import { useSelector } from 'react-redux' -import { DvcEnvInfoRow } from './DvcEnvInfoRow' -import styles from './styles.module.scss' -import { setupWorkspace, updatePythonEnvironment } from '../../util/messages' -import { SetupState } from '../../store' - -interface DvcEnvCommandRowProps { - command: string -} - -export const DvcEnvCommandRow: React.FC = ({ - command -}) => { - const isPythonExtensionUsed = useSelector( - (state: SetupState) => state.dvc.isPythonExtensionUsed - ) - const commandText = command || 'Not found' - const commandValue = ( - <> - {commandText} - - - {isPythonExtensionUsed && ( - <> - - - - )} - - - ) - - return -} diff --git a/webview/src/setup/components/dvc/DvcEnvDetails.tsx b/webview/src/setup/components/dvc/DvcEnvDetails.tsx index c315f689be..522009d81a 100644 --- a/webview/src/setup/components/dvc/DvcEnvDetails.tsx +++ b/webview/src/setup/components/dvc/DvcEnvDetails.tsx @@ -1,29 +1,51 @@ import React from 'react' +import { useSelector } from 'react-redux' import { DvcCliDetails } from 'dvc/src/setup/webview/contract' import { LATEST_TESTED_CLI_VERSION, MIN_CLI_VERSION } from 'dvc/src/cli/dvc/contract' -import { DvcEnvInfoRow } from './DvcEnvInfoRow' -import styles from './styles.module.scss' -import { DvcEnvCommandRow } from './DvcEnvCommandRow' +import { DetailsTable } from '../shared/DetailsTable' +import { + DetailsTableRow, + DetailsTableRowActions +} from '../shared/DetailsTableRow' +import { setupWorkspace, updatePythonEnvironment } from '../../util/messages' +import { SetupState } from '../../store' export const DvcEnvDetails: React.FC = ({ command, version }) => { - const versionText = ( - {`${ - version || 'Not found' - } (required ${MIN_CLI_VERSION} and above, tested with ${LATEST_TESTED_CLI_VERSION})`} + const isPythonExtensionUsed = useSelector( + (state: SetupState) => state.dvc.isPythonExtensionUsed ) + const commandRowActions: DetailsTableRowActions = [ + { onClick: setupWorkspace, text: 'Locate DVC' } + ] + + if (isPythonExtensionUsed) { + commandRowActions.push({ + onClick: updatePythonEnvironment, + text: 'Set Env' + }) + } return ( - - - {version && } - - -
+ + {version && ( + + )} + + ) } diff --git a/webview/src/setup/components/dvc/DvcEnvInfoRow.tsx b/webview/src/setup/components/dvc/DvcEnvInfoRow.tsx deleted file mode 100644 index 1d7b3bd27b..0000000000 --- a/webview/src/setup/components/dvc/DvcEnvInfoRow.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { ReactElement } from 'react' -import styles from './styles.module.scss' - -export const DvcEnvInfoRow: React.FC<{ - title: string - text: string | ReactElement -}> = ({ title, text }) => ( - - {title}: - {text} - -) diff --git a/webview/src/setup/components/dvc/styles.module.scss b/webview/src/setup/components/dvc/styles.module.scss index dd8db78424..a9b5e65b1f 100644 --- a/webview/src/setup/components/dvc/styles.module.scss +++ b/webview/src/setup/components/dvc/styles.module.scss @@ -1,37 +1,6 @@ @import '../../../shared/variables'; @import '../../../shared/mixins'; -.envDetails { - margin: 0 auto; - text-align: left; - margin-bottom: 1rem; -} - -.envDetailsKey, -.envDetailsValue { - padding: 5px; -} - -.envDetailsKey { - font-weight: bold; - white-space: nowrap; - vertical-align: top; -} - -.envDetailsValue { - padding-left: 50px; - display: flex; - flex-direction: column; -} - -.separator { - margin: 0 5px; - - &::before { - content: '|'; - } -} - .inlineWarningSvg { vertical-align: middle; fill: $warn-color; @@ -39,12 +8,6 @@ height: 16px; } -.buttonAsLink { - @extend %buttonAsLink; - - font-size: inherit; -} - .sideBySideButtons *:not(:first-child) { margin-left: 15px; } diff --git a/webview/src/setup/components/shared/DetailsTable.tsx b/webview/src/setup/components/shared/DetailsTable.tsx new file mode 100644 index 0000000000..87cabd97a3 --- /dev/null +++ b/webview/src/setup/components/shared/DetailsTable.tsx @@ -0,0 +1,14 @@ +import React, { PropsWithChildren } from 'react' + +import styles from './styles.module.scss' + +export const DetailsTable: React.FC> = ({ + children, + testId +}) => { + return ( + + {children} +
+ ) +} diff --git a/webview/src/setup/components/shared/DetailsTableRow.tsx b/webview/src/setup/components/shared/DetailsTableRow.tsx new file mode 100644 index 0000000000..fae2a1d178 --- /dev/null +++ b/webview/src/setup/components/shared/DetailsTableRow.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import styles from './styles.module.scss' + +export type DetailsTableRowActions = { text: string; onClick: () => void }[] + +type DetailsTableRowProps = { + title: string + text: string + actions?: DetailsTableRowActions +} + +export const DetailsTableRow: React.FC = ({ + title, + text, + actions +}) => { + return ( + + {title}: + + {text} + {actions && ( + + {actions.map(({ text, onClick }, index) => ( + <> + {index > 0 && } + + + ))} + + )} + + + ) +} diff --git a/webview/src/setup/components/shared/styles.module.scss b/webview/src/setup/components/shared/styles.module.scss index 1e203d451d..abd780c708 100644 --- a/webview/src/setup/components/shared/styles.module.scss +++ b/webview/src/setup/components/shared/styles.module.scss @@ -1,4 +1,5 @@ @import '../../../shared/variables'; +@import '../../../shared/mixins'; .codeBlock { text-align: left; @@ -19,3 +20,35 @@ fill: $accent-color; margin-bottom: -3px; } + +.detailsTable { + margin: 0 auto; + text-align: left; + margin-bottom: 1rem; +} + +.detailsTableKey { + font-weight: bold; + white-space: nowrap; + vertical-align: top; +} + +.detailsTableValue { + padding-left: 50px; + display: flex; + flex-direction: column; +} + +.buttonAsLink { + @extend %buttonAsLink; + + font-size: inherit; +} + +.separator { + margin: 0 5px; + + &::before { + content: '|'; + } +} From 65b537b5d78a482adf13ed95ffde2bde8f7e689f Mon Sep 17 00:00:00 2001 From: julieg18 Date: Fri, 8 Dec 2023 12:06:12 -0600 Subject: [PATCH 2/7] Add Studio url table --- extension/src/setup/index.ts | 4 +++ extension/src/setup/webview/contract.ts | 1 + extension/src/setup/webview/messages.ts | 4 +++ extension/src/webview/contract.ts | 4 +++ webview/src/setup/components/App.test.tsx | 1 + webview/src/setup/components/App.tsx | 4 +++ .../components/shared/DetailsTableRow.tsx | 4 +-- .../src/setup/components/studio/Connect.tsx | 5 ++-- .../src/setup/components/studio/Settings.tsx | 11 ++++--- .../src/setup/components/studio/Studio.tsx | 30 +++++++++++++++++-- webview/src/setup/state/dvcSlice.ts | 1 + webview/src/setup/state/studioSlice.ts | 16 ++++++++-- webview/src/setup/util/messages.ts | 6 ++++ webview/src/stories/Setup.stories.tsx | 1 + 14 files changed, 78 insertions(+), 14 deletions(-) diff --git a/extension/src/setup/index.ts b/extension/src/setup/index.ts index 27c3bc0b41..ff49fd532b 100644 --- a/extension/src/setup/index.ts +++ b/extension/src/setup/index.ts @@ -3,6 +3,7 @@ import { Event, EventEmitter } from 'vscode' import { Disposable, Disposer } from '@hediet/std/disposable' import isEmpty from 'lodash.isempty' import { + DEFAULT_STUDIO_URL, DvcCliDetails, SetupSection, SetupData as TSetupData @@ -421,6 +422,8 @@ export class Setup const isPythonEnvironmentGlobal = isPythonExtensionUsed && (await isActivePythonEnvGlobal()) + const studioUrl = this.studio.getStudioUrl() + this.webviewMessages.sendWebviewMessage({ canGitInitialize, cliCompatible: this.getCliCompatible(), @@ -437,6 +440,7 @@ export class Setup pythonBinPath: getBinDisplayText(pythonBinPath), remoteList, sectionCollapsed: collectSectionCollapsed(this.focusedSection), + selfHostedStudioUrl: studioUrl === DEFAULT_STUDIO_URL ? null : studioUrl, shareLiveToStudio: !!this.studio.getShareLiveToStudio() }) this.focusedSection = undefined diff --git a/extension/src/setup/webview/contract.ts b/extension/src/setup/webview/contract.ts index bc0436d766..d6aefcabc5 100644 --- a/extension/src/setup/webview/contract.ts +++ b/extension/src/setup/webview/contract.ts @@ -22,6 +22,7 @@ export type SetupData = { pythonBinPath: string | undefined remoteList: RemoteList sectionCollapsed: typeof DEFAULT_SECTION_COLLAPSED | undefined + selfHostedStudioUrl: string | null shareLiveToStudio: boolean isAboveLatestTestedVersion: boolean | undefined } diff --git a/extension/src/setup/webview/messages.ts b/extension/src/setup/webview/messages.ts index 980a5129ad..a4f11f07fb 100644 --- a/extension/src/setup/webview/messages.ts +++ b/extension/src/setup/webview/messages.ts @@ -70,10 +70,14 @@ export class WebviewMessages { return commands.executeCommand( RegisteredCommands.ADD_STUDIO_ACCESS_TOKEN ) + case MessageFromWebviewType.SAVE_STUDIO_URL: + return commands.executeCommand(RegisteredCommands.UPDATE_STUDIO_URL) case MessageFromWebviewType.REMOVE_STUDIO_TOKEN: return commands.executeCommand( RegisteredCommands.REMOVE_STUDIO_ACCESS_TOKEN ) + case MessageFromWebviewType.REMOVE_STUDIO_URL: + return commands.executeCommand(RegisteredCommands.REMOVE_STUDIO_URL) case MessageFromWebviewType.SET_STUDIO_SHARE_EXPERIMENTS_LIVE: return this.updateStudioOffline(message.payload) case MessageFromWebviewType.REQUEST_STUDIO_TOKEN: diff --git a/extension/src/webview/contract.ts b/extension/src/webview/contract.ts index 11eee13d42..8e765436e5 100644 --- a/extension/src/webview/contract.ts +++ b/extension/src/webview/contract.ts @@ -47,6 +47,7 @@ export enum MessageFromWebviewType { RESIZE_PLOTS = 'resize-plots', REQUEST_STUDIO_TOKEN = 'request-studio-token', SAVE_STUDIO_TOKEN = 'save-studio-token', + SAVE_STUDIO_URL = 'save-studio-url', SET_COMPARISON_MULTI_PLOT_VALUE = 'update-comparison-multi-plot-value', SET_SMOOTH_PLOT_VALUE = 'update-smooth-plot-value', SHOW_EXPERIMENT_LOGS = 'show-experiment-logs', @@ -71,6 +72,7 @@ export enum MessageFromWebviewType { REMOTE_REMOVE = 'remote-remove', REMOVE_CUSTOM_PLOTS = 'remove-custom-plots', REMOVE_STUDIO_TOKEN = 'remove-studio-token', + REMOVE_STUDIO_URL = 'remove-studio-url', MODIFY_WORKSPACE_PARAMS_AND_QUEUE = 'modify-workspace-params-and-queue', MODIFY_WORKSPACE_PARAMS_AND_RUN = 'modify-workspace-params-and-run', MODIFY_WORKSPACE_PARAMS_RESET_AND_RUN = 'modify-workspace-params-reset-and-run', @@ -224,6 +226,7 @@ export type MessageFromWebview = type: MessageFromWebviewType.REMOVE_CUSTOM_PLOTS } | { type: MessageFromWebviewType.REMOVE_STUDIO_TOKEN } + | { type: MessageFromWebviewType.REMOVE_STUDIO_URL } | { type: MessageFromWebviewType.REORDER_PLOTS_COMPARISON payload: string[] @@ -291,6 +294,7 @@ export type MessageFromWebview = | { type: MessageFromWebviewType.UPGRADE_DVC } | { type: MessageFromWebviewType.SETUP_WORKSPACE } | { type: MessageFromWebviewType.SAVE_STUDIO_TOKEN } + | { type: MessageFromWebviewType.SAVE_STUDIO_URL } | { type: MessageFromWebviewType.REQUEST_STUDIO_TOKEN } | { type: MessageFromWebviewType.ADD_CONFIGURATION } | { type: MessageFromWebviewType.ZOOM_PLOT; payload?: string } diff --git a/webview/src/setup/components/App.test.tsx b/webview/src/setup/components/App.test.tsx index 6a61e995b6..c99d3debb6 100644 --- a/webview/src/setup/components/App.test.tsx +++ b/webview/src/setup/components/App.test.tsx @@ -41,6 +41,7 @@ const DEFAULT_DATA = { pythonBinPath: undefined, remoteList: undefined, sectionCollapsed: undefined, + selfHostedStudioUrl: null, shareLiveToStudio: false } diff --git a/webview/src/setup/components/App.tsx b/webview/src/setup/components/App.tsx index 23b5ddd134..fe3e0c99e8 100644 --- a/webview/src/setup/components/App.tsx +++ b/webview/src/setup/components/App.tsx @@ -33,6 +33,7 @@ import { import { updateRemoteList } from '../state/remoteSlice' import { updateIsStudioConnected, + updateSelfHostedStudioUrl, updateShareLiveToStudio } from '../state/studioSlice' import { setStudioShareExperimentsLive } from '../util/messages' @@ -119,6 +120,9 @@ export const feedStore = ( case 'shareLiveToStudio': dispatch(updateShareLiveToStudio(data.data.shareLiveToStudio)) continue + case 'selfHostedStudioUrl': + dispatch(updateSelfHostedStudioUrl(data.data.selfHostedStudioUrl)) + continue case 'remoteList': dispatch(updateRemoteList(data.data.remoteList)) continue diff --git a/webview/src/setup/components/shared/DetailsTableRow.tsx b/webview/src/setup/components/shared/DetailsTableRow.tsx index fae2a1d178..17f33ee241 100644 --- a/webview/src/setup/components/shared/DetailsTableRow.tsx +++ b/webview/src/setup/components/shared/DetailsTableRow.tsx @@ -22,12 +22,12 @@ export const DetailsTableRow: React.FC = ({ {actions && ( {actions.map(({ text, onClick }, index) => ( - <> + {index > 0 && } - + ))} )} diff --git a/webview/src/setup/components/studio/Connect.tsx b/webview/src/setup/components/studio/Connect.tsx index 2f89aebc06..a094c8444d 100644 --- a/webview/src/setup/components/studio/Connect.tsx +++ b/webview/src/setup/components/studio/Connect.tsx @@ -1,16 +1,17 @@ -import React from 'react' +import React, { PropsWithChildren } from 'react' import { DEFAULT_STUDIO_URL } from 'dvc/src/setup/webview/contract' import { EmptyState } from '../../../shared/components/emptyState/EmptyState' import { requestStudioToken, saveStudioToken } from '../../util/messages' import { Button } from '../../../shared/components/button/Button' -export const Connect: React.FC = () => { +export const Connect: React.FC = ({ children }) => { return (

Connect to Studio

+ {children}

Share experiments and plots with collaborators directly from your IDE. Start sending data with an{' '} diff --git a/webview/src/setup/components/studio/Settings.tsx b/webview/src/setup/components/studio/Settings.tsx index 46bf7acd8c..5392f27df9 100644 --- a/webview/src/setup/components/studio/Settings.tsx +++ b/webview/src/setup/components/studio/Settings.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { PropsWithChildren } from 'react' import { useSelector } from 'react-redux' import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react' import styles from './styles.module.scss' @@ -7,9 +7,11 @@ import { EmptyState } from '../../../shared/components/emptyState/EmptyState' import { Button } from '../../../shared/components/button/Button' import { SetupState } from '../../store' -export const Settings: React.FC<{ - setShareLiveToStudio: (shareLiveToStudio: boolean) => void -}> = ({ setShareLiveToStudio }) => { +export const Settings: React.FC< + PropsWithChildren<{ + setShareLiveToStudio: (shareLiveToStudio: boolean) => void + }> +> = ({ children, setShareLiveToStudio }) => { const shareLiveToStudio = useSelector( (state: SetupState) => state.studio.shareLiveToStudio ) @@ -18,6 +20,7 @@ export const Settings: React.FC<{

Studio Settings

+ {children}
void }> = ({ cliCompatible, setShareLiveToStudio }) => { - const { isStudioConnected } = useSelector((state: SetupState) => state.studio) + const { isStudioConnected, selfHostedStudioUrl } = useSelector( + (state: SetupState) => state.studio + ) if (!cliCompatible) { return ( @@ -22,9 +27,28 @@ export const Studio: React.FC<{ ) } + const children = selfHostedStudioUrl && ( + + + + ) + return isStudioConnected ? ( - + {children} ) : ( - + {children} ) } diff --git a/webview/src/setup/state/dvcSlice.ts b/webview/src/setup/state/dvcSlice.ts index 3368e8604e..95417ba051 100644 --- a/webview/src/setup/state/dvcSlice.ts +++ b/webview/src/setup/state/dvcSlice.ts @@ -8,6 +8,7 @@ export type DvcState = Omit< | 'needsGitCommit' | 'sectionCollapsed' | 'shareLiveToStudio' + | 'selfHostedStudioUrl' > export const dvcInitialState: DvcState = { diff --git a/webview/src/setup/state/studioSlice.ts b/webview/src/setup/state/studioSlice.ts index f2ce1628ce..89d3188fb0 100644 --- a/webview/src/setup/state/studioSlice.ts +++ b/webview/src/setup/state/studioSlice.ts @@ -3,11 +3,12 @@ import { SetupData } from 'dvc/src/setup/webview/contract' export type StudioState = Pick< SetupData, - 'shareLiveToStudio' | 'isStudioConnected' + 'shareLiveToStudio' | 'isStudioConnected' | 'selfHostedStudioUrl' > export const studioInitialState: StudioState = { isStudioConnected: false, + selfHostedStudioUrl: null, shareLiveToStudio: false } @@ -18,13 +19,22 @@ export const studioSlice = createSlice({ updateIsStudioConnected: (state, action: PayloadAction) => { state.isStudioConnected = action.payload }, + updateSelfHostedStudioUrl: ( + state, + action: PayloadAction + ) => { + state.selfHostedStudioUrl = action.payload + }, updateShareLiveToStudio: (state, action: PayloadAction) => { state.shareLiveToStudio = action.payload } } }) -export const { updateIsStudioConnected, updateShareLiveToStudio } = - studioSlice.actions +export const { + updateIsStudioConnected, + updateShareLiveToStudio, + updateSelfHostedStudioUrl +} = studioSlice.actions export default studioSlice.reducer diff --git a/webview/src/setup/util/messages.ts b/webview/src/setup/util/messages.ts index 58d6e901ff..04333aa5ad 100644 --- a/webview/src/setup/util/messages.ts +++ b/webview/src/setup/util/messages.ts @@ -48,6 +48,9 @@ export const showExperiments = () => { export const saveStudioToken = () => sendMessage({ type: MessageFromWebviewType.SAVE_STUDIO_TOKEN }) +export const saveStudioUrl = () => + sendMessage({ type: MessageFromWebviewType.SAVE_STUDIO_URL }) + export const setStudioShareExperimentsLive = (shouldShareLive: boolean) => sendMessage({ payload: shouldShareLive, @@ -57,6 +60,9 @@ export const setStudioShareExperimentsLive = (shouldShareLive: boolean) => export const removeStudioToken = () => sendMessage({ type: MessageFromWebviewType.REMOVE_STUDIO_TOKEN }) +export const removeStudioUrl = () => + sendMessage({ type: MessageFromWebviewType.REMOVE_STUDIO_URL }) + export const requestStudioToken = () => sendMessage({ type: MessageFromWebviewType.REQUEST_STUDIO_TOKEN }) diff --git a/webview/src/stories/Setup.stories.tsx b/webview/src/stories/Setup.stories.tsx index 9c10a44791..6900d9e5e5 100644 --- a/webview/src/stories/Setup.stories.tsx +++ b/webview/src/stories/Setup.stories.tsx @@ -32,6 +32,7 @@ const DEFAULT_DATA: SetupData = { [SetupSection.REMOTES]: false, [SetupSection.STUDIO]: true }, + selfHostedStudioUrl: null, shareLiveToStudio: false } From 2a002977737ac4c85326f7a8d74f91a507a67db8 Mon Sep 17 00:00:00 2001 From: julieg18 Date: Fri, 8 Dec 2023 16:13:42 -0600 Subject: [PATCH 3/7] Add frontend tests --- webview/src/setup/components/App.test.tsx | 52 +++++++++++++++++++ .../src/setup/components/studio/Studio.tsx | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/webview/src/setup/components/App.test.tsx b/webview/src/setup/components/App.test.tsx index c99d3debb6..2f7bc378f9 100644 --- a/webview/src/setup/components/App.test.tsx +++ b/webview/src/setup/components/App.test.tsx @@ -676,6 +676,40 @@ describe('App', () => { within(iconWrapper).getByTestId(TooltipIconType.WARNING) ).toBeInTheDocument() }) + + it('should show the self hosted url with actions to change it if the user has set one', () => { + const selfHostedUrl = 'https://studio.example.com' + renderApp({ selfHostedStudioUrl: selfHostedUrl }) + + const urlDetails = screen.getByTestId('studio-url-details') + + expect( + within(urlDetails).getByText('Self-Hosted Url:') + ).toBeInTheDocument() + expect(within(urlDetails).getByText(selfHostedUrl)).toBeInTheDocument() + + const updateUrlBtn = within(urlDetails).getByText('Update') + const removeUrlBtn = within(urlDetails).getByText('Remove') + + expect(updateUrlBtn).toBeInTheDocument() + expect(removeUrlBtn).toBeInTheDocument() + + mockPostMessage.mockClear() + fireEvent.click(updateUrlBtn) + + expect(mockPostMessage).toHaveBeenCalledTimes(1) + expect(mockPostMessage).toHaveBeenCalledWith({ + type: MessageFromWebviewType.SAVE_STUDIO_URL + }) + + mockPostMessage.mockClear() + fireEvent.click(removeUrlBtn) + + expect(mockPostMessage).toHaveBeenCalledTimes(1) + expect(mockPostMessage).toHaveBeenCalledWith({ + type: MessageFromWebviewType.REMOVE_STUDIO_URL + }) + }) }) describe('Studio connected', () => { @@ -719,6 +753,24 @@ describe('App', () => { within(iconWrapper).getByTestId(TooltipIconType.PASSED) ).toBeInTheDocument() }) + + it('should show the self hosted url with actions to change it if the user has set one', () => { + const selfHostedUrl = 'https://studio.example.com' + renderApp({ selfHostedStudioUrl: selfHostedUrl }) + + const urlDetails = screen.getByTestId('studio-url-details') + + expect( + within(urlDetails).getByText('Self-Hosted Url:') + ).toBeInTheDocument() + expect(within(urlDetails).getByText(selfHostedUrl)).toBeInTheDocument() + + const updateUrlBtn = within(urlDetails).getByText('Update') + const removeUrlBtn = within(urlDetails).getByText('Remove') + + expect(updateUrlBtn).toBeInTheDocument() + expect(removeUrlBtn).toBeInTheDocument() + }) }) describe('focused section', () => { diff --git a/webview/src/setup/components/studio/Studio.tsx b/webview/src/setup/components/studio/Studio.tsx index d45117a1ac..a032398df6 100644 --- a/webview/src/setup/components/studio/Studio.tsx +++ b/webview/src/setup/components/studio/Studio.tsx @@ -28,7 +28,7 @@ export const Studio: React.FC<{ } const children = selfHostedStudioUrl && ( - + Date: Fri, 8 Dec 2023 18:50:08 -0600 Subject: [PATCH 4/7] add vscode tests --- extension/src/setup/index.ts | 5 +- extension/src/setup/studio.ts | 5 ++ extension/src/test/suite/setup/index.test.ts | 70 ++++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/extension/src/setup/index.ts b/extension/src/setup/index.ts index ff49fd532b..6e37fcf782 100644 --- a/extension/src/setup/index.ts +++ b/extension/src/setup/index.ts @@ -3,7 +3,6 @@ import { Event, EventEmitter } from 'vscode' import { Disposable, Disposer } from '@hediet/std/disposable' import isEmpty from 'lodash.isempty' import { - DEFAULT_STUDIO_URL, DvcCliDetails, SetupSection, SetupData as TSetupData @@ -422,8 +421,6 @@ export class Setup const isPythonEnvironmentGlobal = isPythonExtensionUsed && (await isActivePythonEnvGlobal()) - const studioUrl = this.studio.getStudioUrl() - this.webviewMessages.sendWebviewMessage({ canGitInitialize, cliCompatible: this.getCliCompatible(), @@ -440,7 +437,7 @@ export class Setup pythonBinPath: getBinDisplayText(pythonBinPath), remoteList, sectionCollapsed: collectSectionCollapsed(this.focusedSection), - selfHostedStudioUrl: studioUrl === DEFAULT_STUDIO_URL ? null : studioUrl, + selfHostedStudioUrl: this.studio.getSelfHostedStudioUrl(), shareLiveToStudio: !!this.studio.getShareLiveToStudio() }) this.focusedSection = undefined diff --git a/extension/src/setup/studio.ts b/extension/src/setup/studio.ts index ea91c19cc2..17df0fa119 100644 --- a/extension/src/setup/studio.ts +++ b/extension/src/setup/studio.ts @@ -58,6 +58,11 @@ export class Studio extends Disposable { return this.studioUrl } + public getSelfHostedStudioUrl() { + const url = this.getStudioUrl() + return url === DEFAULT_STUDIO_URL ? null : url + } + public removeStudioAccessToken(dvcRoots: string[]) { return this.removeKeyFromConfig(dvcRoots, ConfigKey.STUDIO_TOKEN) } diff --git a/extension/src/test/suite/setup/index.test.ts b/extension/src/test/suite/setup/index.test.ts index 8bf2b24484..f093f41e46 100644 --- a/extension/src/test/suite/setup/index.test.ts +++ b/extension/src/test/suite/setup/index.test.ts @@ -395,6 +395,7 @@ suite('Setup Test Suite', () => { pythonBinPath: undefined, remoteList: undefined, sectionCollapsed: undefined, + selfHostedStudioUrl: null, shareLiveToStudio: false }) }).timeout(WEBVIEW_TEST_TIMEOUT) @@ -441,6 +442,7 @@ suite('Setup Test Suite', () => { pythonBinPath: undefined, remoteList: undefined, sectionCollapsed: undefined, + selfHostedStudioUrl: null, shareLiveToStudio: true }) }).timeout(WEBVIEW_TEST_TIMEOUT) @@ -495,6 +497,7 @@ suite('Setup Test Suite', () => { pythonBinPath: undefined, remoteList: undefined, sectionCollapsed: undefined, + selfHostedStudioUrl: null, shareLiveToStudio: true }) }).timeout(WEBVIEW_TEST_TIMEOUT) @@ -549,6 +552,7 @@ suite('Setup Test Suite', () => { pythonBinPath: undefined, remoteList: { [dvcDemoPath]: undefined }, sectionCollapsed: undefined, + selfHostedStudioUrl: null, shareLiveToStudio: true }) }).timeout(WEBVIEW_TEST_TIMEOUT) @@ -1332,6 +1336,72 @@ suite('Setup Test Suite', () => { ) }) + it("should handle a message from the webview to update the user's self hosted url", async () => { + const { setup, mockExecuteCommand } = buildSetup({ + disposer: disposable + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + stub(Setup.prototype as any, 'getCliCompatible').returns(true) + + mockExecuteCommand.restore() + const executeCommandSpy = spy(commands, 'executeCommand') + + const webview = await setup.showWebview() + await webview.isReady() + + const inputEvent = new Promise(resolve => + stub(window, 'showInputBox').callsFake(() => { + resolve(undefined) + return Promise.resolve(undefined) + }) + ) + const mockMessageReceived = getMessageReceivedEmitter(webview) + + mockMessageReceived.fire({ + type: MessageFromWebviewType.SAVE_STUDIO_URL + }) + + await inputEvent + + expect(executeCommandSpy).to.be.calledWithExactly( + RegisteredCommands.UPDATE_STUDIO_URL + ) + }).timeout(WEBVIEW_TEST_TIMEOUT) + + it("should handle a message from the webview to remove the user's self hosted url", async () => { + const { setup, mockExecuteCommand } = buildSetup({ + disposer: disposable + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + stub(Setup.prototype as any, 'getCliCompatible').returns(true) + + mockExecuteCommand.restore() + const executeCommandSpy = spy(commands, 'executeCommand') + + const webview = await setup.showWebview() + await webview.isReady() + + const configCalled = new Promise(resolve => + stub(DvcConfig.prototype, 'config').callsFake(() => { + resolve(undefined) + return Promise.resolve(undefined) + }) + ) + const mockMessageReceived = getMessageReceivedEmitter(webview) + + mockMessageReceived.fire({ + type: MessageFromWebviewType.REMOVE_STUDIO_URL + }) + + await configCalled + + expect(executeCommandSpy).to.be.calledWithExactly( + RegisteredCommands.REMOVE_STUDIO_URL + ) + }).timeout(WEBVIEW_TEST_TIMEOUT) + it('should check if experiments and dvc are setup', async () => { const { setup } = buildSetup({ disposer: disposable, From c9a2215d0a3070bc7c049bf99fc250f83ee8c2c1 Mon Sep 17 00:00:00 2001 From: julieg18 Date: Mon, 11 Dec 2023 09:27:04 -0600 Subject: [PATCH 5/7] Clean up tests --- extension/src/test/suite/setup/index.test.ts | 30 +++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/extension/src/test/suite/setup/index.test.ts b/extension/src/test/suite/setup/index.test.ts index f093f41e46..843ea54a9f 100644 --- a/extension/src/test/suite/setup/index.test.ts +++ b/extension/src/test/suite/setup/index.test.ts @@ -1344,27 +1344,25 @@ suite('Setup Test Suite', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any stub(Setup.prototype as any, 'getCliCompatible').returns(true) - mockExecuteCommand.restore() - const executeCommandSpy = spy(commands, 'executeCommand') - const webview = await setup.showWebview() await webview.isReady() - const inputEvent = new Promise(resolve => - stub(window, 'showInputBox').callsFake(() => { + const mockMessageReceived = getMessageReceivedEmitter(webview) + + const commandCalled = new Promise(resolve => + mockExecuteCommand.callsFake(() => { resolve(undefined) return Promise.resolve(undefined) }) ) - const mockMessageReceived = getMessageReceivedEmitter(webview) mockMessageReceived.fire({ type: MessageFromWebviewType.SAVE_STUDIO_URL }) - await inputEvent + await commandCalled - expect(executeCommandSpy).to.be.calledWithExactly( + expect(mockExecuteCommand).to.be.calledWithExactly( RegisteredCommands.UPDATE_STUDIO_URL ) }).timeout(WEBVIEW_TEST_TIMEOUT) @@ -1377,28 +1375,26 @@ suite('Setup Test Suite', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any stub(Setup.prototype as any, 'getCliCompatible').returns(true) - mockExecuteCommand.restore() - const executeCommandSpy = spy(commands, 'executeCommand') - const webview = await setup.showWebview() await webview.isReady() - const configCalled = new Promise(resolve => - stub(DvcConfig.prototype, 'config').callsFake(() => { + const commandCalled = new Promise(resolve => + mockExecuteCommand.callsFake(() => { resolve(undefined) return Promise.resolve(undefined) }) ) + const mockMessageReceived = getMessageReceivedEmitter(webview) mockMessageReceived.fire({ - type: MessageFromWebviewType.REMOVE_STUDIO_URL + type: MessageFromWebviewType.SAVE_STUDIO_URL }) - await configCalled + await commandCalled - expect(executeCommandSpy).to.be.calledWithExactly( - RegisteredCommands.REMOVE_STUDIO_URL + expect(mockExecuteCommand).to.be.calledWithExactly( + RegisteredCommands.UPDATE_STUDIO_URL ) }).timeout(WEBVIEW_TEST_TIMEOUT) From 3275b9a14d26e364811ce5032b7a6dafee1b340b Mon Sep 17 00:00:00 2001 From: julieg18 Date: Mon, 11 Dec 2023 09:37:18 -0600 Subject: [PATCH 6/7] Add storybook --- webview/src/stories/Setup.stories.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webview/src/stories/Setup.stories.tsx b/webview/src/stories/Setup.stories.tsx index 6900d9e5e5..75db364aca 100644 --- a/webview/src/stories/Setup.stories.tsx +++ b/webview/src/stories/Setup.stories.tsx @@ -100,6 +100,11 @@ CompletedConnected.args = getUpdatedArgs({ shareLiveToStudio: true }) +export const WithSelfHostedStudioUrl = Template.bind({}) +WithSelfHostedStudioUrl.args = getUpdatedArgs({ + selfHostedStudioUrl: 'https://studio.example.com' +}) + export const CLIPythonNotFound = Template.bind({}) CLIPythonNotFound.args = getUpdatedArgs({ cliCompatible: undefined, From e3148ee23a0e80038cbbc8183f6ca859742478a0 Mon Sep 17 00:00:00 2001 From: julieg18 Date: Tue, 12 Dec 2023 08:32:20 -0600 Subject: [PATCH 7/7] Keep section at all times --- webview/src/setup/components/App.test.tsx | 27 +++++++++++++++++- .../src/setup/components/studio/Studio.tsx | 28 +++++++++++-------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/webview/src/setup/components/App.test.tsx b/webview/src/setup/components/App.test.tsx index 2f7bc378f9..c21777ab89 100644 --- a/webview/src/setup/components/App.test.tsx +++ b/webview/src/setup/components/App.test.tsx @@ -710,6 +710,31 @@ describe('App', () => { type: MessageFromWebviewType.REMOVE_STUDIO_URL }) }) + + it('should show the self hosted url with "Not found" with an action to add one if the user has not set one', () => { + renderApp() + + const urlDetails = screen.getByTestId('studio-url-details') + + expect( + within(urlDetails).getByText('Self-Hosted Url:') + ).toBeInTheDocument() + expect(within(urlDetails).getByText('Not found')).toBeInTheDocument() + + const addUrlBtn = within(urlDetails).getByText('Add Url') + + expect(addUrlBtn).toBeInTheDocument() + + mockPostMessage.mockClear() + fireEvent.click(addUrlBtn) + + expect(mockPostMessage).toHaveBeenCalledTimes(1) + expect(mockPostMessage).toHaveBeenCalledWith({ + type: MessageFromWebviewType.SAVE_STUDIO_URL + }) + + mockPostMessage.mockClear() + }) }) describe('Studio connected', () => { @@ -756,7 +781,7 @@ describe('App', () => { it('should show the self hosted url with actions to change it if the user has set one', () => { const selfHostedUrl = 'https://studio.example.com' - renderApp({ selfHostedStudioUrl: selfHostedUrl }) + renderApp({ isStudioConnected: true, selfHostedStudioUrl: selfHostedUrl }) const urlDetails = screen.getByTestId('studio-url-details') diff --git a/webview/src/setup/components/studio/Studio.tsx b/webview/src/setup/components/studio/Studio.tsx index a032398df6..19de6b9548 100644 --- a/webview/src/setup/components/studio/Studio.tsx +++ b/webview/src/setup/components/studio/Studio.tsx @@ -27,21 +27,25 @@ export const Studio: React.FC<{ ) } - const children = selfHostedStudioUrl && ( + const children = ( )