Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.

Commit db95881

Browse files
committed
feat: wip
1 parent b734b2a commit db95881

File tree

4 files changed

+155
-57
lines changed

4 files changed

+155
-57
lines changed

src/extension/scripts/background.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ permissions.onRemoved(permissions => {
162162
})
163163
})
164164

165-
// Ensure access tokens are in storage and they are in the correct shape.
165+
// Ensure access tokens are in storage and they are in the correct shape.jj
166166
storage.addSyncMigration((items, set, remove) => {
167167
if (!items.accessTokens) {
168168
set({ accessTokens: {} })
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { assert } from 'chai'
2+
import { describe, it } from 'mocha'
3+
import * as React from 'react'
4+
import { render } from 'react-testing-library'
5+
import { noop, Observable, of } from 'rxjs'
6+
import sinon from 'sinon'
7+
import { FeatureFlags } from '../../browser/types'
8+
import { OptionsContainer } from './OptionsContainer'
9+
10+
describe('OptionsContainer', () => {
11+
const stubGetConfigurableSettings = () => new Observable<Partial<FeatureFlags>>()
12+
const stubSetConfigurableSettings = (settings: Observable<Partial<FeatureFlags>>) =>
13+
new Observable<Partial<FeatureFlags>>()
14+
const stub
15+
16+
it('checks the connection status when it renders', () => {
17+
const fetchSiteSpy = sinon.spy()
18+
const fetchSite = (url: string) => {
19+
fetchSiteSpy(url)
20+
21+
return of(undefined)
22+
}
23+
24+
render(
25+
<OptionsContainer
26+
sourcegraphURL={'url'}
27+
fetchSite={fetchSite}
28+
setSourcegraphURL={noop}
29+
getConfigurableSettings={stubGetConfigurableSettings}
30+
setConfigurableSettings={stubSetConfigurableSettings}
31+
/>
32+
)
33+
34+
assert.isTrue(fetchSiteSpy.calledOnceWith('url'))
35+
})
36+
37+
it('handles when an error is thrown checking the site connection', () => {
38+
const fetchSite = () => {
39+
throw new Error('no site, woops')
40+
}
41+
42+
try {
43+
render(
44+
<OptionsContainer
45+
sourcegraphURL={'url'}
46+
fetchSite={fetchSite}
47+
setSourcegraphURL={noop}
48+
getConfigurableSettings={stubGetConfigurableSettings}
49+
setConfigurableSettings={stubSetConfigurableSettings}
50+
/>
51+
)
52+
} catch (err) {
53+
throw new Error("shouldn't be hit")
54+
}
55+
})
56+
57+
it('creates a token when no token exists after connection check', () => {})
58+
})

src/libs/options/OptionsContainer.tsx

Lines changed: 94 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,132 @@
1+
import { propertyIsDefined } from '@sourcegraph/codeintellify/lib/helpers'
12
import * as React from 'react'
2-
import { of, Subject, Subscription } from 'rxjs'
3-
import { catchError, map, switchMap, tap } from 'rxjs/operators'
3+
import { Observable, of, Subject, Subscription } from 'rxjs'
4+
import { catchError, filter, map, switchMap } from 'rxjs/operators'
45
import { getExtensionVersionSync } from '../../browser/runtime'
5-
import { ERAUTHREQUIRED, isErrorLike } from '../../shared/backend/errors'
6-
import { fetchSite } from '../../shared/backend/server'
7-
import { sourcegraphUrl } from '../../shared/util/context'
6+
import { AccessToken, FeatureFlags } from '../../browser/types'
7+
import { ERAUTHREQUIRED, ErrorLike, isErrorLike } from '../../shared/backend/errors'
8+
import { GQL } from '../../types/gqlschema'
89
import { OptionsMenu, OptionsMenuProps } from './Menu'
910
import { ConnectionErrors } from './ServerURLForm'
10-
import { getConfigurableSettings, setConfigurabelSettings, setSourcegraphURL } from './settings'
11+
12+
export interface OptionsContainerProps {
13+
sourcegraphURL: string
14+
15+
fetchSite: (url: string) => Observable<void>
16+
fetchCurrentUser: (useToken: boolean) => Observable<GQL.IUser | undefined>
17+
18+
setSourcegraphURL: (url: string) => void
19+
getConfigurableSettings: () => Observable<Partial<FeatureFlags>>
20+
setConfigurableSettings: (settings: Observable<Partial<FeatureFlags>>) => Observable<Partial<FeatureFlags>>
21+
22+
createAccessToken: (url: string) => Observable<AccessToken>
23+
getAccessToken: (url: string) => Observable<AccessToken | undefined>
24+
setAccessToken: (url: string) => (tokens: Observable<AccessToken>) => Observable<AccessToken>
25+
fetchAccessTokenIDs: (url: string) => Observable<Pick<AccessToken, 'id'>[]>
26+
}
1127

1228
interface OptionsContainerState
1329
extends Pick<
1430
OptionsMenuProps,
1531
'isSettingsOpen' | 'status' | 'sourcegraphURL' | 'settings' | 'settingsHaveChanged' | 'connectionError'
1632
> {}
1733

18-
export class OptionsContainer extends React.Component<{}, OptionsContainerState> {
19-
public state: OptionsContainerState = {
20-
status: 'connecting',
21-
sourcegraphURL: sourcegraphUrl,
22-
isSettingsOpen: false,
23-
settingsHaveChanged: false,
24-
settings: {},
25-
}
26-
34+
export class OptionsContainer extends React.Component<OptionsContainerProps, OptionsContainerState> {
2735
private version = getExtensionVersionSync()
2836

2937
private urlUpdates = new Subject<string>()
3038
private settingsSaves = new Subject<any>()
3139

3240
private subscriptions = new Subscription()
3341

34-
constructor(props: {}) {
42+
constructor(props: OptionsContainerProps) {
3543
super(props)
3644

45+
this.state = {
46+
status: 'connecting',
47+
sourcegraphURL: props.sourcegraphURL,
48+
isSettingsOpen: false,
49+
settingsHaveChanged: false,
50+
settings: {},
51+
connectionError: undefined,
52+
}
53+
54+
const fetchingSite: Observable<string | ErrorLike> = this.urlUpdates.pipe(
55+
switchMap(url => this.props.fetchSite(url).pipe(map(() => url))),
56+
catchError(err => of(err))
57+
)
58+
3759
this.subscriptions.add(
38-
this.urlUpdates
60+
fetchingSite.subscribe(res => {
61+
let url = ''
62+
63+
if (isErrorLike(res)) {
64+
this.setState({
65+
status: 'error',
66+
connectionError:
67+
res.code === ERAUTHREQUIRED ? ConnectionErrors.AuthError : ConnectionErrors.UnableToConnect,
68+
})
69+
url = this.state.sourcegraphURL
70+
} else {
71+
this.setState({ status: 'connected' })
72+
url = res
73+
}
74+
75+
props.setSourcegraphURL(url)
76+
})
77+
)
78+
79+
this.subscriptions.add(
80+
// Ensure the site is valid.
81+
fetchingSite
3982
.pipe(
40-
tap(a => {
41-
console.log('a', a)
42-
}),
43-
switchMap(url => fetchSite(url).pipe(map(() => url))),
44-
catchError(err => of(err))
83+
filter(urlOrError => !isErrorLike(urlOrError)),
84+
map(urlOrError => urlOrError as string),
85+
// Get the access token for this server if we have it.
86+
switchMap(url => this.props.getAccessToken(url).pipe(map(token => ({ token, url })))),
87+
switchMap(({ url, token }) =>
88+
this.props.fetchCurrentUser(false).pipe(map(user => ({ user, token, url })))
89+
),
90+
filter(propertyIsDefined('user')),
91+
// Get the IDs for all access tokens for the user.
92+
switchMap(({ token, user, url }) =>
93+
this.props
94+
.fetchAccessTokenIDs(user.id)
95+
.pipe(map(usersTokenIDs => ({ usersTokenIDs, user, token, url })))
96+
),
97+
// Make sure the token still exists on the server. If it
98+
// does exits, use it, otherwise create a new one.
99+
switchMap(({ user, token, usersTokenIDs, url }) => {
100+
const tokenExists = token && usersTokenIDs.map(({ id }) => id).includes(token.id)
101+
102+
return token && tokenExists
103+
? of(token)
104+
: this.props.createAccessToken(user.id).pipe(this.props.setAccessToken(url))
105+
})
45106
)
46-
.subscribe(res => {
47-
let url = ''
48-
49-
if (isErrorLike(res)) {
50-
this.setState({
51-
status: 'error',
52-
connectionError:
53-
res.code === ERAUTHREQUIRED
54-
? ConnectionErrors.AuthError
55-
: ConnectionErrors.UnableToConnect,
56-
})
57-
url = this.state.sourcegraphURL
58-
} else {
59-
this.setState({ status: 'connected' })
60-
url = res
61-
}
62-
63-
console.log(res, url)
64-
65-
setSourcegraphURL(url)
107+
.subscribe(() => {
108+
// We don't need to do anything with the token now. We just
109+
// needed to ensure we had one saved.
66110
})
67111
)
68112

69113
this.subscriptions.add(
70-
this.settingsSaves.pipe(switchMap(settings => setConfigurabelSettings(settings))).subscribe(settings => {
71-
this.setState({
72-
settings,
73-
settingsHaveChanged: false,
114+
this.settingsSaves
115+
.pipe(switchMap(settings => props.setConfigurableSettings(settings)))
116+
.subscribe(settings => {
117+
this.setState({
118+
settings,
119+
settingsHaveChanged: false,
120+
})
74121
})
75-
})
76122
)
77123
}
78124

79125
public componentDidMount(): void {
80-
getConfigurableSettings().subscribe(settings => {
126+
this.props.getConfigurableSettings().subscribe(settings => {
81127
this.setState({ settings })
82128
})
83129

84-
console.log('next url')
85130
this.urlUpdates.next(this.state.sourcegraphURL)
86131
}
87132

@@ -108,7 +153,6 @@ export class OptionsContainer extends React.Component<{}, OptionsContainerState>
108153
}
109154

110155
private handleURLSubmit = () => {
111-
console.log('submitted', this.state.sourcegraphURL)
112156
this.urlUpdates.next(this.state.sourcegraphURL)
113157
}
114158

src/libs/options/ServerURLForm.test.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@ import { assert, expect } from 'chai'
22
import { describe, it } from 'mocha'
33
import * as React from 'react'
44
import { cleanup, fireEvent, render } from 'react-testing-library'
5-
import { EMPTY, merge, of, Subject } from 'rxjs'
5+
import { EMPTY, merge, noop, of, Subject } from 'rxjs'
66
import { switchMap, tap } from 'rxjs/operators'
77
import { TestScheduler } from 'rxjs/testing'
8-
import * as sinon from 'sinon'
8+
import sinon from 'sinon'
99

1010
import { ServerURLForm, ServerURLFormProps } from './ServerURLForm'
1111

12-
const noop = () => {
13-
/* noop */
14-
}
15-
1612
describe('ServerURLForm', () => {
1713
after(cleanup)
1814

0 commit comments

Comments
 (0)