Skip to content

Commit 66b6dcc

Browse files
committed
fix schema fetching
1 parent b03e4fe commit 66b6dcc

File tree

7 files changed

+147
-81
lines changed

7 files changed

+147
-81
lines changed

packages/graphql-playground-react/src/components/Playground.tsx

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
import { Session } from '../state/sessions/reducers'
4747
import { getWorkspaceId } from './Playground/util/getWorkspaceId'
4848
import { getSettings, getSettingsString } from '../state/workspace/reducers'
49+
import { Backoff } from './Playground/util/fibonacci-backoff'
4950

5051
export interface Response {
5152
resultID: string
@@ -95,7 +96,7 @@ export interface ReduxProps {
9596
setTracingSupported: (value: boolean) => void
9697
injectHeaders: (headers: string, endpoint: string) => void
9798
setConfigString: (str: string) => void
98-
schemaFetchingError: (endpoint: string) => void
99+
schemaFetchingError: (endpoint: string, error: string) => void
99100
schemaFetchingSuccess: (endpoint: string, tracingSupported: boolean) => void
100101
isConfigTab: boolean
101102
isSettingsTab: boolean
@@ -122,10 +123,10 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
122123
apolloLinks: { [sessionId: string]: any } = {}
123124
observers: { [sessionId: string]: any } = {}
124125
graphiqlComponents: any[] = []
126+
private backoff: Backoff
125127
private initialIndex: number = -1
126128
private mounted = false
127-
private retries = 0
128-
private maxRetries = 10
129+
private fetchingSchema = false
129130

130131
constructor(props: Props & ReduxProps) {
131132
super(props)
@@ -193,26 +194,38 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
193194
if (this.mounted && this.state.schema) {
194195
this.setState({ schema: undefined })
195196
}
196-
try {
197-
const schema = await schemaFetcher.fetch({
198-
endpoint: props.endpoint,
199-
headers: props.headers
200-
? JSON.stringify(props.headers)
201-
: props.sessionHeaders,
202-
})
203-
if (schema) {
204-
this.setState({ schema: schema.schema })
205-
this.props.schemaFetchingSuccess(
206-
props.endpoint,
207-
schema.tracingSupported,
208-
)
197+
let first = true
198+
this.backoff = new Backoff(async () => {
199+
if (first) {
200+
await this.schemaGetter(props)
201+
first = false
202+
} else {
203+
await this.schemaGetter()
209204
}
210-
} catch (e) {
211-
this.props.schemaFetchingError(props.endpoint)
212-
if (this.retries < this.maxRetries) {
213-
await new Promise(r => setTimeout(r, 5000))
214-
this.retries++
215-
this.getSchema(props)
205+
})
206+
this.backoff.start()
207+
}
208+
209+
async schemaGetter(props = this.props) {
210+
if (!this.fetchingSchema) {
211+
try {
212+
const data = {
213+
endpoint: props.endpoint,
214+
headers: props.headers
215+
? JSON.stringify(props.headers)
216+
: props.sessionHeaders,
217+
}
218+
const schema = await schemaFetcher.fetch(data)
219+
if (schema) {
220+
this.setState({ schema: schema.schema })
221+
this.props.schemaFetchingSuccess(
222+
props.endpoint,
223+
schema.tracingSupported,
224+
)
225+
this.backoff.stop()
226+
}
227+
} catch (e) {
228+
this.props.schemaFetchingError(props.endpoint, e.message)
216229
}
217230
}
218231
}

packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,31 @@ export type LinkGetter = (session: SchemaFetchProps) => { link: ApolloLink }
2020
export class SchemaFetcher {
2121
cache: Map<string, TracingSchemaTuple>
2222
linkGetter: LinkGetter
23+
fetching: Map<string, Promise<any>>
2324
constructor(linkGetter: LinkGetter) {
2425
this.cache = Map()
26+
this.fetching = Map()
2527
this.linkGetter = linkGetter
2628
}
2729
async fetch(session: SchemaFetchProps) {
2830
const hash = this.hash(session)
2931
const cachedSchema = this.cache.get(hash)
30-
return cachedSchema || this.fetchSchema(session)
32+
if (cachedSchema) {
33+
return cachedSchema
34+
}
35+
const fetching = this.fetching.get(hash)
36+
if (fetching) {
37+
return fetching
38+
}
39+
const promise = this.fetchSchema(session)
40+
this.fetching = this.fetching.set(hash, promise)
41+
return promise
3142
}
3243
refetch(session: SchemaFetchProps) {
3344
return this.fetchSchema(session)
3445
}
3546
hash(session: SchemaFetchProps) {
36-
const { endpoint, headers } = session
37-
return `${endpoint}~${headers}`
47+
return `${session.endpoint}~${session.headers}`
3848
}
3949
private fetchSchema(
4050
session: SchemaFetchProps,
@@ -45,8 +55,6 @@ export class SchemaFetcher {
4555
'X-Apollo-Tracing': '1',
4656
})
4757

48-
// const newSession = setIn<SchemaFetchProps>(session, ['headers'], headers)
49-
5058
const { link } = this.linkGetter(set(session, 'headers', headers))
5159

5260
const operation = makeOperation({ query: introspectionQuery })
@@ -63,15 +71,6 @@ export class SchemaFetcher {
6371
}
6472

6573
const schema = buildClientSchema(schemaData.data as any)
66-
/**
67-
* DANGER! THIS IS AN EXTREME HACK. As soon, as codemirror-graphql doesn't use getType in .hint anymore
68-
* this can be removed.
69-
*/
70-
// const oldGetType = schema.getType
71-
// schema.getType = type => {
72-
// const getTypeResult = oldGetType.call(schema, type)
73-
// return getTypeResult || type
74-
// }
7574
const tracingSupported =
7675
(schemaData.extensions && Boolean(schemaData.extensions.tracing)) ||
7776
false
@@ -81,9 +80,11 @@ export class SchemaFetcher {
8180
}
8281
this.cache = this.cache.set(this.hash(session), result)
8382
resolve(result)
83+
this.fetching = this.fetching.remove(this.hash(session))
8484
},
8585
error: err => {
86-
reject(JSON.stringify(err, null, 2))
86+
reject(err)
87+
this.fetching = this.fetching.remove(this.hash(session))
8788
},
8889
})
8990
})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { memoize } from 'lodash'
2+
const fibonacci = memoize(num => {
3+
if (num <= 1) {
4+
return 1
5+
}
6+
7+
return fibonacci(num - 1) + fibonacci(num - 2)
8+
})
9+
10+
export class Backoff {
11+
cb
12+
count = 1
13+
running = true
14+
timeout
15+
maxRetries = 20
16+
constructor(cb) {
17+
this.cb = cb
18+
}
19+
async start() {
20+
const fn = async () => {
21+
await this.cb()
22+
this.count++
23+
if (this.running && this.count < this.maxRetries) {
24+
setTimeout(fn, fibonacci(this.count) * 1000)
25+
}
26+
}
27+
fn()
28+
}
29+
stop() {
30+
this.running = false
31+
clearTimeout(this.timeout)
32+
}
33+
}

packages/graphql-playground-react/src/localDevIndex.tsx

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,43 @@ if (process.env.NODE_ENV !== 'production') {
1818
<MiddlewareApp
1919
setTitle={true}
2020
showNewWorkspace={false}
21-
config={config}
22-
configString={JSON.stringify(config, null, 2)}
21+
// config={config}
22+
// configString={JSON.stringify(config, null, 2)}
2323
{...options}
2424
/>,
2525
element,
2626
)
2727
},
2828
}
2929

