From 65703fd4b16fe752dfc2f9e627412ea94c08c980 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Wed, 20 Sep 2023 17:14:07 +0100 Subject: [PATCH] INT-1898 Use custom notifs for auth flow (#825) --- intuita-webview/src/codemodList/App.tsx | 8 +- .../CodemodNodeRenderer/Codemod.tsx | 18 +++ intuita-webview/src/main/App.css | 6 + intuita-webview/src/main/App.tsx | 62 ++++++++-- src/components/webview/MainProvider.ts | 15 +++ src/components/webview/webviewEvents.ts | 16 +++ src/data/slice.ts | 4 + src/extension.ts | 114 ++++++++++++------ src/persistedState/codecs.ts | 12 ++ src/selectors/selectMainWebviewViewProps.ts | 4 + 10 files changed, 206 insertions(+), 53 deletions(-) diff --git a/intuita-webview/src/codemodList/App.tsx b/intuita-webview/src/codemodList/App.tsx index 42d758d53..7ebb63183 100644 --- a/intuita-webview/src/codemodList/App.tsx +++ b/intuita-webview/src/codemodList/App.tsx @@ -116,13 +116,7 @@ export const App = memo( { event.preventDefault(); diff --git a/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx b/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx index 037f8e07e..fc4d522a5 100644 --- a/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx +++ b/intuita-webview/src/codemodList/CodemodNodeRenderer/Codemod.tsx @@ -80,6 +80,13 @@ const renderActionButtons = ( }); }; + const handleRemovePrivateCodemod = () => { + vscode.postMessage({ + kind: 'webview.main.removePrivateCodemod', + hashDigest, + }); + }; + return ( <> + {isPrivate && ( + + + + )} ); } @@ -231,10 +247,12 @@ const Codemod = ({ toast.update(progress.codemodHash, { progress: value, render: `Processed ${progress.processedFileNumber} / ${progress.totalFileNumber} files`, + containerId: 'codemodListToastContainer', }); } else { toast(`Processed 0 / ${progress.totalFileNumber} files`, { toastId: progress.codemodHash, + containerId: 'codemodListToastContainer', progress: 0, }); } diff --git a/intuita-webview/src/main/App.css b/intuita-webview/src/main/App.css index 015dfe7a3..d0d4dbcff 100644 --- a/intuita-webview/src/main/App.css +++ b/intuita-webview/src/main/App.css @@ -54,3 +54,9 @@ .vscode-panel-view { padding: 0px; } + +.toasterComponent { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/intuita-webview/src/main/App.tsx b/intuita-webview/src/main/App.tsx index 3fdd80bfc..8e671a05b 100644 --- a/intuita-webview/src/main/App.tsx +++ b/intuita-webview/src/main/App.tsx @@ -1,6 +1,8 @@ +import { toast } from 'react-toastify'; import { useEffect, useRef, useState } from 'react'; import { + VSCodeButton, VSCodePanels, VSCodePanelTab, VSCodePanelView, @@ -18,6 +20,17 @@ import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { useTheme } from '../shared/Snippet/useTheme'; +const toastContainerProps = { + pauseOnHover: false, + pauseOnFocusLoss: false, + hideProgressBar: false, + closeOnClick: false, + closeButton: false, + draggable: false, + autoClose: false as const, + enableMultiContainer: true, +}; + declare global { interface Window { mainWebviewViewProps: MainWebviewViewProps; @@ -76,6 +89,41 @@ function App() { }; }, []); + useEffect(() => { + if (mainWebviewViewProps.toaster === null) { + return; + } + + const { content, ...toasterProps } = mainWebviewViewProps.toaster; + let componentToRender = null; + + if (toasterProps.toastId === 'handleSignedInUser') { + componentToRender = ( +
+

{content}

+ { + toast.dismiss(toasterProps.toastId); + vscode.postMessage({ + kind: 'webview.main.signOut', + }); + }} + > + Sign out + +
+ ); + } + toast(componentToRender ?? content, toasterProps); + + // remove the current toaster props from Redux state + vscode.postMessage({ + kind: 'webview.main.setToaster', + value: null, + }); + }, [mainWebviewViewProps.toaster]); + const handlePanelTabClick = (id: ActiveTabId) => { vscode.postMessage({ kind: 'webview.main.setActiveTabId', @@ -126,16 +174,10 @@ function App() { /> ) : null} + ); } diff --git a/src/components/webview/MainProvider.ts b/src/components/webview/MainProvider.ts index d5750ee26..888c68a6c 100644 --- a/src/components/webview/MainProvider.ts +++ b/src/components/webview/MainProvider.ts @@ -328,6 +328,21 @@ export class MainViewProvider implements WebviewViewProvider { ); } + if (message.kind === 'webview.main.setToaster') { + this.__store.dispatch(actions.setToaster(message.value)); + } + + if (message.kind === 'webview.main.signOut') { + commands.executeCommand('intuita.signOut'); + } + + if (message.kind === 'webview.main.removePrivateCodemod') { + commands.executeCommand( + 'intuita.removePrivateCodemod', + message.hashDigest, + ); + } + if (message.kind === 'webview.global.flipSelectedExplorerNode') { this.__store.dispatch( actions.flipSelectedExplorerNode([ diff --git a/src/components/webview/webviewEvents.ts b/src/components/webview/webviewEvents.ts index 7e7f50b61..8711a2eb4 100644 --- a/src/components/webview/webviewEvents.ts +++ b/src/components/webview/webviewEvents.ts @@ -128,6 +128,22 @@ export type WebviewResponse = kind: 'webview.main.setActiveTabId'; activeTabId: ActiveTabId; }> + | Readonly<{ + kind: 'webview.main.setToaster'; + value: { + containerId: string; + toastId: string; + content: string; + autoClose: number; + } | null; + }> + | Readonly<{ + kind: 'webview.main.removePrivateCodemod'; + hashDigest: CodemodNodeHashDigest; + }> + | Readonly<{ + kind: 'webview.main.signOut'; + }> | Readonly<{ kind: | 'webview.main.setCodemodRunsPanelGroupSettings' diff --git a/src/data/slice.ts b/src/data/slice.ts index 2be45074d..d090c4e67 100644 --- a/src/data/slice.ts +++ b/src/data/slice.ts @@ -90,6 +90,7 @@ export const getInitialState = (): RootState => { collapsedExplorerNodes: {}, reviewedExplorerNodes: {}, focusedExplorerNodes: {}, + toaster: null, }; }; @@ -664,6 +665,9 @@ const rootSlice = createSlice({ ) { state.sourceControl = action.payload; }, + setToaster(state, action: PayloadAction) { + state.toaster = action.payload; + }, collapseResultsPanel(state, action: PayloadAction) { state.codemodRunsTab.resultsCollapsed = action.payload; }, diff --git a/src/extension.ts b/src/extension.ts index db2291247..ae73f0711 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -217,25 +217,35 @@ export async function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push( - vscode.commands.registerCommand( - 'intuita.handleSignedInUser', - async () => { - const decision = await vscode.window.showInformationMessage( - 'You are already signed-in.', - 'Do you want to sign out?', - ); + vscode.commands.registerCommand('intuita.signOut', () => { + userService.unlinkUserIntuitaAccount(); + vscode.commands.executeCommand( + 'setContext', + 'intuita.signedIn', + false, + ); + store.dispatch( + actions.setToaster({ + toastId: 'signOut', + containerId: 'primarySidebarToastContainer', + content: 'Signed out', + autoClose: 3000, + }), + ); + }), + ); - if (decision === 'Do you want to sign out?') { - userService.unlinkUserIntuitaAccount(); - vscode.commands.executeCommand( - 'setContext', - 'intuita.signedIn', - false, - ); - vscode.window.showInformationMessage('You are signed out.'); - } - }, - ), + context.subscriptions.push( + vscode.commands.registerCommand('intuita.handleSignedInUser', () => { + store.dispatch( + actions.setToaster({ + toastId: 'handleSignedInUser', + containerId: 'primarySidebarToastContainer', + content: 'Already signed-in', + autoClose: 5000, + }), + ); + }), ); context.subscriptions.push( @@ -850,27 +860,48 @@ export async function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push( - vscode.commands.registerCommand('intuita.clearPrivateCodemods', () => { - const state = store.getState(); - const hashDigests = state.privateCodemods.ids as CodemodHash[]; - hashDigests.forEach((hashDigest) => { - const codemodPath = join(homedir(), '.intuita', hashDigest); - if (existsSync(codemodPath)) { - rmSync(codemodPath, { recursive: true, force: true }); - } - }); + vscode.commands.registerCommand( + 'intuita.removePrivateCodemod', + (arg0: unknown) => { + try { + const hashDigest: string | null = + typeof arg0 === 'string' ? arg0 : null; - const codemodNamesPath = join( - homedir(), - '.intuita', - 'privateCodemodNames.json', - ); - if (existsSync(codemodNamesPath)) { - rmSync(codemodNamesPath); - } + if (hashDigest === null) { + throw new Error( + 'Did not pass the hashDigest into the command.', + ); + } + const codemodPath = join(homedir(), '.intuita', hashDigest); + if (existsSync(codemodPath)) { + rmSync(codemodPath, { recursive: true, force: true }); + } - store.dispatch(actions.removePrivateCodemods(hashDigests)); - }), + const codemodNamesPath = join( + homedir(), + '.intuita', + 'privateCodemodNames.json', + ); + if (existsSync(codemodNamesPath)) { + rmSync(codemodNamesPath); + } + + store.dispatch( + actions.removePrivateCodemods([ + hashDigest as CodemodHash, + ]), + ); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + vscode.window.showErrorMessage(message); + + vscodeTelemetry.sendError({ + kind: 'failedToExecuteCommand', + commandName: 'intuita.removePrivateCodemod', + }); + } + }, + ), ); context.subscriptions.push( @@ -1140,8 +1171,13 @@ export async function activate(context: vscode.ExtensionContext) { 'intuita.signedIn', true, ); - vscode.window.showInformationMessage( - 'You are successfully signed in.', + store.dispatch( + actions.setToaster({ + toastId: 'signIn', + containerId: 'primarySidebarToastContainer', + content: 'Successfully signed in', + autoClose: 3000, + }), ); } else { await routeUserToStudioToAuthenticate(); diff --git a/src/persistedState/codecs.ts b/src/persistedState/codecs.ts index 399880dbd..3ff807a5a 100644 --- a/src/persistedState/codecs.ts +++ b/src/persistedState/codecs.ts @@ -147,6 +147,18 @@ export const persistedStateCodecNew = buildTypeCodec({ kind: 'IDLENESS', }, ), + toaster: withFallback( + t.union([ + buildTypeCodec({ + containerId: t.string, + toastId: t.string, + content: t.string, + autoClose: t.number, + }), + t.null, + ]), + null, + ), caseHashJobHashes: withFallback(t.readonlyArray(t.string), []), caseHashInProgress: withFallback(t.union([caseHashCodec, t.null]), null), applySelectedInProgress: withFallback(t.boolean, false), diff --git a/src/selectors/selectMainWebviewViewProps.ts b/src/selectors/selectMainWebviewViewProps.ts index 50176c3b5..e48d55a94 100644 --- a/src/selectors/selectMainWebviewViewProps.ts +++ b/src/selectors/selectMainWebviewViewProps.ts @@ -15,6 +15,7 @@ export const selectMainWebviewViewProps = ( if (state.activeTabId === 'codemods') { return { activeTabId: state.activeTabId, + toaster: state.toaster, searchPhrase: state.codemodDiscoveryView.searchPhrase, autocompleteItems, codemodTree: selectCodemodTree( @@ -39,6 +40,7 @@ export const selectMainWebviewViewProps = ( if (state.activeTabId === 'codemodRuns') { return { activeTabId: state.activeTabId, + toaster: state.toaster, applySelectedInProgress: state.applySelectedInProgress, codemodRunsTree: rootUri !== null @@ -61,6 +63,7 @@ export const selectMainWebviewViewProps = ( return { activeTabId: state.activeTabId, + toaster: state.toaster, title: sourceControlTabProps?.title ?? '', body: sourceControlTabProps?.body ?? '', loading: sourceControlTabProps?.loading ?? false, @@ -69,6 +72,7 @@ export const selectMainWebviewViewProps = ( return { activeTabId: state.activeTabId, + toaster: state.toaster, }; };