Skip to content

Commit

Permalink
Handle missing GitHub App token from notification card
Browse files Browse the repository at this point in the history
  • Loading branch information
brunolemos committed Feb 26, 2019
1 parent 7dc982a commit f2362c0
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { SpringAnimatedText } from '../animated/spring/SpringAnimatedText'

export interface GenericMessageWithButtonViewProps {
buttonView: React.ReactNode
emoji: GitHubEmoji
subtitle: string | undefined
title: string
emoji: GitHubEmoji | null
subtitle: string | undefined | null
title: string | undefined | null
}

export const GenericMessageWithButtonView = React.memo(
Expand All @@ -22,7 +22,7 @@ export const GenericMessageWithButtonView = React.memo(

const springAnimatedTheme = useCSSVariablesOrSpringAnimatedTheme()

const emojiImageURL = getEmojiImageURL(emoji)
const emojiImageURL = emoji ? getEmojiImageURL(emoji) : null

return (
<View
Expand Down
11 changes: 7 additions & 4 deletions packages/components/src/components/cards/NoTokenView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { Button } from '../common/Button'
import { GenericMessageWithButtonView } from './GenericMessageWithButtonView'

export interface NoTokenViewProps {
emoji?: GitHubEmoji
emoji?: GitHubEmoji | null
githubAppType: GitHubAppType | 'both'
subtitle?: string
title?: string
subtitle?: string | null
title?: string | null
}

export const NoTokenView = React.memo((props: NoTokenViewProps) => {
Expand All @@ -39,6 +39,8 @@ export const NoTokenView = React.memo((props: NoTokenViewProps) => {
try {
analytics.trackEvent('engagement', `relogin_add_token_${githubAppType}`)

setIsExecutingOAuth(true)

const params = await executeOAuth(githubAppType, {
appToken: existingAppToken,
scope:
Expand All @@ -50,6 +52,7 @@ export const NoTokenView = React.memo((props: NoTokenViewProps) => {
if (!appToken) throw new Error('No app token')

loginRequest({ appToken })
setIsExecutingOAuth(false)
} catch (error) {
const description = 'OAuth execution failed'
console.error(description, error)
Expand All @@ -58,7 +61,7 @@ export const NoTokenView = React.memo((props: NoTokenViewProps) => {
if (error.message === 'Canceled' || error.message === 'Timeout') return
bugsnag.notify(error, { description })

alert(`Login failed. ${error || ''}`)
alert(`Authentication failed. ${error || ''}`)
}
}

Expand Down
2 changes: 0 additions & 2 deletions packages/components/src/components/cards/NotificationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {
isNotificationPrivate,
trimNewLinesAndSpaces,
} from '@devhub/core'
import { useReduxState } from '../../hooks/use-redux-state'
import * as selectors from '../../redux/selectors'
import * as colors from '../../styles/colors'
import { contentPadding } from '../../styles/variables'
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React from 'react'
import { View } from 'react-native'
import React, { useState } from 'react'
import { StyleSheet, View } from 'react-native'

import { constants } from '@devhub/core'
import { useCSSVariablesOrSpringAnimatedTheme } from '../../../../hooks/use-css-variables-or-spring--animated-theme'
import { useReduxAction } from '../../../../hooks/use-redux-action'
import { useReduxState } from '../../../../hooks/use-redux-state'
import { analytics } from '../../../../libs/analytics'
import { bugsnag } from '../../../../libs/bugsnag'
import { executeOAuth } from '../../../../libs/oauth'
import * as actions from '../../../../redux/actions'
import * as selectors from '../../../../redux/selectors'
import { tryParseOAuthParams } from '../../../../utils/helpers/auth'
import { getGitHubAppInstallUri } from '../../../../utils/helpers/shared'
import { SpringAnimatedText } from '../../../animated/spring/SpringAnimatedText'
import { Link } from '../../../common/Link'
Expand All @@ -18,9 +25,90 @@ export interface PrivateNotificationRowProps {

export const PrivateNotificationRow = React.memo(
(props: PrivateNotificationRowProps) => {
const { ownerId, repoId, smallLeftColumn } = props

const springAnimatedTheme = useCSSVariablesOrSpringAnimatedTheme()

const { ownerId, repoId, smallLeftColumn } = props
const existingAppToken = useReduxState(selectors.appTokenSelector)
const githubAppToken = useReduxState(selectors.githubAppTokenSelector)
const isLoggingIn = useReduxState(selectors.isLoggingInSelector)
const installationsLoadState = useReduxState(
selectors.installationsLoadStateSelector,
)
const loginRequest = useReduxAction(actions.loginRequest)

const showLoadingIndicator =
isLoggingIn || installationsLoadState === 'loading'

async function startOAuth() {
try {
analytics.trackEvent('engagement', 'relogin_add_token_app')

const params = await executeOAuth('app', {
appToken: existingAppToken,
scope: undefined,
})

const { appToken } = tryParseOAuthParams(params)
if (!appToken) throw new Error('No app token')

loginRequest({ appToken })
} catch (error) {
const description = 'OAuth execution failed'
console.error(description, error)

if (error.message === 'Canceled' || error.message === 'Timeout') return
bugsnag.notify(error, { description })

alert(`Authentication failed. ${error || ''}`)
}
}

function renderContent() {
if (!(existingAppToken && githubAppToken)) {
return (
<Link
analyticsLabel="setup_github_app_from_private_notification"
disabled={showLoadingIndicator}
onPress={() => startOAuth()}
style={cardRowStyles.mainContentContainer}
>
<SpringAnimatedText
style={[
getCardStylesForTheme(springAnimatedTheme).commentText,
getCardStylesForTheme(springAnimatedTheme).mutedText,
{ fontStyle: 'italic' },
]}
>
Required permission is missing. Tap to login again.
</SpringAnimatedText>
</Link>
)
}

return (
<Link
analyticsLabel="setup_github_app_from_private_notification"
href={getGitHubAppInstallUri({
repositoryIds: repoId ? [repoId] : [],
suggestedTargetId: ownerId,
})}
openOnNewTab={false}
style={cardRowStyles.mainContentContainer}
>
<SpringAnimatedText
style={[
getCardStylesForTheme(springAnimatedTheme).commentText,
getCardStylesForTheme(springAnimatedTheme).mutedText,
{ fontStyle: 'italic' },
]}
>
Install the GitHub App on this repo to unlock details from private
notifications.
</SpringAnimatedText>
</Link>
)
}

return (
<View style={cardRowStyles.container}>
Expand All @@ -35,26 +123,37 @@ export const PrivateNotificationRow = React.memo(
/>

<View style={cardStyles.rightColumn}>
<Link
analyticsLabel="setup_github_app_from_private_notification"
href={getGitHubAppInstallUri({
repositoryIds: repoId ? [repoId] : [],
suggestedTargetId: ownerId,
})}
openOnNewTab={false}
style={cardRowStyles.mainContentContainer}
<View
style={{
flex: 1,
position: 'relative',
opacity: showLoadingIndicator ? 0 : 1,
}}
>
<SpringAnimatedText
{renderContent()}
</View>

{!!showLoadingIndicator && (
<View
style={[
getCardStylesForTheme(springAnimatedTheme).commentText,
getCardStylesForTheme(springAnimatedTheme).mutedText,
{ fontStyle: 'italic' },
StyleSheet.absoluteFill,
{
alignItems: 'flex-start',
justifyContent: 'flex-start',
},
]}
>
Install the GitHub App to unlock details from private
notifications.
</SpringAnimatedText>
</Link>
<SpringAnimatedText
style={[
getCardStylesForTheme(springAnimatedTheme).commentText,
getCardStylesForTheme(springAnimatedTheme).mutedText,
{ fontStyle: 'italic' },
]}
>
Checking required permissions...
</SpringAnimatedText>
</View>
)}
</View>
</View>
)
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/components/common/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function Link(props: LinkProps) {
}
}

const renderTouchable = href || allowEmptyLink
const renderTouchable = href || otherProps.onPress || allowEmptyLink

let finalProps: any
if (renderTouchable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const AdvancedSettingsModal = React.memo(
if (!appToken) throw new Error('No app token')

loginRequest({ appToken })
setExecutingOAuth(null)
} catch (error) {
const description = 'OAuth execution failed'
console.error(description, error)
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/screens/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const LoginScreen = React.memo(() => {
if (!appToken) throw new Error('No app token')

loginRequest({ appToken })
setIsExecutingOAuth(false)
} catch (error) {
const description = 'OAuth execution failed'
console.error(description, error)
Expand Down

0 comments on commit f2362c0

Please sign in to comment.