30-
const config = {
31-
projects: {
32-
app: {
33-
schemaPath: 'src/schema.graphql',
34-
extensions: {
35-
endpoints: {
36-
default: 'http://localhost:4000',
37-
},
38-
},
39-
},
40-
database: {
41-
schemaPath: 'src/generated/prisma.graphql',
42-
extensions: {
43-
prisma: 'database/prisma.yml',
44-
'prepare-binding': {
45-
output: 'src/generated/prisma.ts',
46-
generator: 'prisma-ts',
47-
},
48-
endpoints: {
49-
dev: {
50-
url: 'https://eu1.prisma.sh/lol/hall/dev',
51-
headers: {
52-
Authorization:
53-
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJoYWxsQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MjE4MjIwNDQsImV4cCI6MTUyMjQyNjg0NH0.rxU4v6GeBOZxzSRP9-AeJbrmkcZBNKjecH7d43OEUqc',
54-
},
55-
},
56-
},
57-
},
58-
},
59-
},
60-
}
30+
// const config = {
31+
// projects: {
32+
// app: {
33+
// schemaPath: 'src/schema.graphql',
34+
// extensions: {
35+
// endpoints: {
36+
// default: 'http://localhost:4000',
37+
// },
38+
// },
39+
// },
40+
// database: {
41+
// schemaPath: 'src/generated/prisma.graphql',
42+
// extensions: {
43+
// prisma: 'database/prisma.yml',
44+
// 'prepare-binding': {
45+
// output: 'src/generated/prisma.ts',
46+
// generator: 'prisma-ts',
47+
// },
48+
// endpoints: {
49+
// dev: {
50+
// url: 'https://eu1.prisma.sh/lol/hall/dev',
51+
// headers: {
52+
// Authorization:
53+
// 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJoYWxsQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MjE4MjIwNDQsImV4cCI6MTUyMjQyNjg0NH0.rxU4v6GeBOZxzSRP9-AeJbrmkcZBNKjecH7d43OEUqc',
54+
// },
55+
// },
56+
// },
57+
// },
58+
// },
59+
// },
60+
// }

