Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move retry clone dialog into AppError #9944

Merged
merged 21 commits into from Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/lib/stores/cloning-repositories-store.ts
Expand Up @@ -41,6 +41,7 @@ export class CloningRepositoriesStore extends BaseStore {

const retryAction: RetryAction = {
type: RetryActionType.Clone,
name: repository.name,
url,
path,
options,
Expand Down
8 changes: 0 additions & 8 deletions app/src/models/popup.ts
Expand Up @@ -14,7 +14,6 @@ import { ICommitContext } from './commit'
import { IStashEntry } from './stash-entry'
import { Account } from '../models/account'
import { Progress } from './progress'
import { CloningRepository } from './cloning-repository'
import { ITextDiff, DiffSelection } from './diff'

export enum PopupType {
Expand Down Expand Up @@ -67,7 +66,6 @@ export enum PopupType {
DeleteTag,
LocalChangesOverwritten,
RebaseConflicts,
RetryClone,
ChooseForkSettings,
ConfirmDiscardSelection,
}
Expand Down Expand Up @@ -253,12 +251,6 @@ export type Popup =
initialName?: string
localTags: Map<string, string> | null
}
| {
type: PopupType.RetryClone
repository: Repository | CloningRepository
retryAction: RetryAction
errorMessage: string
}
| {
type: PopupType.DeleteTag
repository: Repository
Expand Down
1 change: 1 addition & 0 deletions app/src/models/retry-actions.ts
Expand Up @@ -18,6 +18,7 @@ export type RetryAction =
| { type: RetryActionType.Fetch; repository: Repository }
| {
type: RetryActionType.Clone
name: string
url: string
path: string
options: CloneOptions
Expand Down
137 changes: 105 additions & 32 deletions app/src/ui/app-error.tsx
Expand Up @@ -10,12 +10,13 @@ import {
dialogTransitionEnterTimeout,
dialogTransitionLeaveTimeout,
} from './app'
import { GitError } from '../lib/git/core'
import { GitError as GitErrorType } from 'dugite'
import { GitError, isAuthFailureError } from '../lib/git/core'
import { Popup, PopupType } from '../models/popup'
import { CSSTransitionGroup } from 'react-transition-group'
import { OkCancelButtonGroup } from './dialog/ok-cancel-button-group'
import { ErrorWithMetadata } from '../lib/error-with-metadata'
import { RetryActionType, RetryAction } from '../models/retry-actions'
import { Ref } from './lib/ref'

interface IAppErrorProps {
/** The list of queued, app-wide, errors */
Expand All @@ -27,6 +28,7 @@ interface IAppErrorProps {
*/
readonly onClearError: (error: Error) => void
readonly onShowPopup: (popupType: Popup) => void | undefined
readonly onRetryAction: (retryAction: RetryAction) => void
}

interface IAppErrorState {
Expand Down Expand Up @@ -69,7 +71,7 @@ export class AppError extends React.Component<IAppErrorProps, IAppErrorState> {
private onDismissed = () => {
const currentError = this.state.error

if (currentError) {
if (currentError !== null) {
this.setState({ error: null, disabled: true })

// Give some time for the dialog to nicely transition
Expand All @@ -91,26 +93,17 @@ export class AppError extends React.Component<IAppErrorProps, IAppErrorState> {
}, dialogTransitionLeaveTimeout)
}

private renderGitErrorFooter(error: GitError) {
const gitErrorType = error.result.gitError

switch (gitErrorType) {
case GitErrorType.HTTPSAuthenticationFailed: {
return (
<DialogFooter>
<OkCancelButtonGroup
okButtonText="Close"
onOkButtonClick={this.onCloseButtonClick}
cancelButtonText={
__DARWIN__ ? 'Open Preferences' : 'Open options'
}
onCancelButtonClick={this.showPreferencesDialog}
/>
</DialogFooter>
)
private onRetryAction = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault()
this.onDismissed()

const { error } = this.state

if (error !== null && isErrorWithMetaData(error)) {
const { retryAction } = error.metadata
if (retryAction !== undefined) {
this.props.onRetryAction(retryAction)
}
default:
return <DefaultDialogFooter onButtonClick={this.onCloseButtonClick} />
}
}

Expand All @@ -132,6 +125,14 @@ export class AppError extends React.Component<IAppErrorProps, IAppErrorState> {
return <p className={className}>{e.message}</p>
}

private getTitle(error: Error) {
if (isCloneError(error)) {
return 'Clone failed'
}

return 'Error'
}

private renderDialog() {
const error = this.state.error

Expand All @@ -144,35 +145,51 @@ export class AppError extends React.Component<IAppErrorProps, IAppErrorState> {
id="app-error"
type="error"
key="error"
title="Error"
title={this.getTitle(error)}
dismissable={false}
onSubmit={this.onDismissed}
onDismissed={this.onDismissed}
disabled={this.state.disabled}
>
<DialogContent onRef={this.onDialogContentRef}>
{this.renderErrorMessage(error)}
{this.renderContentAfterErrorMessage(error)}
</DialogContent>
{this.renderFooter(error)}
</Dialog>
)
}

private renderContentAfterErrorMessage(error: Error) {
if (!isErrorWithMetaData(error)) {
return undefined
}

const { retryAction } = error.metadata

if (retryAction && retryAction.type === RetryActionType.Clone) {
return (
<p>
Would you like to retry cloning <Ref>{retryAction.name}</Ref>?
</p>
)
}

return undefined
}

private onDialogContentRef = (ref: HTMLDivElement | null) => {
this.dialogContent = ref
}

private scrollToBottomOfGitErrorMessage() {
if (!this.dialogContent) {
if (this.dialogContent === null || this.state.error === null) {
return
}

const e =
this.state.error instanceof ErrorWithMetadata
? this.state.error.underlyingError
: this.state.error
const e = getUnderlyingError(this.state.error)

if (e instanceof GitError) {
if (isGitError(e)) {
if (e.message === e.result.stderr || e.message === e.result.stdout) {
this.dialogContent.scrollTop = this.dialogContent.scrollHeight
}
Expand All @@ -198,12 +215,48 @@ export class AppError extends React.Component<IAppErrorProps, IAppErrorState> {
}

private renderFooter(error: Error) {
const e = error instanceof ErrorWithMetadata ? error.underlyingError : error
if (isCloneError(error)) {
return this.renderRetryCloneFooter()
}

if (e instanceof GitError) {
return this.renderGitErrorFooter(e)
const underlyingError = getUnderlyingError(error)

if (isGitError(underlyingError)) {
const { gitError } = underlyingError.result
if (gitError !== null && isAuthFailureError(gitError)) {
return this.renderOpenPreferencesFooter()
}
}

return this.renderDefaultFooter()
}

private renderRetryCloneFooter() {
return (
<DialogFooter>
<OkCancelButtonGroup
okButtonText={__DARWIN__ ? 'Retry Clone' : 'Retry clone'}
onOkButtonClick={this.onRetryAction}
onCancelButtonClick={this.onCloseButtonClick}
/>
</DialogFooter>
)
}

private renderOpenPreferencesFooter() {
return (
<DialogFooter>
<OkCancelButtonGroup
okButtonText="Close"
onOkButtonClick={this.onCloseButtonClick}
cancelButtonText={__DARWIN__ ? 'Open Preferences' : 'Open options'}
onCancelButtonClick={this.showPreferencesDialog}
/>
</DialogFooter>
)
}

private renderDefaultFooter() {
return <DefaultDialogFooter onButtonClick={this.onCloseButtonClick} />
}

Expand All @@ -220,3 +273,23 @@ export class AppError extends React.Component<IAppErrorProps, IAppErrorState> {
)
}
}

function getUnderlyingError(error: Error): Error {
return isErrorWithMetaData(error) ? error.underlyingError : error
}

function isErrorWithMetaData(error: Error): error is ErrorWithMetadata {
return error instanceof ErrorWithMetadata
}

function isGitError(error: Error): error is GitError {
return error instanceof GitError
}

function isCloneError(error: Error) {
if (!isErrorWithMetaData(error)) {
return false
}
const { retryAction } = error.metadata
return retryAction !== undefined && retryAction.type === RetryActionType.Clone
}
17 changes: 5 additions & 12 deletions app/src/ui/app.tsx
Expand Up @@ -120,7 +120,6 @@ import { SChannelNoRevocationCheckDialog } from './schannel-no-revocation-check/
import { findDefaultUpstreamBranch } from '../lib/branch'
import { GitHubRepository } from '../models/github-repository'
import { CreateTag } from './create-tag'
import { RetryCloneDialog } from './clone-repository/retry-clone-dialog'
import { DeleteTag } from './delete-tag'
import { ChooseForkSettings } from './choose-fork-settings'
import { DiscardSelection } from './discard-changes/discard-selection-dialog'
Expand Down Expand Up @@ -1973,17 +1972,6 @@ export class App extends React.Component<IAppProps, IAppState> {
/>
)
}
case PopupType.RetryClone: {
return (
<RetryCloneDialog
repository={popup.repository}
retryAction={popup.retryAction}
onDismissed={this.onPopupDismissed}
dispatcher={this.props.dispatcher}
errorMessage={popup.errorMessage}
/>
)
}
case PopupType.ChooseForkSettings: {
return (
<ChooseForkSettings
Expand Down Expand Up @@ -2162,10 +2150,15 @@ export class App extends React.Component<IAppProps, IAppState> {
errors={this.state.errors}
onClearError={this.clearError}
onShowPopup={this.showPopup}
onRetryAction={this.onRetryAction}
/>
)
}

private onRetryAction = (retryAction: RetryAction) => {
this.props.dispatcher.performRetry(retryAction)
}

private showPopup = (popup: Popup) => {
this.props.dispatcher.showPopup(popup)
}
Expand Down
76 changes: 0 additions & 76 deletions app/src/ui/clone-repository/retry-clone-dialog.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions app/src/ui/dialog/ok-cancel-button-group.tsx
Expand Up @@ -34,10 +34,10 @@ interface IOkCancelButtonGroupProps {
/** Whether the Ok button will be disabled or not, defaults to false */
readonly okButtonDisabled?: boolean

/** An optional text/label for the Ok button, defaults to "Ok" */
/** An optional text/label for the Cancel button, defaults to "Cancel" */
readonly cancelButtonText?: string | JSX.Element

/** An optional title (i.e. tooltip) for the Ok button, defaults to none */
/** An optional title (i.e. tooltip) for the Cancel button, defaults to none */
readonly cancelButtonTitle?: string

/**
Expand Down