From ed5dd880a795c7a05685a7a2a42eaa2deb8a03dd Mon Sep 17 00:00:00 2001 From: Matt Seddon Date: Tue, 7 Feb 2023 12:23:52 +1100 Subject: [PATCH 01/18] add prototype for very simple setup of a Studio token --- extension/package.json | 15 ++++++++ extension/src/extension.ts | 11 +++++- extension/src/webview/contract.ts | 2 +- webview/.eslintrc.js | 1 + webview/src/connect/components/App.tsx | 4 ++ webview/src/connect/components/Studio.tsx | 45 +++++++++++++++++++++++ webview/src/connect/index.tsx | 7 ++++ webview/src/stories/Studio.stories.tsx | 20 ++++++++++ webview/webpack.config.ts | 1 + 9 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 webview/src/connect/components/App.tsx create mode 100644 webview/src/connect/components/Studio.tsx create mode 100644 webview/src/connect/index.tsx create mode 100644 webview/src/stories/Studio.stories.tsx diff --git a/extension/package.json b/extension/package.json index 376119e171..63d8053998 100644 --- a/extension/package.json +++ b/extension/package.json @@ -88,6 +88,11 @@ } ], "commands": [ + { + "title": "Connect to Studio", + "command": "dvc.studioConnect", + "category": "DVC" + }, { "title": "%command.addExperimentsTableFilter%", "command": "dvc.addExperimentsTableFilter", @@ -1319,6 +1324,11 @@ "name": "Views", "when": "true" }, + { + "id": "dvc.views.studio", + "name": "Studio", + "when": "true" + }, { "id": "dvc.views.experimentsColumnsTree", "name": "Columns", @@ -1356,6 +1366,11 @@ "contents": "[Show Experiments](command:dvc.showExperiments)\n[Show Plots](command:dvc.showPlots)\n[Show Experiments and Plots](command:dvc.showExperimentsAndPlots)", "when": "dvc.commands.available && dvc.project.available && dvc.project.hasData" }, + { + "view": "dvc.views.studio", + "contents": "[$(plug) Connect and Collaborate](command:dvc.studioConnect)", + "when": "true" + }, { "view": "dvc.views.webviews", "contents": "[Show Experiments](command:dvc.showSetup)\n[Show Plots](command:dvc.showSetup)\n[Show Experiments and Plots](command:dvc.showSetup)", diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 530e453164..89c46161e7 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -3,7 +3,8 @@ import { env, EventEmitter, ExtensionContext, - ViewColumn + ViewColumn, + window } from 'vscode' import { DvcExecutor } from './cli/dvc/executor' import { DvcRunner } from './cli/dvc/runner' @@ -77,6 +78,14 @@ export class Extension extends Disposable { const stopWatch = new StopWatch() + this.dispose.track( + commands.registerCommand('dvc.studioConnect', () => + window.showInformationMessage('Not implemented', { + modal: true + }) + ) + ) + this.dispose.track(getTelemetryReporter()) this.resourceLocator = this.dispose.track( diff --git a/extension/src/webview/contract.ts b/extension/src/webview/contract.ts index 6b7ddbc8ba..e6af63d0cb 100644 --- a/extension/src/webview/contract.ts +++ b/extension/src/webview/contract.ts @@ -8,7 +8,7 @@ import { } from '../plots/webview/contract' import { SetupData } from '../setup/webview/contract' -export type WebviewData = TableData | PlotsData | SetupData +export type WebviewData = TableData | PlotsData | SetupData | {} export enum MessageFromWebviewType { INITIALIZED = 'initialized', diff --git a/webview/.eslintrc.js b/webview/.eslintrc.js index 250f453848..308dfa66be 100644 --- a/webview/.eslintrc.js +++ b/webview/.eslintrc.js @@ -22,6 +22,7 @@ module.exports = { }, { files: [ + 'src/connect/index.tsx', 'src/experiments/index.tsx', 'src/plots/index.tsx', 'src/shared/components/icons/index.ts', diff --git a/webview/src/connect/components/App.tsx b/webview/src/connect/components/App.tsx new file mode 100644 index 0000000000..e9b23975f2 --- /dev/null +++ b/webview/src/connect/components/App.tsx @@ -0,0 +1,4 @@ +import React from 'react' +import { Studio } from './Studio' + +export const App: React.FC = () => diff --git a/webview/src/connect/components/Studio.tsx b/webview/src/connect/components/Studio.tsx new file mode 100644 index 0000000000..13c9083346 --- /dev/null +++ b/webview/src/connect/components/Studio.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { EmptyState } from '../../shared/components/emptyState/EmptyState' +import { Button } from '../../shared/components/button/Button' + +export const Studio: React.FC = () => { + return ( + +
+

+ Connect to Studio +

+

To share experiments with collaborators directly from your IDE.

+

+ An{' '} + + access token + {' '} + can be generated from your Studio profile page. +

+
+
+ ) +} diff --git a/webview/src/connect/index.tsx b/webview/src/connect/index.tsx new file mode 100644 index 0000000000..95de9279c6 --- /dev/null +++ b/webview/src/connect/index.tsx @@ -0,0 +1,7 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import '../shared/style.scss' +import { App } from './components/App' + +const root = ReactDOM.createRoot(document.querySelector('#root') as HTMLElement) +root.render() diff --git a/webview/src/stories/Studio.stories.tsx b/webview/src/stories/Studio.stories.tsx new file mode 100644 index 0000000000..42f13805ea --- /dev/null +++ b/webview/src/stories/Studio.stories.tsx @@ -0,0 +1,20 @@ +import { Story, Meta } from '@storybook/react/types-6-0' +import React from 'react' + +import './test-vscode-styles.scss' +import '../shared/style.scss' +import { Studio } from '../connect/components/Studio' + +export default { + args: { + data: {} + }, + component: Studio, + title: 'Setup' +} as Meta + +const Template: Story = () => { + return +} + +export const ConnectToStudio = Template.bind({}) diff --git a/webview/webpack.config.ts b/webview/webpack.config.ts index febb1f758d..68c6cec109 100644 --- a/webview/webpack.config.ts +++ b/webview/webpack.config.ts @@ -24,6 +24,7 @@ export default { devServer, devtool: 'source-map', entry: { + connect: { dependOn: 'react', import: r('src/connect/index.tsx') }, experiments: { dependOn: 'react', import: r('src/experiments/index.tsx') }, plots: { dependOn: 'react', import: r('src/plots/index.tsx') }, react: ['react', 'react-dom'], From efdcceea492e1d3290a3b62ac217a3e042fc9b00 Mon Sep 17 00:00:00 2001 From: Matt Seddon Date: Fri, 10 Feb 2023 15:50:27 +1100 Subject: [PATCH 02/18] wire up new webview --- extension/src/connect/index.ts | 13 +++++++++++++ extension/src/extension.ts | 11 ++++++----- extension/src/telemetry/constants.ts | 8 ++++++++ extension/src/vscode/context.ts | 3 ++- extension/src/webview/constants.ts | 22 +++++++++++++++++++++- extension/src/webview/repository.ts | 2 +- webview/index.d.ts | 1 + webview/index.js | 1 + webview/src/connect/components/App.tsx | 7 ++++++- 9 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 extension/src/connect/index.ts diff --git a/extension/src/connect/index.ts b/extension/src/connect/index.ts new file mode 100644 index 0000000000..a389ac5b90 --- /dev/null +++ b/extension/src/connect/index.ts @@ -0,0 +1,13 @@ +import { Resource } from '../resourceLocator' +import { ViewKey } from '../webview/constants' +import { BaseRepository } from '../webview/repository' + +export class Connect extends BaseRepository { + public readonly viewKey = ViewKey.CONNECT + + constructor(webviewIcon: Resource) { + super('', webviewIcon) + } + + public sendInitialWebviewData(): void {} +} diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 89c46161e7..e04277af0b 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -3,8 +3,7 @@ import { env, EventEmitter, ExtensionContext, - ViewColumn, - window + ViewColumn } from 'vscode' import { DvcExecutor } from './cli/dvc/executor' import { DvcRunner } from './cli/dvc/runner' @@ -53,12 +52,14 @@ import { stopProcesses } from './processExecution' import { Flag } from './cli/dvc/constants' import { LanguageClient } from './languageClient' import { collectRunningExperimentPids } from './experiments/processExecution/collect' +import { Connect } from './connect' export class Extension extends Disposable { protected readonly internalCommands: InternalCommands private readonly resourceLocator: ResourceLocator private readonly repositories: WorkspaceRepositories + private readonly connect: Connect private readonly experiments: WorkspaceExperiments private readonly plots: WorkspacePlots private readonly setup: Setup @@ -80,9 +81,7 @@ export class Extension extends Disposable { this.dispose.track( commands.registerCommand('dvc.studioConnect', () => - window.showInformationMessage('Not implemented', { - modal: true - }) + this.connect.showWebview() ) ) @@ -194,6 +193,8 @@ export class Extension extends Disposable { ) ) + this.connect = this.dispose.track(new Connect(this.resourceLocator.dvcIcon)) + registerExperimentCommands(this.experiments, this.internalCommands) registerPlotsCommands(this.plots, this.internalCommands) this.internalCommands.registerExternalCommand( diff --git a/extension/src/telemetry/constants.ts b/extension/src/telemetry/constants.ts index dc1bbcc98c..1951df4365 100644 --- a/extension/src/telemetry/constants.ts +++ b/extension/src/telemetry/constants.ts @@ -28,6 +28,10 @@ export const EventName = Object.assign( EXTENSION_EXECUTION_DETAILS_CHANGED: 'extension.executionDetails.changed', EXTENSION_LOAD: 'extension.load', + VIEWS_CONNECT_CLOSED: 'views.connect.closed', + VIEWS_CONNECT_CREATED: 'views.connect.created', + VIEWS_CONNECT_FOCUS_CHANGED: 'views.connect.focusChanged', + VIEWS_EXPERIMENTS_TABLE_CLOSED: 'views.experimentsTable.closed', VIEWS_EXPERIMENTS_TABLE_COLUMNS_REORDERED: 'views.experimentsTable.columnsReordered', @@ -207,6 +211,10 @@ export interface IEventNamePropertyMapping { [EventName.EXTENSION_SHOW_COMMANDS]: undefined [EventName.EXTENSION_SHOW_OUTPUT]: undefined + [EventName.VIEWS_CONNECT_CLOSED]: undefined + [EventName.VIEWS_CONNECT_CREATED]: undefined + [EventName.VIEWS_CONNECT_FOCUS_CHANGED]: undefined + [EventName.VIEWS_EXPERIMENTS_TREE_OPENED]: DvcRootCount [EventName.VIEWS_EXPERIMENTS_FILTER_BY_TREE_OPENED]: DvcRootCount [EventName.VIEWS_EXPERIMENTS_METRICS_AND_PARAMS_TREE_OPENED]: DvcRootCount diff --git a/extension/src/vscode/context.ts b/extension/src/vscode/context.ts index 71046850db..51e0e5cd78 100644 --- a/extension/src/vscode/context.ts +++ b/extension/src/vscode/context.ts @@ -1,8 +1,9 @@ import { commands } from 'vscode' export enum ContextKey { - COMMANDS_AVAILABLE = 'dvc.commands.available', CLI_INCOMPATIBLE = 'dvc.cli.incompatible', + COMMANDS_AVAILABLE = 'dvc.commands.available', + CONNECT_WEBVIEW_ACTIVE = 'dvc.connect.webview.active', EXPERIMENT_CHECKPOINTS = 'dvc.experiment.checkpoints', EXPERIMENT_FILTERS_SELECTED = 'dvc.experiments.filter.selected', EXPERIMENTS_WEBVIEW_ACTIVE = 'dvc.experiments.webview.active', diff --git a/extension/src/webview/constants.ts b/extension/src/webview/constants.ts index 762e1c81b2..95526a1395 100644 --- a/extension/src/webview/constants.ts +++ b/extension/src/webview/constants.ts @@ -1,8 +1,16 @@ -import { distPath, react, experiments, plots, setup } from 'dvc-vscode-webview' +import { + distPath, + connect, + react, + experiments, + plots, + setup +} from 'dvc-vscode-webview' import { EventName, IEventNamePropertyMapping } from '../telemetry/constants' import { ContextKey } from '../vscode/context' export enum ViewKey { + CONNECT = 'dvc-connect', EXPERIMENTS = 'dvc-experiments', PLOTS = 'dvc-plots', SETUP = 'dvc-setup' @@ -25,6 +33,18 @@ export const WebviewDetails: { viewKey: ViewKey } } = { + [ViewKey.CONNECT]: { + contextKey: ContextKey.CONNECT_WEBVIEW_ACTIVE, + distPath, + eventNames: { + closedEvent: EventName.VIEWS_CONNECT_CLOSED, + createdEvent: EventName.VIEWS_CONNECT_CREATED, + focusChangedEvent: EventName.VIEWS_CONNECT_FOCUS_CHANGED + }, + scripts: [react, connect], + title: 'Connect', + viewKey: ViewKey.CONNECT + }, [ViewKey.EXPERIMENTS]: { contextKey: ContextKey.EXPERIMENTS_WEBVIEW_ACTIVE, distPath, diff --git a/extension/src/webview/repository.ts b/extension/src/webview/repository.ts index 0b68880d79..2cb8349e04 100644 --- a/extension/src/webview/repository.ts +++ b/extension/src/webview/repository.ts @@ -48,7 +48,7 @@ export abstract class BaseRepository< this.dvcRoot, this.webviewIcon, viewColumn, - this.viewKey === ViewKey.SETUP + [ViewKey.CONNECT, ViewKey.SETUP].includes(this.viewKey) ) this.setWebview(webview) diff --git a/webview/index.d.ts b/webview/index.d.ts index 15d44552d4..f056f41cba 100644 --- a/webview/index.d.ts +++ b/webview/index.d.ts @@ -1,3 +1,4 @@ +export const connect: string export const distPath: string export const experiments: string export const plots: string diff --git a/webview/index.js b/webview/index.js index bfb661e5ab..f45978b6fb 100644 --- a/webview/index.js +++ b/webview/index.js @@ -6,6 +6,7 @@ const path = require('path') module.exports.distPath = path.join(__dirname, 'dist') +module.exports.connect = path.join(__dirname, 'dist/connect.js') module.exports.experiments = path.join(__dirname, 'dist/experiments.js') module.exports.plots = path.join(__dirname, 'dist/plots.js') module.exports.setup = path.join(__dirname, 'dist/setup.js') diff --git a/webview/src/connect/components/App.tsx b/webview/src/connect/components/App.tsx index e9b23975f2..e9951c26db 100644 --- a/webview/src/connect/components/App.tsx +++ b/webview/src/connect/components/App.tsx @@ -1,4 +1,9 @@ import React from 'react' import { Studio } from './Studio' +import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' -export const App: React.FC = () => +export const App: React.FC = () => { + useVsCodeMessaging(() => undefined) + + return +} From 555d6c65b56f4a4b5812c344c0d3ddc4f66999f6 Mon Sep 17 00:00:00 2001 From: Matt Seddon Date: Mon, 13 Feb 2023 12:04:39 +1100 Subject: [PATCH 03/18] wire up new actions --- README.md | 1 + extension/package.json | 6 ++++ extension/package.nls.json | 4 ++- extension/src/connect/index.ts | 43 +++++++++++++++++++++++ extension/src/vscode/config.ts | 5 +-- extension/src/vscode/title.ts | 2 ++ extension/src/webview/contract.ts | 8 ++++- webview/src/connect/components/Studio.tsx | 20 ++++++++--- 8 files changed, 81 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1a808e0eb6..a9a67827bb 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ These are the VS Code [settings] available for the Extension: | **Option** | **Description** | | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dvc.studioAccessToken` | Token used by DVC to share experiments and plots data to [Iterative Studio](https://studio.iterative.ai). | | `dvc.dvcPath` | Path or shell command to the DVC binary. Required unless Microsoft's [Python extension] is installed and the `dvc` package found in its environment. | | `dvc.pythonPath` | Path to the desired Python interpreter to use with DVC. Should only be utilized when using a virtual environment without Microsoft's [Python extension]. | | `dvc.experimentsTableHeadMaxHeight` | Maximum height of experiment table head rows. | diff --git a/extension/package.json b/extension/package.json index 63d8053998..b85d97dd53 100644 --- a/extension/package.json +++ b/extension/package.json @@ -595,6 +595,12 @@ "description": "%config.pythonPath.description%", "type": "string", "default": null + }, + "dvc.studioAccessToken": { + "title": "%config.studioAccessToken.title%", + "description": "%config.studioAccessToken.description%", + "type": "string", + "default": null } } }, diff --git a/extension/package.nls.json b/extension/package.nls.json index 4a5e22a10e..860442903a 100644 --- a/extension/package.nls.json +++ b/extension/package.nls.json @@ -92,5 +92,7 @@ "config.experimentsTableHeadMaxHeight.title": "Maximum height of Experiment table head rows", "config.experimentsTableHeadMaxHeight.description": "Use 0 for infinite height.", "config.pythonPath.description": "Path to the desired Python interpreter to use with DVC. Required when using a virtual environment. Overrides any other extension's settings for this extension's purposes.", - "config.pythonPath.title": "Python Interpreter" + "config.pythonPath.title": "Python Interpreter", + "config.studioAccessToken.description": "Token used by DVC to share experiments and plots data to Iterative Studio.", + "config.studioAccessToken.title": "Studio Access Token" } diff --git a/extension/src/connect/index.ts b/extension/src/connect/index.ts index a389ac5b90..77b58d76f5 100644 --- a/extension/src/connect/index.ts +++ b/extension/src/connect/index.ts @@ -1,13 +1,56 @@ +import { Uri, env } from 'vscode' import { Resource } from '../resourceLocator' import { ViewKey } from '../webview/constants' +import { MessageFromWebview, MessageFromWebviewType } from '../webview/contract' import { BaseRepository } from '../webview/repository' +import { Logger } from '../common/logger' +import { getInput } from '../vscode/inputBox' +import { Title } from '../vscode/title' +import { ConfigKey, setUserConfigValue } from '../vscode/config' export class Connect extends BaseRepository { public readonly viewKey = ViewKey.CONNECT constructor(webviewIcon: Resource) { super('', webviewIcon) + + this.dispose.track( + this.onDidReceivedWebviewMessage(message => + this.handleMessageFromWebview(message) + ) + ) } public sendInitialWebviewData(): void {} + + private handleMessageFromWebview(message: MessageFromWebview) { + switch (message.type) { + case MessageFromWebviewType.OPEN_STUDIO_IN_BROWSER: + return env.openExternal(Uri.parse('https://studio.iterative.ai')) + case MessageFromWebviewType.OPEN_STUDIO_PROFILE: + return this.openStudioProfile() + case MessageFromWebviewType.SAVE_STUDIO_TOKEN: + return this.saveStudioToken() + default: + Logger.error('method not implemented') + } + } + + private async openStudioProfile() { + const username = await getInput(Title.ENTER_STUDIO_USERNAME) + if (!username) { + return + } + return env.openExternal( + Uri.parse(`https://studio.iterative.ai/user/${username}/profile`) + ) + } + + private async saveStudioToken() { + const token = await getInput(Title.ENTER_STUDIO_TOKEN) + if (!token) { + return + } + return setUserConfigValue(ConfigKey.STUDIO_ACCESS_TOKEN, token) + } } diff --git a/extension/src/vscode/config.ts b/extension/src/vscode/config.ts index 5c2e73a787..566431fe3e 100644 --- a/extension/src/vscode/config.ts +++ b/extension/src/vscode/config.ts @@ -5,10 +5,11 @@ export enum ConfigKey { DO_NOT_SHOW_CLI_UNAVAILABLE = 'dvc.doNotShowCliUnavailable', DO_NOT_SHOW_WALKTHROUGH_AFTER_INSTALL = 'dvc.doNotShowWalkthroughAfterInstall', DO_NOT_SHOW_UNABLE_TO_FILTER = 'dvc.doNotShowUnableToFilter', + DVC_PATH = 'dvc.dvcPath', EXP_TABLE_HEAD_MAX_HEIGHT = 'dvc.experimentsTableHeadMaxHeight', FOCUSED_PROJECTS = 'dvc.focusedProjects', - DVC_PATH = 'dvc.dvcPath', - PYTHON_PATH = 'dvc.pythonPath' + PYTHON_PATH = 'dvc.pythonPath', + STUDIO_ACCESS_TOKEN = 'dvc.studioAccessToken' } export const getConfigValue = ( diff --git a/extension/src/vscode/title.ts b/extension/src/vscode/title.ts index 5037f51d7a..4caefeebcd 100644 --- a/extension/src/vscode/title.ts +++ b/extension/src/vscode/title.ts @@ -6,6 +6,8 @@ export enum Title { ENTER_FILTER_VALUE = 'Enter a Filter Value', ENTER_RELATIVE_DESTINATION = 'Enter a Destination Relative to the Root', ENTER_PATH_OR_CHOOSE_FILE = 'Enter the path to your training script or select it', + ENTER_STUDIO_USERNAME = 'Enter your Studio username', + ENTER_STUDIO_TOKEN = 'Enter your Studio access token', ENTER_STAGE_NAME = 'Enter a name for the main stage of your pipeline', GARBAGE_COLLECT_EXPERIMENTS = 'Garbage Collect Experiments', SHOW_SETUP = 'Show Setup', diff --git a/extension/src/webview/contract.ts b/extension/src/webview/contract.ts index e6af63d0cb..fee9df5f2d 100644 --- a/extension/src/webview/contract.ts +++ b/extension/src/webview/contract.ts @@ -17,8 +17,10 @@ export enum MessageFromWebviewType { CREATE_BRANCH_FROM_EXPERIMENT = 'create-branch-from-experiment', FOCUS_FILTERS_TREE = 'focus-filters-tree', FOCUS_SORTS_TREE = 'focus-sorts-tree', - OPEN_PLOTS_WEBVIEW = 'open-plots-webview', OPEN_PARAMS_FILE_TO_THE_SIDE = 'open-params-file-to-the-side', + OPEN_PLOTS_WEBVIEW = 'open-plots-webview', + OPEN_STUDIO_IN_BROWSER = 'open-studio-in-browser', + OPEN_STUDIO_PROFILE = 'open-studio-in-profile', REMOVE_COLUMN_SORT = 'remove-column-sort', REMOVE_EXPERIMENT = 'remove-experiment', REORDER_COLUMNS = 'reorder-columns', @@ -30,6 +32,7 @@ export enum MessageFromWebviewType { REFRESH_REVISIONS = 'refresh-revisions', RESIZE_COLUMN = 'resize-column', RESIZE_PLOTS = 'resize-plots', + SAVE_STUDIO_TOKEN = 'save-studio-token', STOP_EXPERIMENT = 'stop-experiment', SORT_COLUMN = 'sort-column', TOGGLE_EXPERIMENT = 'toggle-experiment', @@ -196,6 +199,9 @@ export type MessageFromWebview = | { type: MessageFromWebviewType.SHOW_SCM_PANEL } | { type: MessageFromWebviewType.INSTALL_DVC } | { type: MessageFromWebviewType.SETUP_WORKSPACE } + | { type: MessageFromWebviewType.OPEN_STUDIO_IN_BROWSER } + | { type: MessageFromWebviewType.OPEN_STUDIO_PROFILE } + | { type: MessageFromWebviewType.SAVE_STUDIO_TOKEN } export type MessageToWebview = { type: MessageToWebviewType.SET_DATA diff --git a/webview/src/connect/components/Studio.tsx b/webview/src/connect/components/Studio.tsx index 13c9083346..5fc427b534 100644 --- a/webview/src/connect/components/Studio.tsx +++ b/webview/src/connect/components/Studio.tsx @@ -1,6 +1,8 @@ import React from 'react' +import { MessageFromWebviewType } from 'dvc/src/webview/contract' import { EmptyState } from '../../shared/components/emptyState/EmptyState' import { Button } from '../../shared/components/button/Button' +import { sendMessage } from '../../shared/vscode' export const Studio: React.FC = () => { return ( @@ -9,7 +11,10 @@ export const Studio: React.FC = () => {

Connect to Studio

-

To share experiments with collaborators directly from your IDE.

+

+ To share experiments and plots with collaborators directly from your + IDE. +

An{' '} @@ -21,22 +26,29 @@ export const Studio: React.FC = () => { appearance="primary" isNested={false} text={'Sign In'} - onClick={() => undefined} + onClick={() => + sendMessage({ type: MessageFromWebviewType.OPEN_STUDIO_IN_BROWSER }) + } />