packages/graphql-playground-react/src/state/sessions/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export const {
126126
+ tracingSupported
127127
})
128128
*/
129-
SCHEMA_FETCHING_ERROR: endpoint => ({ endpoint }),
129+
SCHEMA_FETCHING_ERROR: (endpoint, error) => ({ endpoint, error }),
130130
/*
131131
132132
this.setState({

packages/graphql-playground-react/src/state/sessions/reducers.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -255,18 +255,30 @@ export default handleActions(
255255
})
256256
},
257257
SCHEMA_FETCHING_SUCCESS: (state, { payload }) => {
258-
const newSessions = state.get('sessions').map((session, sessionId) => {
259-
if (session.get('endpoint') === payload.endpoint) {
260-
return session.merge(
261-
Map({
258+
const newSessions = state
259+
.get('sessions')
260+
.map((session: Session, sessionId) => {
261+
if (session.endpoint === payload.endpoint) {
262+
// if there was an error, clear it away
263+
const data: any = {
262264
tracingSupported: payload.tracingSupported,
263265
isReloadingSchema: false,
264266
endpointUnreachable: false,
265-
}),
266-
)
267-
}
268-
return session
269-
})
267+
}
268+
const response = session.responses
269+
? session.responses!.first()
270+
: null
271+
if (
272+
response &&
273+
session.responses!.size === 1 &&
274+
response.date.includes('error')
275+
) {
276+
data.responses = List([])
277+
}
278+
return session.merge(Map(data))
279+
}
280+
return session
281+
})
270282
return state.set('sessions', newSessions)
271283
},
272284
SCHEMA_FETCHING_ERROR: (state, { payload }) => {
@@ -276,6 +288,13 @@ export default handleActions(
276288
Map({
277289
isReloadingSchema: false,
278290
endpointUnreachable: true,
291+
responses: List([
292+
new ResponseRecord({
293+
resultID: cuid(),
294+
date: payload.error,
295+
time: new Date(),
296+
}),
297+
]),
279298
}),
280299
)
281300
}

packages/graphql-playground-react/src/state/sessions/sagas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function* runQueryAtPosition(action) {
9797

9898
function* fetchSchemaSaga() {
9999
const session: Session = yield select(getSelectedSession)
100-
yield schemaFetcher.refetch(session)
100+
yield schemaFetcher.fetch(session)
101101
try {
102102
yield put(schemaFetchingSuccess(session.endpoint))
103103
} catch (e) {

0 commit comments

Comments
 (0)