Skip to content

Commit d5fb2f9

Browse files
author
Jaipreet Singh
committed
Display push/pull progress and success.
* Show a spinner while the operation is in progress (Helps with large commits or slow remotes) * Show a confirmation in the success case. * Show error message in the failure case.
1 parent 98e5ef2 commit d5fb2f9

File tree

2 files changed

+134
-37
lines changed

2 files changed

+134
-37
lines changed

src/components/PathHeader.tsx

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { classes } from 'typestyle';
1212

1313
import { Git } from '../git';
1414

15-
import { Dialog, showDialog } from '@jupyterlab/apputils';
15+
import { Dialog } from '@jupyterlab/apputils';
16+
17+
import { GitPullPushDialog, Operation } from '../gitPushPull';
1618

1719
export interface IPathHeaderState {
1820
refresh: any;
@@ -50,12 +52,24 @@ export class PathHeader extends React.Component<
5052
<button
5153
className={classes(gitPullStyle, 'jp-Icon-16')}
5254
title={'Pull latest changes'}
53-
onClick={() => this.executeGitPull()}
55+
onClick={() =>
56+
this.showGitPushPullDialog(
57+
this.props.currentFileBrowserPath,
58+
Operation.Pull,
59+
'Git Pull'
60+
)
61+
}
5462
/>
5563
<button
5664
className={classes(gitPushStyle, 'jp-Icon-16')}
5765
title={'Push committed changes'}
58-
onClick={() => this.executeGitPush()}
66+
onClick={() =>
67+
this.showGitPushPullDialog(
68+
this.props.currentFileBrowserPath,
69+
Operation.Push,
70+
'Git Push'
71+
)
72+
}
5973
/>
6074
<button
6175
className={classes(repoRefreshStyle, 'jp-Icon-16')}
@@ -65,46 +79,21 @@ export class PathHeader extends React.Component<
6579
);
6680
}
6781

68-
/**
69-
* Execute the `/git/pull` API
70-
*/
71-
private executeGitPull(): void {
72-
this.state.gitApi
73-
.pull(this.props.currentFileBrowserPath)
74-
.then(response => {
75-
if (response.code !== 0) {
76-
this.showErrorDialog('Pull failed', response.message);
77-
}
78-
})
79-
.catch(() => this.showErrorDialog('Pull failed'));
80-
}
81-
82-
/**
83-
* Execute the `/git/push` API
84-
*/
85-
private executeGitPush(): void {
86-
this.state.gitApi
87-
.push(this.props.currentFileBrowserPath)
88-
.then(response => {
89-
if (response.code !== 0) {
90-
this.showErrorDialog('Push failed', response.message);
91-
}
92-
})
93-
.catch(() => this.showErrorDialog('Push failed'));
94-
}
95-
9682
/**
9783
* Displays the error dialog when the Git Push/Pull operation fails.
9884
* @param title the title of the error dialog
9985
* @param body the message to be shown in the body of the modal.
10086
*/
101-
private showErrorDialog(title: string, body: string = ''): Promise<void> {
102-
return showDialog({
87+
private showGitPushPullDialog(
88+
currentFileBrowserPath: string,
89+
operation: Operation,
90+
title: string
91+
): Promise<void> {
92+
let dialog = new Dialog({
10393
title: title,
104-
body: body,
105-
buttons: [Dialog.warnButton({ label: 'DISMISS' })]
106-
}).then(() => {
107-
// NO-OP
94+
body: new GitPullPushDialog(currentFileBrowserPath, operation),
95+
buttons: [Dialog.okButton({ label: 'DISMISS' })]
10896
});
97+
return dialog.launch().then(() => {});
10998
}
11099
}

src/gitPushPull.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Spinner } from '@jupyterlab/apputils';
2+
import { Widget } from '@phosphor/widgets';
3+
import { Git, IGitPushPullResult } from './git';
4+
5+
export enum Operation {
6+
Pull = 'Pull',
7+
Push = 'Push'
8+
}
9+
10+
/**
11+
* The UI for the content shown within the Git push/pull modal.
12+
*/
13+
export class GitPullPushDialog extends Widget {
14+
spinner: Spinner;
15+
gitApi: Git;
16+
body: HTMLElement;
17+
operation: Operation;
18+
19+
/**
20+
* Instantiates the dialog and makes the relevant service API call.
21+
*/
22+
constructor(currentFileBrowserPath: string, operation: Operation) {
23+
super();
24+
this.operation = operation;
25+
26+
this.body = this.createBody();
27+
this.node.appendChild(this.body);
28+
29+
this.spinner = new Spinner();
30+
this.node.appendChild(this.spinner.node);
31+
32+
this.gitApi = new Git();
33+
this.executeGitApi(currentFileBrowserPath);
34+
}
35+
36+
/**
37+
* Executes the relevant service API depending on the operation and handles response and errors.
38+
* @param currentFileBrowserPath the path to the current repo
39+
*/
40+
private executeGitApi(currentFileBrowserPath: string) {
41+
switch (this.operation) {
42+
case Operation.Pull:
43+
this.gitApi
44+
.pull(currentFileBrowserPath)
45+
.then(response => {
46+
this.handleResponse(response);
47+
})
48+
.catch(() => this.handleError());
49+
break;
50+
case Operation.Push:
51+
this.gitApi
52+
.push(currentFileBrowserPath)
53+
.then(response => {
54+
this.handleResponse(response);
55+
})
56+
.catch(() => this.handleError());
57+
break;
58+
default:
59+
throw new Error(`Invalid operation type: ${this.operation}`);
60+
}
61+
}
62+
63+
/**
64+
* Handles the response from the server by removing the spinner and showing the appropriate
65+
* success or error message.
66+
* @param response the response from the server API call
67+
*/
68+
private handleResponse(response: IGitPushPullResult) {
69+
this.node.removeChild(this.spinner.node);
70+
this.spinner.dispose();
71+
if (response.code !== 0) {
72+
this.handleError(response.message);
73+
} else {
74+
this.handleSuccess();
75+
}
76+
}
77+
78+
private handleError(
79+
message: string = 'Unexpected failure. Please check your Jupyter server logs for more details.'
80+
): void {
81+
const label = document.createElement('label');
82+
const text = document.createElement('span');
83+
text.textContent = `Git ${this.operation} failed with error:`;
84+
const errorMessage = document.createElement('span');
85+
errorMessage.textContent = message;
86+
errorMessage.setAttribute(
87+
'style',
88+
'background-color:var(--jp-rendermime-error-background)'
89+
);
90+
label.appendChild(text);
91+
label.appendChild(document.createElement('p'));
92+
label.appendChild(errorMessage);
93+
this.body.appendChild(label);
94+
}
95+
96+
private handleSuccess(): void {
97+
const label = document.createElement('label');
98+
const text = document.createElement('span');
99+
text.textContent = `Git ${this.operation} completed successfully`;
100+
label.appendChild(text);
101+
this.body.appendChild(label);
102+
}
103+
private createBody(): HTMLElement {
104+
const node = document.createElement('div');
105+
node.className = 'jp-RedirectForm';
106+
return node;
107+
}
108+
}

0 commit comments

Comments
 (0)