From 98ad587dc30bb4321558bf1ce6dfbc613eb132dd Mon Sep 17 00:00:00 2001 From: timsuchanek Date: Wed, 30 Jan 2019 16:20:15 +0100 Subject: [PATCH 1/8] Make schema reloading more resilient. Closes #940 --- package.json | 3 +- .../src/components/Playground.tsx | 14 +++++--- .../components/Playground/SchemaFetcher.ts | 26 +++++++++++--- .../components/Playground/TopBar/Polling.tsx | 5 +++ .../src/components/PlaygroundWrapper.tsx | 2 -- .../src/state/sessions/reducers.ts | 34 ++++++++++++------- .../src/state/sessions/sagas.ts | 17 ++++++---- .../src/state/workspace/deserialize.ts | 7 +++- yarn.lock | 5 +++ 9 files changed, 81 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index a6f2ff252..c5b29dca9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "rimraf": "2.6.2" }, "dependencies": { - "dotenv": "^5.0.1" + "dotenv": "^5.0.1", + "hashlru": "^2.3.0" } } diff --git a/packages/graphql-playground-react/src/components/Playground.tsx b/packages/graphql-playground-react/src/components/Playground.tsx index 2b33a3d9b..8295899a3 100644 --- a/packages/graphql-playground-react/src/components/Playground.tsx +++ b/packages/graphql-playground-react/src/components/Playground.tsx @@ -173,6 +173,7 @@ export class Playground extends React.PureComponent { private backoff: Backoff private initialIndex: number = -1 private mounted = false + private initialSchemaFetch = true constructor(props: Props & ReduxProps) { super(props) @@ -270,11 +271,14 @@ export class Playground extends React.PureComponent { }) if (schema) { this.updateSchema(currentSchema, schema.schema, props) - this.props.schemaFetchingSuccess( - data.endpoint, - schema.tracingSupported, - props.isPollingSchema, - ) + if (this.initialSchemaFetch) { + this.props.schemaFetchingSuccess( + data.endpoint, + schema.tracingSupported, + props.isPollingSchema, + ) + this.initialSchemaFetch = false + } this.backoff.stop() } } catch (e) { diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts index 938fcd6d0..1e3e6a817 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts +++ b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts @@ -5,6 +5,9 @@ import { Map, set } from 'immutable' import { makeOperation } from './util/makeOperation' import { parseHeaders } from './util/parseHeaders' import { LinkCreatorProps } from '../../state/sessions/fetchingSagas' +import * as HLRU from 'hashlru' + +const LRU: any = HLRU export interface TracingSchemaTuple { schema: GraphQLSchema @@ -19,12 +22,14 @@ export interface SchemaFetchProps { export type LinkGetter = (session: LinkCreatorProps) => { link: ApolloLink } export class SchemaFetcher { - cache: Map + cache: any + schemaCache: any linkGetter: LinkGetter fetching: Map> subscriptions: Map void> = Map() constructor(linkGetter: LinkGetter) { - this.cache = Map() + this.cache = LRU(10) + this.schemaCache = LRU(10) this.fetching = Map() this.linkGetter = linkGetter } @@ -52,6 +57,19 @@ export class SchemaFetcher { hash(session: SchemaFetchProps) { return `${session.endpoint}~${session.headers || ''}` } + private getSchema(data: any) { + const schemaString = JSON.stringify(data) + const cachedSchema = this.schemaCache.get(schemaString) + if (cachedSchema) { + return cachedSchema + } + + const schema = buildClientSchema(data as any) + + this.schemaCache.set(schemaString, schema) + + return schema + } private fetchSchema( session: SchemaFetchProps, ): Promise<{ schema: GraphQLSchema; tracingSupported: boolean } | null> { @@ -83,7 +101,7 @@ export class SchemaFetcher { throw new NoSchemaError(endpoint) } - const schema = buildClientSchema(schemaData.data as any) + const schema = this.getSchema(schemaData.data as any) const tracingSupported = (schemaData.extensions && Boolean(schemaData.extensions.tracing)) || false @@ -91,7 +109,7 @@ export class SchemaFetcher { schema, tracingSupported, } - this.cache = this.cache.set(this.hash(session), result) + this.cache.set(this.hash(session), result) resolve(result) this.fetching = this.fetching.remove(hash) const subscription = this.subscriptions.get(hash) diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx index 395f7e861..dd7e27664 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx @@ -60,6 +60,11 @@ class SchemaPolling extends React.Component { if (!props.isReloadingSchema && this.state.windowVisible) { // timer starts only when introspection not in flight this.timer = setInterval(() => props.onReloadSchema(), props.interval) + } else { + // Check if polling is available again + setTimeout(() => { + this.updatePolling() + }, 5000) } } diff --git a/packages/graphql-playground-react/src/components/PlaygroundWrapper.tsx b/packages/graphql-playground-react/src/components/PlaygroundWrapper.tsx index 40b999d7c..7bf3c0445 100644 --- a/packages/graphql-playground-react/src/components/PlaygroundWrapper.tsx +++ b/packages/graphql-playground-react/src/components/PlaygroundWrapper.tsx @@ -427,10 +427,8 @@ class PlaygroundWrapper extends React.Component< handleSaveConfig = () => { /* tslint:disable-next-line */ - console.log('handleSaveConfig called') if (typeof this.props.onSaveConfig === 'function') { /* tslint:disable-next-line */ - console.log('calling this.props.onSaveConfig', this.state.configString) this.props.onSaveConfig(this.state.configString!) } } diff --git a/packages/graphql-playground-react/src/state/sessions/reducers.ts b/packages/graphql-playground-react/src/state/sessions/reducers.ts index 1d353b394..12d0a02a6 100644 --- a/packages/graphql-playground-react/src/state/sessions/reducers.ts +++ b/packages/graphql-playground-react/src/state/sessions/reducers.ts @@ -142,10 +142,12 @@ export class ResponseRecord extends Record({ resultID: '', date: '', time: new Date(), + isSchemaError: false, }) { resultID: string date: string time: Date + isSchemaError: boolean } function makeSession(endpoint = '') { @@ -317,8 +319,7 @@ const reducer = handleActions( if ( response && session.responses!.size === 1 && - ((response.date.includes('error') && !payload.isPollingSchema) || - response.date.includes('Failed to fetch')) + response.isSchemaError ) { data.responses = List([]) } @@ -344,21 +345,28 @@ const reducer = handleActions( SCHEMA_FETCHING_ERROR: (state, { payload }) => { const newSessions = state.get('sessions').map((session, sessionId) => { if (session.get('endpoint') === payload.endpoint) { + let { responses } = session + + // Only override the responses if there is one or zero and that one is a schemaError + // Don't override user's responses! + if (responses.size <= 1) { + let response = session.responses ? session.responses!.first() : null + if (!response || response.isSchemaError) { + response = new ResponseRecord({ + resultID: cuid(), + isSchemaError: true, + date: JSON.stringify(formatError(payload.error, true), null, 2), + time: new Date(), + }) + } + responses = List([response]) + } + return session.merge( Map({ isReloadingSchema: false, endpointUnreachable: true, - responses: List([ - new ResponseRecord({ - resultID: cuid(), - date: JSON.stringify( - formatError(payload.error, true), - null, - 2, - ), - time: new Date(), - }), - ]), + responses, }), ) } diff --git a/packages/graphql-playground-react/src/state/sessions/sagas.ts b/packages/graphql-playground-react/src/state/sessions/sagas.ts index 905e8ec03..dffb4eea1 100644 --- a/packages/graphql-playground-react/src/state/sessions/sagas.ts +++ b/packages/graphql-playground-react/src/state/sessions/sagas.ts @@ -18,10 +18,12 @@ import { setOperationName, schemaFetchingSuccess, schemaFetchingError, - fetchSchema, + // fetchSchema, runQuery, setTracingSupported, setQueryTypes, + refetchSchema, + fetchSchema, } from './actions' import { getRootMap, getNewStack } from '../../components/Playground/util/stack' import { DocsSessionState } from '../docs/reducers' @@ -137,8 +139,8 @@ function* getSessionWithCredentials() { function* fetchSchemaSaga() { const session: Session = yield getSessionWithCredentials() - yield schemaFetcher.fetch(session) try { + yield schemaFetcher.fetch(session) yield put( schemaFetchingSuccess( session.endpoint, @@ -155,8 +157,8 @@ function* fetchSchemaSaga() { function* refetchSchemaSaga() { const session: Session = yield getSessionWithCredentials() - yield schemaFetcher.refetch(session) try { + yield schemaFetcher.refetch(session) yield put( schemaFetchingSuccess( session.endpoint, @@ -167,23 +169,26 @@ function* refetchSchemaSaga() { } catch (e) { yield put(schemaFetchingError(session.endpoint)) yield call(delay, 5000) - yield put(fetchSchema()) + yield put(refetchSchema()) } } +let lastSchema + function* renewStacks() { const session: Session = yield select(getSelectedSession) const fetchSession = yield getSessionWithCredentials() const docs: DocsSessionState = yield select(getSessionDocsState) const result = yield schemaFetcher.fetch(fetchSession) const { schema, tracingSupported } = result - if (schema) { + if (schema && (!lastSchema || lastSchema !== schema)) { const rootMap = getRootMap(schema) const stacks = docs.navStack .map(stack => getNewStack(rootMap, schema, stack)) .filter(s => s) yield put(setStacks(session.id, stacks)) yield put(setTracingSupported(tracingSupported)) + lastSchema = schema } } @@ -211,7 +216,7 @@ function* prettifyQuery() { }) yield put(editQuery(prettyQuery)) } catch (e) { - // TODO show erros somewhere + // TODO show errors somewhere // tslint:disable-next-line console.log(e) } diff --git a/packages/graphql-playground-react/src/state/workspace/deserialize.ts b/packages/graphql-playground-react/src/state/workspace/deserialize.ts index ec73d0f87..99a0488bb 100644 --- a/packages/graphql-playground-react/src/state/workspace/deserialize.ts +++ b/packages/graphql-playground-react/src/state/workspace/deserialize.ts @@ -92,7 +92,11 @@ function deserializeSession(session) { } function deserializeResponses(responses) { - return List(responses.map(response => deserializeResponse(response))) + return List( + responses + .filter(r => r.isSchemaError) + .map(response => deserializeResponse(response)), + ) } function deserializeResponse(response) { @@ -100,6 +104,7 @@ function deserializeResponse(response) { resultID: response.resultID, date: response.date, time: new Date(response.time), + isSchemaError: response.isSchemaError || false, }) } diff --git a/yarn.lock b/yarn.lock index aca3a321e..b76f94ee5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -358,6 +358,11 @@ hapi@17.5.0: teamwork "3.x.x" topo "3.x.x" +hashlru@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/hashlru/-/hashlru-2.3.0.tgz#5dc15928b3f6961a2056416bb3a4910216fdfb51" + integrity sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A== + heavy@6.x.x: version "6.1.0" resolved "https://registry.yarnpkg.com/heavy/-/heavy-6.1.0.tgz#1bbfa43dc61dd4b543ede3ff87db8306b7967274" From 39fc10739ada78b897abdd62597076b481214f6d Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Jan 2019 10:32:18 -0800 Subject: [PATCH 2/8] =?UTF-8?q?Regarding=20"If=20there=E2=80=99s=20an=20ac?= =?UTF-8?q?tive=20introspection=20query,=20wait=20for=20it=E2=80=99s=20res?= =?UTF-8?q?ult=20before=20sending=20another=20one"=20is=20default=20behavi?= =?UTF-8?q?or=20when=20schema=20is=20reloading.=20The=20changes=20I=20made?= =?UTF-8?q?=20to=20account=20for=20this=20are=20unnecessary=20and=20actual?= =?UTF-8?q?ly=20cause=20polling=20to=20stop=20if=20the=20endpoint=20is=20u?= =?UTF-8?q?nreachable.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Playground/TopBar/Polling.tsx | 7 ++----- .../src/components/Playground/TopBar/SchemaReload.tsx | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx index 395f7e861..2bca34034 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx @@ -3,7 +3,6 @@ import PollingIcon from './PollingIcon' export interface Props { interval: number - isReloadingSchema: boolean onReloadSchema: () => void } @@ -47,9 +46,7 @@ class SchemaPolling extends React.Component { } } componentWillReceiveProps(nextProps: Props) { - if (nextProps.isReloadingSchema !== this.props.isReloadingSchema) { - this.updatePolling(nextProps) - } + this.updatePolling(nextProps) } render() { @@ -57,7 +54,7 @@ class SchemaPolling extends React.Component { } private updatePolling = (props: Props = this.props) => { this.clearTimer() - if (!props.isReloadingSchema && this.state.windowVisible) { + if (this.state.windowVisible) { // timer starts only when introspection not in flight this.timer = setInterval(() => props.onReloadSchema(), props.interval) } diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx index a56e678ed..67f6f451b 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx @@ -15,7 +15,6 @@ export default (props: Props) => { return ( ) From 4609819d51c453de53061c257e441d1ed9db0e6b Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 30 Jan 2019 13:14:50 -0800 Subject: [PATCH 3/8] getIsPollingSchema catching regex error --- .../src/state/sessions/selectors.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/graphql-playground-react/src/state/sessions/selectors.ts b/packages/graphql-playground-react/src/state/sessions/selectors.ts index 8430eae7c..c65b24397 100644 --- a/packages/graphql-playground-react/src/state/sessions/selectors.ts +++ b/packages/graphql-playground-react/src/state/sessions/selectors.ts @@ -71,11 +71,15 @@ export const getIsPollingSchema = createSelector( [getEndpoint, getSettings], (endpoint, settings) => { const json = JSON.parse(settings) - return ( - json['schema.polling.enable'] && - endpoint.match(`/${json['schema.polling.endpointFilter']}`) && - true - ) + try { + const isPolling = + json['schema.polling.enable'] && + endpoint.match(`/${json['schema.polling.endpointFilter']}`) && + true + return isPolling + } catch (e) { + return false + } }, ) From 293889cef1eeacb9489f6d99a050704c52252062 Mon Sep 17 00:00:00 2001 From: timsuchanek Date: Fri, 1 Feb 2019 11:20:34 +0100 Subject: [PATCH 4/8] add schema reference equality & printing cache --- package.json | 3 +-- .../graphql-playground-react/package.json | 2 ++ .../src/components/Playground.tsx | 18 +++++++++++++----- .../components/Playground/SchemaFetcher.ts | 14 ++++++-------- .../src/components/util.ts | 19 +++++++++++++++++++ packages/graphql-playground-react/yarn.lock | 10 ++++++++++ yarn.lock | 5 ----- 7 files changed, 51 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index c5b29dca9..a6f2ff252 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "rimraf": "2.6.2" }, "dependencies": { - "dotenv": "^5.0.1", - "hashlru": "^2.3.0" + "dotenv": "^5.0.1" } } diff --git a/packages/graphql-playground-react/package.json b/packages/graphql-playground-react/package.json index d5e8d9d43..e1ff85bb6 100644 --- a/packages/graphql-playground-react/package.json +++ b/packages/graphql-playground-react/package.json @@ -105,6 +105,7 @@ "why-did-you-update": "0.1.1" }, "dependencies": { + "@types/quick-lru": "^2.0.0", "apollo-link": "^1.0.7", "apollo-link-http": "^1.3.2", "apollo-link-ws": "1.0.8", @@ -128,6 +129,7 @@ "prettier": "^1.13.0", "prop-types": "^15.6.0", "query-string": "5", + "quick-lru": "^2.0.0", "react": "^16.3.1", "react-addons-shallow-compare": "^15.6.2", "react-codemirror": "^1.0.0", diff --git a/packages/graphql-playground-react/src/components/Playground.tsx b/packages/graphql-playground-react/src/components/Playground.tsx index 8295899a3..df2855370 100644 --- a/packages/graphql-playground-react/src/components/Playground.tsx +++ b/packages/graphql-playground-react/src/components/Playground.tsx @@ -27,7 +27,7 @@ import { } from '../state/sessions/actions' import { setConfigString } from '../state/general/actions' import { initState } from '../state/workspace/actions' -import { GraphQLSchema, printSchema } from 'graphql' +import { GraphQLSchema } from 'graphql' import { createStructuredSelector } from 'reselect' import { getIsConfigTab, @@ -50,6 +50,7 @@ import { getWorkspaceId } from './Playground/util/getWorkspaceId' import { getSettings, getSettingsString } from '../state/workspace/reducers' import { Backoff } from './Playground/util/fibonacci-backoff' import { debounce } from 'lodash' +import { cachedPrintSchema } from './util' export interface Response { resultID: string @@ -371,10 +372,17 @@ export class Playground extends React.PureComponent { props: Readonly<{ children?: React.ReactNode }> & Readonly, ) { - const currentSchemaStr = currentSchema ? printSchema(currentSchema) : null - const newSchemaStr = printSchema(newSchema) - if (newSchemaStr !== currentSchemaStr || !props.isPollingSchema) { - this.setState({ schema: newSchema }) + // first check for reference equality + if (currentSchema !== newSchema) { + // if references are not equal, do an equality check on the printed schema + const currentSchemaStr = currentSchema + ? cachedPrintSchema(currentSchema) + : null + const newSchemaStr = cachedPrintSchema(newSchema) + + if (newSchemaStr !== currentSchemaStr || !props.isPollingSchema) { + this.setState({ schema: newSchema }) + } } } diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts index 1e3e6a817..dd5061312 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts +++ b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts @@ -5,9 +5,7 @@ import { Map, set } from 'immutable' import { makeOperation } from './util/makeOperation' import { parseHeaders } from './util/parseHeaders' import { LinkCreatorProps } from '../../state/sessions/fetchingSagas' -import * as HLRU from 'hashlru' - -const LRU: any = HLRU +import * as LRU from 'quick-lru' export interface TracingSchemaTuple { schema: GraphQLSchema @@ -22,14 +20,14 @@ export interface SchemaFetchProps { export type LinkGetter = (session: LinkCreatorProps) => { link: ApolloLink } export class SchemaFetcher { - cache: any - schemaCache: any + cache: LRU + schemaCache: LRU linkGetter: LinkGetter fetching: Map> subscriptions: Map void> = Map() constructor(linkGetter: LinkGetter) { - this.cache = LRU(10) - this.schemaCache = LRU(10) + this.cache = new LRU({ maxSize: 10 }) + this.schemaCache = new LRU({ maxSize: 10 }) this.fetching = Map() this.linkGetter = linkGetter } @@ -105,7 +103,7 @@ export class SchemaFetcher { const tracingSupported = (schemaData.extensions && Boolean(schemaData.extensions.tracing)) || false - const result = { + const result: TracingSchemaTuple = { schema, tracingSupported, } diff --git a/packages/graphql-playground-react/src/components/util.ts b/packages/graphql-playground-react/src/components/util.ts index 2e818134e..8bf2710eb 100644 --- a/packages/graphql-playground-react/src/components/util.ts +++ b/packages/graphql-playground-react/src/components/util.ts @@ -1,4 +1,6 @@ import { GraphQLConfig, GraphQLConfigEnpointConfig } from '../graphqlConfig' +import { GraphQLSchema, printSchema } from 'graphql' +import * as LRU from 'quick-lru' export function getActiveEndpoints( config: GraphQLConfig, @@ -32,3 +34,20 @@ export function getEndpointFromEndpointConfig( } } } + +const printSchemaCache: LRU = new LRU({ maxSize: 10 }) +/** + * A cached version of `printSchema` + * @param schema GraphQLSchema instance + */ +export function cachedPrintSchema(schema: GraphQLSchema) { + const cachedString = printSchemaCache.get(schema) + if (cachedString) { + return cachedString + } + + const schemaString = printSchema(schema) + printSchemaCache.set(schema, schemaString) + + return schemaString +} diff --git a/packages/graphql-playground-react/yarn.lock b/packages/graphql-playground-react/yarn.lock index 4e03bb8a7..58360961b 100644 --- a/packages/graphql-playground-react/yarn.lock +++ b/packages/graphql-playground-react/yarn.lock @@ -66,6 +66,11 @@ version "10.1.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.4.tgz#606651d3f8a8bec08b8cb262161aab9209f4a29d" +"@types/quick-lru@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/quick-lru/-/quick-lru-2.0.0.tgz#04b3a558d45533ceef1be33950b7ed7e1202cd2f" + integrity sha512-bQLWvCkzO4OFPUFAQH5Ql0K8Q+9XH0uGbsODUXQlv9PfT+1hMVB++XZpiLDTKad35hu0KB2e9sYop/jVnkosVg== + "@types/react@16.3.14": version "16.3.14" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.14.tgz#f90ac6834de172e13ecca430dcb6814744225d36" @@ -6514,6 +6519,11 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" +quick-lru@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-2.0.0.tgz#32b017b28d1784631c8ab0a1ed2978e094dbe181" + integrity sha512-DqOtZziv7lDjEyuqyVQacRciAwMCEjTNrLYCHYEIIgjcE/tLEpBF82hiDIwCjRnEL9/hY2GJxA0T8ZvYvVVSSA== + raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" diff --git a/yarn.lock b/yarn.lock index b76f94ee5..aca3a321e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -358,11 +358,6 @@ hapi@17.5.0: teamwork "3.x.x" topo "3.x.x" -hashlru@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/hashlru/-/hashlru-2.3.0.tgz#5dc15928b3f6961a2056416bb3a4910216fdfb51" - integrity sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A== - heavy@6.x.x: version "6.1.0" resolved "https://registry.yarnpkg.com/heavy/-/heavy-6.1.0.tgz#1bbfa43dc61dd4b543ede3ff87db8306b7967274" From d17e15d602fa2fddcf05a4a344a2958bbede65d7 Mon Sep 17 00:00:00 2001 From: timsuchanek Date: Fri, 1 Feb 2019 11:40:09 +0100 Subject: [PATCH 5/8] improve code readability --- .../components/Playground/SchemaFetcher.ts | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts index dd5061312..dc8450cba 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts +++ b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts @@ -19,21 +19,53 @@ export interface SchemaFetchProps { export type LinkGetter = (session: LinkCreatorProps) => { link: ApolloLink } +/** + * The SchemaFetcher class servers the purpose of providing the GraphQLSchema. + * All sagas and every part of the UI is using this as a singleton to prevent + * unnecessary calls to the server. We're not storing this information in Redux, + * as it's a good practice to only store serializable data in Redux. + * GraphQLSchema objects are serializable, but can easily exceed the localStorage + * max. Another reason to keep this in a separate class is, that we have more + * advanced requirements like caching. + */ export class SchemaFetcher { - cache: LRU - schemaCache: LRU + /** + * The `sessionCache` property is used for UI components, that need fast access to the current schema. + * If the relevant information of the session didn't change (endpoint and headers), + * the cached schema will be returned. + */ + sessionCache: LRU + /** + * The `schemaInstanceCache` property is used to prevent unnecessary buildClientSchema calls. + * It's tested by stringifying the introspection result, which is orders of magnitude + * faster than rebuilding the schema. + */ + schemaInstanceCache: LRU + /** + * The `linkGetter` property is a callback that provides an ApolloLink instance. + * This can be overriden by the user. + */ linkGetter: LinkGetter + /** + * In order to prevent duplicate fetching of the same schema, we keep track + * of all subsequent calls to `.fetch` with the `fetching` property. + */ fetching: Map> + /** + * Other parts of the application can subscribe to change of a schema for a + * particular session. These subscribers are being kept track of in the + * `subscriptions` property + */ subscriptions: Map void> = Map() constructor(linkGetter: LinkGetter) { - this.cache = new LRU({ maxSize: 10 }) - this.schemaCache = new LRU({ maxSize: 10 }) + this.sessionCache = new LRU({ maxSize: 10 }) + this.schemaInstanceCache = new LRU({ maxSize: 10 }) this.fetching = Map() this.linkGetter = linkGetter } async fetch(session: SchemaFetchProps) { const hash = this.hash(session) - const cachedSchema = this.cache.get(hash) + const cachedSchema = this.sessionCache.get(hash) if (cachedSchema) { return cachedSchema } @@ -57,14 +89,14 @@ export class SchemaFetcher { } private getSchema(data: any) { const schemaString = JSON.stringify(data) - const cachedSchema = this.schemaCache.get(schemaString) + const cachedSchema = this.schemaInstanceCache.get(schemaString) if (cachedSchema) { return cachedSchema } const schema = buildClientSchema(data as any) - this.schemaCache.set(schemaString, schema) + this.schemaInstanceCache.set(schemaString, schema) return schema } @@ -107,7 +139,7 @@ export class SchemaFetcher { schema, tracingSupported, } - this.cache.set(this.hash(session), result) + this.sessionCache.set(this.hash(session), result) resolve(result) this.fetching = this.fetching.remove(hash) const subscription = this.subscriptions.get(hash) From 772aa353fd1e045f2127364bbe15d64d9a253266 Mon Sep 17 00:00:00 2001 From: timsuchanek Date: Fri, 1 Feb 2019 11:43:25 +0100 Subject: [PATCH 6/8] cleanup: remove dead code --- .../src/components/Playground/TopBar/Polling.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx index 1fac44325..2bca34034 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/Polling.tsx @@ -57,11 +57,6 @@ class SchemaPolling extends React.Component { if (this.state.windowVisible) { // timer starts only when introspection not in flight this.timer = setInterval(() => props.onReloadSchema(), props.interval) - } else { - // Check if polling is available again - setTimeout(() => { - this.updatePolling() - }, 5000) } } From 37e4fb793cbcfb7e04f00622ce78b00fd9f47f43 Mon Sep 17 00:00:00 2001 From: timsuchanek Date: Fri, 1 Feb 2019 12:15:34 +0100 Subject: [PATCH 7/8] fix build: use lru-cache. less unneeded repaints --- .../graphql-playground-react/package.json | 3 +- .../components/Playground/SchemaFetcher.ts | 10 ++--- .../Playground/TopBar/SchemaReload.tsx | 11 ++++- .../components/Playground/TopBar/TopBar.tsx | 4 -- .../src/state/sessions/reducers.ts | 42 +++++++++---------- packages/graphql-playground-react/yarn.lock | 15 +++---- 6 files changed, 40 insertions(+), 45 deletions(-) diff --git a/packages/graphql-playground-react/package.json b/packages/graphql-playground-react/package.json index e1ff85bb6..9b7af6571 100644 --- a/packages/graphql-playground-react/package.json +++ b/packages/graphql-playground-react/package.json @@ -105,7 +105,7 @@ "why-did-you-update": "0.1.1" }, "dependencies": { - "@types/quick-lru": "^2.0.0", + "@types/lru-cache": "^4.1.1", "apollo-link": "^1.0.7", "apollo-link-http": "^1.3.2", "apollo-link-ws": "1.0.8", @@ -129,7 +129,6 @@ "prettier": "^1.13.0", "prop-types": "^15.6.0", "query-string": "5", - "quick-lru": "^2.0.0", "react": "^16.3.1", "react-addons-shallow-compare": "^15.6.2", "react-codemirror": "^1.0.0", diff --git a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts index dc8450cba..51a1c8da5 100644 --- a/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts +++ b/packages/graphql-playground-react/src/components/Playground/SchemaFetcher.ts @@ -5,7 +5,7 @@ import { Map, set } from 'immutable' import { makeOperation } from './util/makeOperation' import { parseHeaders } from './util/parseHeaders' import { LinkCreatorProps } from '../../state/sessions/fetchingSagas' -import * as LRU from 'quick-lru' +import * as LRU from 'lru-cache' export interface TracingSchemaTuple { schema: GraphQLSchema @@ -34,13 +34,13 @@ export class SchemaFetcher { * If the relevant information of the session didn't change (endpoint and headers), * the cached schema will be returned. */ - sessionCache: LRU + sessionCache: LRU.Cache /** * The `schemaInstanceCache` property is used to prevent unnecessary buildClientSchema calls. * It's tested by stringifying the introspection result, which is orders of magnitude * faster than rebuilding the schema. */ - schemaInstanceCache: LRU + schemaInstanceCache: LRU.Cache /** * The `linkGetter` property is a callback that provides an ApolloLink instance. * This can be overriden by the user. @@ -58,8 +58,8 @@ export class SchemaFetcher { */ subscriptions: Map void> = Map() constructor(linkGetter: LinkGetter) { - this.sessionCache = new LRU({ maxSize: 10 }) - this.schemaInstanceCache = new LRU({ maxSize: 10 }) + this.sessionCache = new LRU({ max: 10 }) + this.schemaInstanceCache = new LRU({ max: 10 }) this.fetching = Map() this.linkGetter = linkGetter } diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx index 67f6f451b..cc48a429e 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/SchemaReload.tsx @@ -2,6 +2,9 @@ import * as React from 'react' import ReloadIcon from './Reload' import Polling from './Polling' import { ISettings } from '../../../types' +import { createStructuredSelector } from 'reselect' +import { getIsReloadingSchema } from '../../../state/sessions/selectors' +import { connect } from 'react-redux' export interface Props { isPollingSchema: boolean @@ -10,7 +13,7 @@ export interface Props { settings: ISettings } -export default (props: Props) => { +const SchemaReload = (props: Props) => { if (props.isPollingSchema) { return ( { /> ) } + +const mapStateToProps = createStructuredSelector({ + isReloadingSchema: getIsReloadingSchema, +}) + +export default connect(mapStateToProps)(SchemaReload) diff --git a/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx b/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx index e5ce6e211..1236efaeb 100644 --- a/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx +++ b/packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx @@ -8,7 +8,6 @@ import { createStructuredSelector } from 'reselect' import { getEndpoint, getSelectedSession, - getIsReloadingSchema, getEndpointUnreachable, getIsPollingSchema, } from '../../../state/sessions/selectors' @@ -29,7 +28,6 @@ export interface Props { endpoint: string shareEnabled?: boolean fixedEndpoint?: boolean - isReloadingSchema: boolean isPollingSchema: boolean endpointUnreachable: boolean @@ -83,7 +81,6 @@ class TopBar extends React.Component { @@ -157,7 +154,6 @@ class TopBar extends React.Component { const mapStateToProps = createStructuredSelector({ endpoint: getEndpoint, fixedEndpoint: getFixedEndpoint, - isReloadingSchema: getIsReloadingSchema, isPollingSchema: getIsPollingSchema, endpointUnreachable: getEndpointUnreachable, settings: getSettings, diff --git a/packages/graphql-playground-react/src/state/sessions/reducers.ts b/packages/graphql-playground-react/src/state/sessions/reducers.ts index 12d0a02a6..9989a608e 100644 --- a/packages/graphql-playground-react/src/state/sessions/reducers.ts +++ b/packages/graphql-playground-react/src/state/sessions/reducers.ts @@ -303,30 +303,26 @@ const reducer = handleActions( return state }, SCHEMA_FETCHING_SUCCESS: (state, { payload }) => { - const newSessions = state - .get('sessions') - .map((session: Session, sessionId) => { - if (session.endpoint === payload.endpoint) { - // if there was an error, clear it away - const data: any = { - tracingSupported: payload.tracingSupported, - isReloadingSchema: false, - endpointUnreachable: false, - } - const response = session.responses - ? session.responses!.first() - : null - if ( - response && - session.responses!.size === 1 && - response.isSchemaError - ) { - data.responses = List([]) - } - return session.merge(Map(data)) + const newSessions = state.get('sessions').map((session: Session) => { + if (session.endpoint === payload.endpoint) { + // if there was an error, clear it away + const data: any = { + tracingSupported: payload.tracingSupported, + isReloadingSchema: false, + endpointUnreachable: false, } - return session - }) + const response = session.responses ? session.responses!.first() : null + if ( + response && + session.responses!.size === 1 && + response.isSchemaError + ) { + data.responses = List([]) + } + return session.merge(Map(data)) + } + return session + }) return state.set('sessions', newSessions) }, SET_ENDPOINT_UNREACHABLE: (state, { payload }) => { diff --git a/packages/graphql-playground-react/yarn.lock b/packages/graphql-playground-react/yarn.lock index 58360961b..29a81100a 100644 --- a/packages/graphql-playground-react/yarn.lock +++ b/packages/graphql-playground-react/yarn.lock @@ -58,6 +58,11 @@ version "4.14.92" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.92.tgz#6e3cb0b71a1e12180a47a42a744e856c3ae99a57" +"@types/lru-cache@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-4.1.1.tgz#b2d87a5e3df8d4b18ca426c5105cd701c2306d40" + integrity sha512-8mNEUG6diOrI6pMqOHrHPDBB1JsrpedeMK9AWGzVCQ7StRRribiT9BRvUmF8aUws9iBbVlgVekOT5Sgzc1MTKw== + "@types/node@*": version "9.3.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5" @@ -66,11 +71,6 @@ version "10.1.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.4.tgz#606651d3f8a8bec08b8cb262161aab9209f4a29d" -"@types/quick-lru@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/quick-lru/-/quick-lru-2.0.0.tgz#04b3a558d45533ceef1be33950b7ed7e1202cd2f" - integrity sha512-bQLWvCkzO4OFPUFAQH5Ql0K8Q+9XH0uGbsODUXQlv9PfT+1hMVB++XZpiLDTKad35hu0KB2e9sYop/jVnkosVg== - "@types/react@16.3.14": version "16.3.14" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.14.tgz#f90ac6834de172e13ecca430dcb6814744225d36" @@ -6519,11 +6519,6 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" -quick-lru@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-2.0.0.tgz#32b017b28d1784631c8ab0a1ed2978e094dbe181" - integrity sha512-DqOtZziv7lDjEyuqyVQacRciAwMCEjTNrLYCHYEIIgjcE/tLEpBF82hiDIwCjRnEL9/hY2GJxA0T8ZvYvVVSSA== - raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" From 54c8bee03329a18cea763a66f1a6574e8923f0b1 Mon Sep 17 00:00:00 2001 From: timsuchanek Date: Fri, 1 Feb 2019 12:21:10 +0100 Subject: [PATCH 8/8] fix lru cache usage --- packages/graphql-playground-react/src/components/util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/graphql-playground-react/src/components/util.ts b/packages/graphql-playground-react/src/components/util.ts index 8bf2710eb..d5751c399 100644 --- a/packages/graphql-playground-react/src/components/util.ts +++ b/packages/graphql-playground-react/src/components/util.ts @@ -1,6 +1,6 @@ import { GraphQLConfig, GraphQLConfigEnpointConfig } from '../graphqlConfig' import { GraphQLSchema, printSchema } from 'graphql' -import * as LRU from 'quick-lru' +import * as LRU from 'lru-cache' export function getActiveEndpoints( config: GraphQLConfig, @@ -35,7 +35,7 @@ export function getEndpointFromEndpointConfig( } } -const printSchemaCache: LRU = new LRU({ maxSize: 10 }) +const printSchemaCache: LRU.Cache = new LRU({ max: 10 }) /** * A cached version of `printSchema` * @param schema GraphQLSchema instance