Skip to content

Commit ac778dd

Browse files
Merge pull request #340 from jaipreet-s/pushpullspinner
Display push/pull progress and success.
2 parents f3d0f0d + 984ad94 commit ac778dd

File tree

2 files changed

+132
-38
lines changed

2 files changed

+132
-38
lines changed

src/components/PathHeader.tsx

Lines changed: 24 additions & 38 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,22 @@ 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+
)
60+
}
5461
/>
5562
<button
5663
className={classes(gitPushStyle, 'jp-Icon-16')}
5764
title={'Push committed changes'}
58-
onClick={() => this.executeGitPush()}
65+
onClick={() =>
66+
this.showGitPushPullDialog(
67+
this.props.currentFileBrowserPath,
68+
Operation.Push
69+
)
70+
}
5971
/>
6072
<button
6173
className={classes(repoRefreshStyle, 'jp-Icon-16')}
@@ -65,46 +77,20 @@ export class PathHeader extends React.Component<
6577
);
6678
}
6779

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-
9680
/**
9781
* Displays the error dialog when the Git Push/Pull operation fails.
9882
* @param title the title of the error dialog
9983
* @param body the message to be shown in the body of the modal.
10084
*/
101-
private showErrorDialog(title: string, body: string = ''): Promise<void> {
102-
return showDialog({
103-
title: title,
104-
body: body,
105-
buttons: [Dialog.warnButton({ label: 'DISMISS' })]
106-
}).then(() => {
107-
// NO-OP
85+
private showGitPushPullDialog(
86+
currentFileBrowserPath: string,
87+
operation: Operation
88+
): Promise<void> {
89+
let dialog = new Dialog({
90+
title: `Git ${operation}`,
91+
body: new GitPullPushDialog(currentFileBrowserPath, operation),
92+
buttons: [Dialog.okButton({ label: 'DISMISS' })]
10893
});
94+
return dialog.launch().then(() => {});
10995
}
11096
}

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)