From 36790f976f0cee52bb4d3ecb4ab3eb08c83a1691 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?=
Date: Mon, 28 Sep 2020 21:03:38 +0200
Subject: [PATCH] Refactor user feedbacks (#777)
* Extract the task handler from the model
Refactor user logger
* Avoid focus capture by suspend modal
* Propagate error not related to authentication
---
package.json | 3 +
src/commandsAndMenu.tsx | 225 +++++--
src/components/Alert.tsx | 34 +-
src/components/BranchMenu.tsx | 172 ++---
src/components/Feedback.tsx | 132 ++++
src/components/GitPanel.tsx | 202 ++----
src/components/HistorySideBar.tsx | 6 -
src/components/NewBranchDialog.tsx | 166 +----
src/components/PastCommitNode.tsx | 6 -
src/components/ResetRevertDialog.tsx | 253 ++------
src/components/SinglePastCommitInfo.tsx | 26 +-
src/components/SuspendModal.tsx | 7 +-
src/components/Toolbar.tsx | 179 ++----
src/index.ts | 2 +-
src/logger.ts | 40 ++
src/model.ts | 589 ++++++++----------
src/taskhandler.ts | 111 ++++
src/tokens.ts | 27 +-
src/widgets/GitCloneForm.ts | 36 ++
src/widgets/GitWidget.tsx | 42 +-
src/widgets/StatusWidget.ts | 8 +-
src/widgets/gitClone.tsx | 136 +---
src/widgets/gitPushPull.ts | 119 ----
tests/test-components/BranchMenu.spec.tsx | 35 +-
tests/test-components/GitPanel.spec.tsx | 2 +
tests/test-components/HistorySideBar.spec.tsx | 3 +-
tests/test-components/PastCommitNode.spec.tsx | 3 +-
tests/test-components/Toolbar.spec.tsx | 37 +-
28 files changed, 1117 insertions(+), 1484 deletions(-)
create mode 100644 src/components/Feedback.tsx
create mode 100644 src/logger.ts
create mode 100644 src/taskhandler.ts
create mode 100644 src/widgets/GitCloneForm.ts
delete mode 100644 src/widgets/gitPushPull.ts
diff --git a/package.json b/package.json
index 657ae4bda..ac1da94ee 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,10 @@
"@jupyterlab/terminal": "^2.0.0",
"@jupyterlab/ui-components": "^2.0.0",
"@lumino/collections": "^1.2.3",
+ "@lumino/commands": "^1.11.0",
+ "@lumino/coreutils": "^1.5.0",
"@lumino/polling": "^1.0.4",
+ "@lumino/signaling": "^1.4.0",
"@lumino/widgets": "^1.11.1",
"@material-ui/core": "^4.8.2",
"@material-ui/icons": "^4.5.1",
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index b8d506d6b..2e3479643 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -21,12 +21,13 @@ import {
RenderMimeProvider
} from './components/diff/Diff';
import { getRefValue, IDiffContext } from './components/diff/model';
+import { AUTH_ERROR_MESSAGES } from './git';
+import { logger } from './logger';
import { GitExtension } from './model';
import { diffIcon } from './style/icons';
-import { Git } from './tokens';
+import { Git, Level } from './tokens';
import { GitCredentialsForm } from './widgets/CredentialsBox';
-import { doGitClone } from './widgets/gitClone';
-import { GitPullPushDialog, Operation } from './widgets/gitPushPull';
+import { GitCloneForm } from './widgets/GitCloneForm';
const RESOURCES = [
{
@@ -39,6 +40,26 @@ const RESOURCES = [
}
];
+interface IGitCloneArgs {
+ /**
+ * Path in which to clone the Git repository
+ */
+ path: string;
+ /**
+ * Git repository url
+ */
+ url: string;
+}
+
+/**
+ * Git operations requiring authentication
+ */
+enum Operation {
+ Clone = 'Clone',
+ Pull = 'Pull',
+ Push = 'Push'
+}
+
/**
* The command IDs used by the git plugin.
*/
@@ -133,8 +154,28 @@ export function addCommands(
});
if (result.button.accept) {
- await model.init(currentPath);
- model.pathRepository = currentPath;
+ logger.log({
+ message: 'Initializing...',
+ level: Level.RUNNING
+ });
+ try {
+ await model.init(currentPath);
+ model.pathRepository = currentPath;
+ logger.log({
+ message: 'Git repository initialized.',
+ level: Level.SUCCESS
+ });
+ } catch (error) {
+ console.error(
+ 'Encountered an error when initializing the repository. Error: ',
+ error
+ );
+ logger.log({
+ message: 'Failed to initialize the Git repository',
+ level: Level.ERROR,
+ error
+ });
+ }
}
},
isEnabled: () => model.pathRepository === null
@@ -208,8 +249,41 @@ export function addCommands(
caption: 'Clone a repository from a URL',
isEnabled: () => model.pathRepository === null,
execute: async () => {
- await doGitClone(model, fileBrowser.model.path);
- fileBrowser.model.refresh();
+ const result = await showDialog({
+ title: 'Clone a repo',
+ body: new GitCloneForm(),
+ focusNodeSelector: 'input',
+ buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'CLONE' })]
+ });
+
+ if (result.button.accept && result.value) {
+ logger.log({
+ level: Level.RUNNING,
+ message: 'Cloning...'
+ });
+ try {
+ await Private.showGitOperationDialog(
+ model,
+ Operation.Clone,
+ { path: fileBrowser.model.path, url: result.value }
+ );
+ logger.log({
+ message: 'Successfully cloned',
+ level: Level.SUCCESS
+ });
+ await fileBrowser.model.refresh();
+ } catch (error) {
+ console.error(
+ 'Encountered an error when cloning the repository. Error: ',
+ error
+ );
+ logger.log({
+ message: 'Failed to clone',
+ level: Level.ERROR,
+ error
+ });
+ }
+ }
}
});
@@ -229,13 +303,27 @@ export function addCommands(
caption: 'Push code to remote repository',
isEnabled: () => model.pathRepository !== null,
execute: async () => {
- await Private.showGitOperationDialog(model, Operation.Push).catch(
- reason => {
- console.error(
- `Encountered an error when pushing changes. Error: ${reason}`
- );
- }
- );
+ logger.log({
+ level: Level.RUNNING,
+ message: 'Pushing...'
+ });
+ try {
+ await Private.showGitOperationDialog(model, Operation.Push);
+ logger.log({
+ message: 'Successfully pushed',
+ level: Level.SUCCESS
+ });
+ } catch (error) {
+ console.error(
+ 'Encountered an error when pushing changes. Error: ',
+ error
+ );
+ logger.log({
+ message: 'Failed to push',
+ level: Level.ERROR,
+ error
+ });
+ }
}
});
@@ -245,13 +333,27 @@ export function addCommands(
caption: 'Pull latest code from remote repository',
isEnabled: () => model.pathRepository !== null,
execute: async () => {
- await Private.showGitOperationDialog(model, Operation.Pull).catch(
- reason => {
- console.error(
- `Encountered an error when pulling changes. Error: ${reason}`
- );
- }
- );
+ logger.log({
+ level: Level.RUNNING,
+ message: 'Pulling...'
+ });
+ try {
+ await Private.showGitOperationDialog(model, Operation.Pull);
+ logger.log({
+ message: 'Successfully pulled',
+ level: Level.SUCCESS
+ });
+ } catch (error) {
+ console.error(
+ 'Encountered an error when pulling changes. Error: ',
+ error
+ );
+ logger.log({
+ message: 'Failed to pull',
+ level: Level.ERROR,
+ error
+ });
+ }
}
});
@@ -515,44 +617,69 @@ export function createGitMenu(commands: CommandRegistry): Menu {
/* eslint-disable no-inner-declarations */
namespace Private {
/**
- * Displays an error dialog when a Git operation fails.
+ * Handle Git operation that may require authentication.
*
* @private
* @param model - Git extension model
* @param operation - Git operation name
+ * @param args - Git operation arguments
+ * @param authentication - Git authentication information
+ * @param retry - Is this operation retried?
* @returns Promise for displaying a dialog
*/
- export async function showGitOperationDialog(
+ export async function showGitOperationDialog(
model: GitExtension,
- operation: Operation
+ operation: Operation,
+ args?: T,
+ authentication?: Git.IAuth,
+ retry = false
): Promise {
- const title = `Git ${operation}`;
- let result = await showDialog({
- title: title,
- body: new GitPullPushDialog(model, operation),
- buttons: [Dialog.okButton({ label: 'DISMISS' })]
- });
- let retry = false;
- while (!result.button.accept) {
- const credentials = await showDialog({
- title: 'Git credentials required',
- body: new GitCredentialsForm(
- 'Enter credentials for remote repository',
- retry ? 'Incorrect username or password.' : ''
- ),
- buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OK' })]
- });
-
- if (!credentials.button.accept) {
- break;
+ try {
+ // the Git action
+ switch (operation) {
+ case Operation.Clone:
+ // eslint-disable-next-line no-case-declarations
+ const { path, url } = (args as any) as IGitCloneArgs;
+ await model.clone(path, url, authentication);
+ break;
+ case Operation.Pull:
+ await model.pull(authentication);
+ break;
+ case Operation.Push:
+ await model.push(authentication);
+ break;
+ default:
+ return;
}
+ } catch (error) {
+ if (
+ AUTH_ERROR_MESSAGES.some(
+ errorMessage => error.message.indexOf(errorMessage) > -1
+ )
+ ) {
+ // If the error is an authentication error, ask the user credentials
+ const credentials = await showDialog({
+ title: 'Git credentials required',
+ body: new GitCredentialsForm(
+ 'Enter credentials for remote repository',
+ retry ? 'Incorrect username or password.' : ''
+ )
+ });
- result = await showDialog({
- title: title,
- body: new GitPullPushDialog(model, operation, credentials.value),
- buttons: [Dialog.okButton({ label: 'DISMISS' })]
- });
- retry = true;
+ if (credentials.button.accept) {
+ // Retry the operation if the user provides its credentials
+ return await showGitOperationDialog(
+ model,
+ operation,
+ args,
+ credentials.value,
+ true
+ );
+ }
+ }
+ // Throw the error if it cannot be handled or
+ // if the user did not accept to provide its credentials
+ throw error;
}
}
}
diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx
index b41a07e1d..4e47b2256 100644
--- a/src/components/Alert.tsx
+++ b/src/components/Alert.tsx
@@ -1,9 +1,10 @@
-import * as React from 'react';
+import { showErrorMessage } from '@jupyterlab/apputils';
+import { Button } from '@material-ui/core';
import Portal from '@material-ui/core/Portal';
-import Snackbar from '@material-ui/core/Snackbar';
import Slide from '@material-ui/core/Slide';
-import { default as MuiAlert } from '@material-ui/lab/Alert';
-import { Severity } from '../tokens';
+import Snackbar from '@material-ui/core/Snackbar';
+import { Color, default as MuiAlert } from '@material-ui/lab/Alert';
+import * as React from 'react';
/**
* Returns a React component for "sliding-in" an alert.
@@ -30,10 +31,15 @@ export interface IAlertProps {
*/
message: string;
+ /**
+ * Error object
+ */
+ error?: Error;
+
/**
* Alert severity.
*/
- severity?: Severity;
+ severity?: Color;
/**
* Alert duration (in milliseconds).
@@ -91,7 +97,23 @@ export class Alert extends React.Component {
onClick={this._onClick}
onClose={this._onClose}
>
-
+ {
+ showErrorMessage('Error', this.props.error);
+ }}
+ >
+ SHOW
+
+ )
+ }
+ variant="filled"
+ severity={severity}
+ >
{this.props.message || '(missing message)'}
diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx
index 6cf0ca79d..550a49379 100644
--- a/src/components/BranchMenu.tsx
+++ b/src/components/BranchMenu.tsx
@@ -5,6 +5,7 @@ import ClearIcon from '@material-ui/icons/Clear';
import * as React from 'react';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { classes } from 'typestyle';
+import { Logger } from '../logger';
import {
activeListItemClass,
filterClass,
@@ -16,11 +17,8 @@ import {
newBranchButtonClass,
wrapperClass
} from '../style/BranchMenu';
-import { Git, IGitExtension, ILogMessage } from '../tokens';
-import { sleep } from '../utils';
-import { Alert } from './Alert';
+import { Git, IGitExtension, Level } from '../tokens';
import { NewBranchDialog } from './NewBranchDialog';
-import { SuspendModal } from './SuspendModal';
const CHANGES_ERR_MSG =
'The current branch contains files with uncommitted changes. Please commit or discard these changes before switching to or creating another branch.';
@@ -32,10 +30,16 @@ const MAX_HEIGHT = 400; // Maximal HTML element height for the branches list
* Callback invoked upon encountering an error when switching branches.
*
* @private
- * @param err - error
+ * @param error - error
+ * @param logger - the logger
*/
-function onBranchError(err: any): void {
- if (err.message.includes('following files would be overwritten')) {
+function onBranchError(error: any, logger: Logger): void {
+ if (error.message.includes('following files would be overwritten')) {
+ // Empty log message to hide the executing alert
+ logger.log({
+ message: '',
+ level: Level.INFO
+ });
showDialog({
title: 'Unable to switch branch',
body: (
@@ -45,7 +49,7 @@ function onBranchError(err: any): void {
switching:
- {err.message
+ {error.message
.split('\n')
.slice(1, -3)
.map(renderFileName)}
@@ -59,7 +63,11 @@ function onBranchError(err: any): void {
buttons: [Dialog.okButton({ label: 'Dismiss' })]
});
} else {
- showErrorMessage('Error switching branch', err.message);
+ logger.log({
+ level: Level.ERROR,
+ message: 'Failed to switch branch.',
+ error
+ });
}
}
@@ -79,19 +87,19 @@ function renderFileName(filename: string): React.ReactElement {
*/
export interface IBranchMenuProps {
/**
- * Git extension data model.
+ * Boolean indicating whether branching is disabled.
*/
- model: IGitExtension;
+ branching: boolean;
/**
- * Boolean indicating whether branching is disabled.
+ * Extension logger
*/
- branching: boolean;
+ logger: Logger;
/**
- * Boolean indicating whether to enable UI suspension.
+ * Git extension data model.
*/
- suspend: boolean;
+ model: IGitExtension;
}
/**
@@ -99,39 +107,24 @@ export interface IBranchMenuProps {
*/
export interface IBranchMenuState {
/**
- * Menu filter.
+ * Current branch name.
*/
- filter: string;
+ current: string;
/**
* Boolean indicating whether to show a dialog to create a new branch.
*/
branchDialog: boolean;
- /**
- * Current branch name.
- */
- current: string;
-
/**
* Current list of branches.
*/
branches: Git.IBranch[];
/**
- * Boolean indicating whether UI interaction should be suspended (e.g., due to pending command).
- */
- suspend: boolean;
-
- /**
- * Boolean indicating whether to show an alert.
- */
- alert: boolean;
-
- /**
- * Log message.
+ * Menu filter.
*/
- log: ILogMessage;
+ filter: string;
}
/**
@@ -156,13 +149,7 @@ export class BranchMenu extends React.Component<
filter: '',
branchDialog: false,
current: repo ? this.props.model.currentBranch.name : '',
- branches: repo ? this.props.model.branches : [],
- suspend: false,
- alert: false,
- log: {
- severity: 'info',
- message: ''
- }
+ branches: repo ? this.props.model.branches : []
};
}
@@ -191,7 +178,6 @@ export class BranchMenu extends React.Component<
{this._renderFilter()}
{this._renderBranchList()}
{this._renderNewBranchDialog()}
- {this._renderFeedback()}
);
}
@@ -304,36 +290,14 @@ export class BranchMenu extends React.Component<
private _renderNewBranchDialog(): React.ReactElement {
return (
);
}
- /**
- * Renders a component to provide UI feedback.
- *
- * @returns React element
- */
- private _renderFeedback(): React.ReactElement {
- return (
-
-
-
-
- );
- }
-
/**
* Adds model listeners.
*/
@@ -364,31 +328,6 @@ export class BranchMenu extends React.Component<
});
}
- /**
- * Sets the suspension state.
- *
- * @param bool - boolean indicating whether to suspend UI interaction
- */
- private _suspend(bool: boolean): void {
- if (this.props.suspend) {
- this.setState({
- suspend: bool
- });
- }
- }
-
- /**
- * Sets the current component log message.
- *
- * @param msg - log message
- */
- private _log(msg: ILogMessage): void {
- this.setState({
- alert: true,
- log: msg
- });
- }
-
/**
* Callback invoked upon a change to the menu filter.
*
@@ -459,60 +398,21 @@ export class BranchMenu extends React.Component<
branchname: branch
};
- self._log({
- severity: 'info',
+ self.props.logger.log({
+ level: Level.RUNNING,
message: 'Switching branch...'
});
- self._suspend(true);
- let result: Array;
try {
- result = await Promise.all([
- sleep(1000),
- self.props.model.checkout(opts)
- ]);
+ await self.props.model.checkout(opts);
} catch (err) {
- self._suspend(false);
- self._log({
- severity: 'error',
- message: 'Failed to switch branch.'
- });
- return onBranchError(err);
+ return onBranchError(err, self.props.logger);
}
- self._suspend(false);
- const res = result[1] as Git.ICheckoutResult;
- if (res.code !== 0) {
- self._log({
- severity: 'error',
- message: 'Failed to switch branch.'
- });
- showErrorMessage('Error switching branch', res.message);
- return;
- }
- self._log({
- severity: 'success',
+
+ self.props.logger.log({
+ level: Level.SUCCESS,
message: 'Switched branch.'
});
}
}
-
- /**
- * Callback invoked upon clicking on the feedback modal.
- *
- * @param event - event object
- */
- private _onFeedbackModalClick = (): void => {
- this._suspend(false);
- };
-
- /**
- * Callback invoked upon closing a feedback alert.
- *
- * @param event - event object
- */
- private _onFeedbackAlertClose = (): void => {
- this.setState({
- alert: false
- });
- };
}
diff --git a/src/components/Feedback.tsx b/src/components/Feedback.tsx
new file mode 100644
index 000000000..24c76203c
--- /dev/null
+++ b/src/components/Feedback.tsx
@@ -0,0 +1,132 @@
+import { ISettingRegistry } from '@jupyterlab/settingregistry';
+import { Color } from '@material-ui/lab/Alert';
+import * as React from 'react';
+import { ILogMessage, Level } from '../tokens';
+import { Alert } from './Alert';
+import { SuspendModal } from './SuspendModal';
+
+const LEVEL_TO_SEVERITY: Map = new Map([
+ [Level.ERROR, 'error'],
+ [Level.WARNING, 'warning'],
+ [Level.SUCCESS, 'success'],
+ [Level.INFO, 'info'],
+ [Level.RUNNING, 'info']
+]);
+
+const VISUAL_DELAY = 1000; // in ms
+
+export interface IFeedbackProps {
+ /**
+ * Alert
+ */
+ log: ILogMessage;
+
+ /**
+ * Extension settings
+ */
+ settings: ISettingRegistry.ISettings;
+}
+
+export interface IFeedbackState {
+ /**
+ * Overlay visibility
+ */
+ blockUI: boolean;
+
+ /**
+ * Log message stack
+ */
+ logStack: ILogMessage[];
+
+ /**
+ * Last time the feedback message was changed
+ */
+ lastUpdate: number;
+
+ /**
+ * Alert visibility
+ */
+ showAlert: boolean;
+}
+
+/**
+ * Component to handle logger user feedback
+ */
+export class Feedback extends React.Component {
+ constructor(props: IFeedbackProps) {
+ super(props);
+
+ this.state = {
+ blockUI: false,
+ lastUpdate: Date.now() - VISUAL_DELAY,
+ logStack: [],
+ showAlert: false
+ };
+ }
+
+ static getDerivedStateFromProps(
+ props: IFeedbackProps,
+ state: IFeedbackState
+ ): IFeedbackState {
+ const latestLog = state.logStack[state.logStack.length - 1];
+ const now = Date.now();
+ if (props.log !== latestLog) {
+ if (now - state.lastUpdate > VISUAL_DELAY) {
+ state.logStack.shift();
+ }
+ if (latestLog && props.log.level > latestLog.level) {
+ // Higher level takes over
+ state.logStack.splice(0, 1, props.log);
+ state.lastUpdate = now;
+ } else {
+ state.logStack.push(props.log);
+ }
+ state.blockUI = props.settings.composite[
+ 'blockWhileCommandExecutes'
+ ] as boolean;
+ state.showAlert = true;
+ }
+ return state;
+ }
+
+ render() {
+ if (this.state.logStack.length > 1) {
+ setTimeout(() => {
+ if (this.state.logStack.length > 1) {
+ this.setState({
+ blockUI: this.props.settings.composite[
+ 'blockWhileCommandExecutes'
+ ] as boolean,
+ logStack: this.state.logStack.slice(1),
+ lastUpdate: Date.now(),
+ showAlert: true
+ });
+ }
+ }, VISUAL_DELAY);
+ }
+
+ const log = this.state.logStack[0];
+
+ return (
+
+ {
+ this.setState({ blockUI: false });
+ }}
+ />
+ this.setState({ showAlert: false })}
+ />
+
+ );
+ }
+}
diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx
index b8026c339..e7fff87c0 100644
--- a/src/components/GitPanel.tsx
+++ b/src/components/GitPanel.tsx
@@ -1,4 +1,4 @@
-import { showDialog, showErrorMessage } from '@jupyterlab/apputils';
+import { showDialog } from '@jupyterlab/apputils';
import { PathExt } from '@jupyterlab/coreutils';
import { FileBrowserModel } from '@jupyterlab/filebrowser';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
@@ -8,6 +8,7 @@ import Tab from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import * as React from 'react';
import { CommandIDs } from '../commandsAndMenu';
+import { Logger } from '../logger';
import { GitExtension } from '../model';
import {
panelWrapperClass,
@@ -18,14 +19,11 @@ import {
tabsClass,
warningTextClass
} from '../style/GitPanel';
-import { Git, ILogMessage } from '../tokens';
-import { sleep } from '../utils';
+import { Git, ILogMessage, Level } from '../tokens';
import { GitAuthorForm } from '../widgets/AuthorBox';
-import { Alert } from './Alert';
import { CommitBox } from './CommitBox';
import { FileList } from './FileList';
import { HistorySideBar } from './HistorySideBar';
-import { SuspendModal } from './SuspendModal';
import { Toolbar } from './Toolbar';
/**
@@ -33,24 +31,29 @@ import { Toolbar } from './Toolbar';
*/
export interface IGitPanelProps {
/**
- * Git extension data model.
+ * Jupyter App commands registry
*/
- model: GitExtension;
+ commands: CommandRegistry;
/**
- * Jupyter App commands registry
+ * File browser model.
*/
- commands: CommandRegistry;
+ filebrowser: FileBrowserModel;
/**
- * Git extension settings.
+ * Extension logger
*/
- settings: ISettingRegistry.ISettings;
+ logger: Logger;
/**
- * File browser model.
+ * Git extension data model.
*/
- filebrowser: FileBrowserModel;
+ model: GitExtension;
+
+ /**
+ * Git extension settings.
+ */
+ settings: ISettingRegistry.ISettings;
}
/**
@@ -86,21 +89,6 @@ export interface IGitPanelState {
* Panel tab identifier.
*/
tab: number;
-
- /**
- * Boolean indicating whether UI interaction should be suspended (e.g., due to pending command).
- */
- suspend: boolean;
-
- /**
- * Boolean indicating whether to show an alert.
- */
- alert: boolean;
-
- /**
- * Log message.
- */
- log: ILogMessage;
}
/**
@@ -115,19 +103,14 @@ export class GitPanel extends React.Component {
*/
constructor(props: IGitPanelProps) {
super(props);
+
this.state = {
branches: [],
currentBranch: '',
files: [],
inGitRepository: false,
pastCommits: [],
- tab: 0,
- suspend: false,
- alert: false,
- log: {
- severity: 'info',
- message: ''
- }
+ tab: 0
};
}
@@ -207,17 +190,14 @@ export class GitPanel extends React.Component {
* @returns a promise which commits the files
*/
commitMarkedFiles = async (message: string): Promise => {
- this._suspend(true);
-
- this._log({
- severity: 'info',
+ this.props.logger.log({
+ level: Level.RUNNING,
message: 'Staging files...'
});
await this.props.model.reset();
await this.props.model.add(...this._markedFiles.map(file => file.to));
await this.commitStagedFiles(message);
- this._suspend(false);
};
/**
@@ -227,50 +207,38 @@ export class GitPanel extends React.Component {
* @returns a promise which commits the files
*/
commitStagedFiles = async (message: string): Promise => {
- let res: boolean;
if (!message) {
return;
}
+
+ const errorLog: ILogMessage = {
+ level: Level.ERROR,
+ message: 'Failed to commit changes.'
+ };
+
try {
- res = await this._hasIdentity(this.props.model.pathRepository);
- } catch (err) {
- this._log({
- severity: 'error',
- message: 'Failed to commit changes.'
- });
- console.error(err);
- showErrorMessage('Fail to commit', err);
- return;
- }
- if (!res) {
- this._log({
- severity: 'error',
- message: 'Failed to commit changes.'
+ const res = await this._hasIdentity(this.props.model.pathRepository);
+
+ if (!res) {
+ this.props.logger.log(errorLog);
+ return;
+ }
+
+ this.props.logger.log({
+ level: Level.RUNNING,
+ message: 'Committing changes...'
});
- return;
- }
- this._log({
- severity: 'info',
- message: 'Committing changes...'
- });
- this._suspend(true);
- try {
- await Promise.all([sleep(1000), this.props.model.commit(message)]);
- } catch (err) {
- this._suspend(false);
- this._log({
- severity: 'error',
- message: 'Failed to commit changes.'
+
+ await this.props.model.commit(message);
+
+ this.props.logger.log({
+ level: Level.SUCCESS,
+ message: 'Committed changes.'
});
- console.error(err);
- showErrorMessage('Fail to commit', err);
- return;
+ } catch (error) {
+ console.error(error);
+ this.props.logger.log({ ...errorLog, error });
}
- this._suspend(false);
- this._log({
- severity: 'success',
- message: 'Committed changes.'
- });
};
/**
@@ -285,7 +253,6 @@ export class GitPanel extends React.Component {
{this._renderToolbar()}
{this._renderMain()}
- {this._renderFeedback()}
) : (
this._renderWarning()
@@ -306,13 +273,11 @@ export class GitPanel extends React.Component {
);
return (
);
}
@@ -411,38 +376,10 @@ export class GitPanel extends React.Component {
commits={this.state.pastCommits}
model={this.props.model}
commands={this.props.commands}
- suspend={
- this.props.settings.composite['blockWhileCommandExecutes'] as boolean
- }
/>
);
}
- /**
- * Renders a component to provide UI feedback.
- *
- * @returns React element
- */
- private _renderFeedback(): React.ReactElement {
- return (
-
-
-
-
- );
- }
-
/**
* Renders a panel for prompting a user to find a Git repository.
*
@@ -491,31 +428,6 @@ export class GitPanel extends React.Component {
);
}
- /**
- * Sets the suspension state.
- *
- * @param bool - boolean indicating whether to suspend UI interaction
- */
- private _suspend(bool: boolean): void {
- if (this.props.settings.composite['blockWhileCommandExecutes']) {
- this.setState({
- suspend: bool
- });
- }
- }
-
- /**
- * Sets the current component log message.
- *
- * @param msg - log message
- */
- private _log(msg: ILogMessage): void {
- this.setState({
- alert: true,
- log: msg
- });
- }
-
/**
* Callback invoked upon changing the active panel tab.
*
@@ -545,26 +457,6 @@ export class GitPanel extends React.Component {
}
};
- /**
- * Callback invoked upon clicking on the feedback modal.
- *
- * @param event - event object
- */
- private _onFeedbackModalClick = (): void => {
- this._suspend(false);
- };
-
- /**
- * Callback invoked upon closing a feedback alert.
- *
- * @param event - event object
- */
- private _onFeedbackAlertClose = (): void => {
- this.setState({
- alert: false
- });
- };
-
/**
* Determines whether a user has a known Git identity.
*
diff --git a/src/components/HistorySideBar.tsx b/src/components/HistorySideBar.tsx
index dea917116..ce6a98219 100644
--- a/src/components/HistorySideBar.tsx
+++ b/src/components/HistorySideBar.tsx
@@ -28,11 +28,6 @@ export interface IHistorySideBarProps {
* Jupyter App commands registry
*/
commands: CommandRegistry;
-
- /**
- * Boolean indicating whether to enable UI suspension.
- */
- suspend: boolean;
}
/**
@@ -52,7 +47,6 @@ export const HistorySideBar: React.FunctionComponent = (
branches={props.branches}
model={props.model}
commands={props.commands}
- suspend={props.suspend}
/>
))}
diff --git a/src/components/NewBranchDialog.tsx b/src/components/NewBranchDialog.tsx
index cbe4a5801..bfc9c6b3c 100644
--- a/src/components/NewBranchDialog.tsx
+++ b/src/components/NewBranchDialog.tsx
@@ -1,11 +1,11 @@
-import * as React from 'react';
-import { classes } from 'typestyle';
-import ListItem from '@material-ui/core/ListItem';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
+import ListItem from '@material-ui/core/ListItem';
import ClearIcon from '@material-ui/icons/Clear';
-import { sleep } from '../utils';
-import { Git, IGitExtension, ILogMessage } from '../tokens';
+import * as React from 'react';
+import { ListChildComponentProps, VariableSizeList } from 'react-window';
+import { classes } from 'typestyle';
+import { Logger } from '../logger';
import {
actionsWrapperClass,
activeListItemClass,
@@ -31,9 +31,7 @@ import {
titleClass,
titleWrapperClass
} from '../style/NewBranchDialog';
-import { SuspendModal } from './SuspendModal';
-import { Alert } from './Alert';
-import { VariableSizeList, ListChildComponentProps } from 'react-window';
+import { Git, IGitExtension, Level } from '../tokens';
const BRANCH_DESC = {
current:
@@ -50,6 +48,11 @@ const HEIGHT = 200; // HTML element height for the branches list
* Interface describing component properties.
*/
export interface INewBranchDialogProps {
+ /**
+ * Extension logger
+ */
+ logger: Logger;
+
/**
* Git extension data model.
*/
@@ -60,11 +63,6 @@ export interface INewBranchDialogProps {
*/
open: boolean;
- /**
- * Boolean indicating whether to enable UI suspension.
- */
- suspend: boolean;
-
/**
* Callback to invoke upon closing the dialog.
*/
@@ -104,21 +102,6 @@ export interface INewBranchDialogState {
* Error message.
*/
error: string;
-
- /**
- * Boolean indicating whether UI interaction should be suspended (e.g., due to pending command).
- */
- suspend: boolean;
-
- /**
- * Boolean indicating whether to show an alert.
- */
- alert: boolean;
-
- /**
- * Log message.
- */
- log: ILogMessage;
}
/**
@@ -146,13 +129,7 @@ export class NewBranchDialog extends React.Component<
filter: '',
current: repo ? this.props.model.currentBranch.name : '',
branches: repo ? this.props.model.branches : [],
- error: '',
- suspend: false,
- alert: false,
- log: {
- severity: 'info',
- message: ''
- }
+ error: ''
};
}
@@ -169,27 +146,12 @@ export class NewBranchDialog extends React.Component<
componentWillUnmount(): void {
this._removeListeners();
}
-
- /**
- * Renders the component.
- *
- * @returns React element
- */
- render(): React.ReactElement {
- return (
-
- {this._renderDialog()}
- {this._renderFeedback()}
-
- );
- }
-
/**
* Renders a dialog for creating a new branch.
*
* @returns React element
*/
- private _renderDialog(): React.ReactElement {
+ render(): React.ReactElement {
return (