Skip to content

Commit

Permalink
Fixes: Schema Polling (#950)
Browse files Browse the repository at this point in the history
* Fixes:
* Enabling polling by default only for localhost with a new setting: `schema.polling.endpointFilter: "*localhost*"`  which is a glob. Move `schema.pollingEnabled` to `schema.polling.enable`
* If there’s an active introspection query, wait for it’s result before sending another one
* Allow polling interval to be set in the settings: `schema.polling.interval`

* tracing.hideTracingResponse default value to true

* fixed selector merge conflict

* fixed: Query execution must have precedence over polling - the Play button must not be affected by that.

* Fix: Pause polling when the tab is not focused

* renamed startPolling -> updatePolling
  • Loading branch information
rohit-ravikoti authored and huv1k committed Jan 30, 2019
1 parent cf922c8 commit 5fa7d83
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 36 deletions.
26 changes: 15 additions & 11 deletions packages/graphql-playground-react/src/components/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
getHeaders,
getIsReloadingSchema,
getEndpoint,
getIsPollingSchema,
} from '../state/sessions/selectors'
import { getHistoryOpen } from '../state/general/selectors'
import {
Expand Down Expand Up @@ -105,8 +106,13 @@ export interface ReduxProps {
injectHeaders: (headers: string, endpoint: string) => void
setConfigString: (str: string) => void
schemaFetchingError: (endpoint: string, error: string) => void
schemaFetchingSuccess: (endpoint: string, tracingSupported: boolean) => void
schemaFetchingSuccess: (
endpoint: string,
tracingSupported: boolean,
isPollingSchema: boolean,
) => void
isReloadingSchema: boolean
isPollingSchema: boolean
isConfigTab: boolean
isSettingsTab: boolean
isFile: boolean
Expand Down Expand Up @@ -143,11 +149,7 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
if (props.schema) {
return
}
if (
this.mounted &&
this.state.schema &&
!props.settings['schema.enablePolling']
) {
if (this.mounted && this.state.schema && !props.isPollingSchema) {
this.setState({ schema: undefined })
}
let first = true
Expand Down Expand Up @@ -268,7 +270,11 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
})
if (schema) {
this.updateSchema(currentSchema, schema.schema, props)
this.props.schemaFetchingSuccess(data.endpoint, schema.tracingSupported)
this.props.schemaFetchingSuccess(
data.endpoint,
schema.tracingSupported,
props.isPollingSchema,
)
this.backoff.stop()
}
} catch (e) {
Expand Down Expand Up @@ -363,10 +369,7 @@ export class Playground extends React.PureComponent<Props & ReduxProps, State> {
) {
const currentSchemaStr = currentSchema ? printSchema(currentSchema) : null
const newSchemaStr = printSchema(newSchema)
if (
newSchemaStr !== currentSchemaStr ||
!props.settings['schema.enablePolling']
) {
if (newSchemaStr !== currentSchemaStr || !props.isPollingSchema) {
this.setState({ schema: newSchema })
}
}
Expand All @@ -386,6 +389,7 @@ const mapStateToProps = createStructuredSelector({
settings: getSettings,
settingsString: getSettingsString,
isReloadingSchema: getIsReloadingSchema,
isPollingSchema: getIsPollingSchema,
sessionEndpoint: getEndpoint,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ISettings } from '../../../types'

export interface Props {
schema?: GraphQLSchema | null
isPollingSchema: boolean
getRef?: (ref: SDLEditor) => void
width?: number
sessionId?: string
Expand Down Expand Up @@ -81,7 +82,7 @@ class SDLEditor extends React.PureComponent<Props, { overflowY: boolean }> {
this.props.settings['schema.disableComments'],
),
)
if (this.props.settings['schema.enablePolling']) {
if (this.props.isPollingSchema) {
this.editor.scrollTo(initialScroll.left, initialScroll.top)
}
CodeMirror.signal(this.editor, 'change', this.editor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ import {
import Spinner from '../../Spinner'
import { columnWidth } from '../../../constants'
import { SideTabContentProps } from '../ExplorerTabs/SideTabs'
import { getSelectedSessionIdFromRoot } from '../../../state/sessions/selectors'
import {
getSelectedSessionIdFromRoot,
getIsPollingSchema,
} from '../../../state/sessions/selectors'
import { getSessionDocs } from '../../../state/docs/selectors'
import { createStructuredSelector } from 'reselect'
import { ErrorContainer } from '../DocExplorer/ErrorContainer'
import { SchemaExplorerContainer, SDLColumn } from './SDLTypes/SDLStyles'
import SDLHeader from './SDLHeader'
import SDLEditor from './SDLEditor'
import { getSettings } from '../../../state/workspace/reducers'
import { ISettings } from '../../../types'

interface StateFromProps {
docs: {
Expand All @@ -25,7 +29,8 @@ interface StateFromProps {
docsWidth: number
keyMove: boolean
}
settings
isPollingSchema: boolean
settings: ISettings
}

interface DispatchFromProps {
Expand Down Expand Up @@ -64,7 +69,7 @@ class SDLView extends React.Component<
}

render() {
const { schema, settings } = this.props
const { schema, settings, isPollingSchema } = this.props
let emptySchema
if (schema === undefined) {
// Schema is undefined when it is being loaded via introspection.
Expand All @@ -88,6 +93,7 @@ class SDLView extends React.Component<
<SDLEditor
schema={schema}
settings={settings}
isPollingSchema={isPollingSchema}
width={this.props.docs.docsWidth || columnWidth}
/>
</SDLColumn>
Expand All @@ -114,6 +120,7 @@ const mapStateToProps = createStructuredSelector({
settings: getSettings,
docs: getSessionDocs,
sessionId: getSelectedSessionIdFromRoot,
isPollingSchema: getIsPollingSchema,
})

export default connect<StateFromProps, DispatchFromProps, SideTabContentProps>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,64 @@ import * as React from 'react'
import PollingIcon from './PollingIcon'

export interface Props {
isPollingSchema: boolean
interval: number
isReloadingSchema: boolean
onReloadSchema: () => void
}

class SchemaPolling extends React.Component<Props> {
interface State {
windowVisible: boolean
}

class SchemaPolling extends React.Component<Props, State> {
timer: any
constructor(props) {
super(props)

this.state = {
windowVisible: true,
}
}
componentDidMount() {
this.startPolling()
this.updatePolling()
document.addEventListener('visibilitychange', this.setWindowVisibility)
}
componentWillUnmount() {
this.clearTimer()
document.removeEventListener('visibilitychange', this.setWindowVisibility)
}
setWindowVisibility = () => {
if (document.visibilityState === 'visible') {
this.setState(
{
windowVisible: true,
},
this.updatePolling,
)
}
if (document.visibilityState === 'hidden') {
this.setState(
{
windowVisible: false,
},
this.updatePolling,
)
}
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.isPollingSchema !== this.props.isPollingSchema) {
this.startPolling(nextProps)
if (nextProps.isReloadingSchema !== this.props.isReloadingSchema) {
this.updatePolling(nextProps)
}
}

render() {
return <PollingIcon animate={true} />
return <PollingIcon animate={this.state.windowVisible} />
}
private startPolling(props: Props = this.props) {
private updatePolling = (props: Props = this.props) => {
this.clearTimer()
if (props.isPollingSchema) {
this.timer = setInterval(() => props.onReloadSchema(), 2000)
if (!props.isReloadingSchema && this.state.windowVisible) {
// timer starts only when introspection not in flight
this.timer = setInterval(() => props.onReloadSchema(), props.interval)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import * as React from 'react'
import ReloadIcon from './Reload'
import PollingIcon from './Polling'
import Polling from './Polling'
import { ISettings } from '../../../types'

export interface Props {
isPollingSchema: boolean
isReloadingSchema: boolean
onReloadSchema: () => any
settings: ISettings
}

export default (props: Props) => {
if (props.isPollingSchema) {
return (
<PollingIcon
isPollingSchema={props.isPollingSchema}
<Polling
interval={props.settings['schema.polling.interval']}
isReloadingSchema={props.isReloadingSchema}
onReloadSchema={props.onReloadSchema}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getSelectedSession,
getIsReloadingSchema,
getEndpointUnreachable,
getIsPollingSchema,
} from '../../../state/sessions/selectors'
import { connect } from 'react-redux'
import { getFixedEndpoint } from '../../../state/general/selectors'
Expand All @@ -22,12 +23,14 @@ import {
import { share } from '../../../state/sharing/actions'
import { openHistory } from '../../../state/general/actions'
import { getSettings } from '../../../state/workspace/reducers'
import { ISettings } from '../../../types'

export interface Props {
endpoint: string
shareEnabled?: boolean
fixedEndpoint?: boolean
isReloadingSchema: boolean
isPollingSchema: boolean
endpointUnreachable: boolean

editEndpoint: (value: string) => void
Expand All @@ -36,7 +39,7 @@ export interface Props {
share: () => void
refetchSchema: () => void

settings
settings: ISettings
}

class TopBar extends React.Component<Props, {}> {
Expand Down Expand Up @@ -78,7 +81,8 @@ class TopBar extends React.Component<Props, {}> {
}}
>
<SchemaReload
isPollingSchema={settings['schema.enablePolling']}
settings={settings}
isPollingSchema={this.props.isPollingSchema}
isReloadingSchema={this.props.isReloadingSchema}
onReloadSchema={this.props.refetchSchema}
/>
Expand Down Expand Up @@ -154,6 +158,7 @@ const mapStateToProps = createStructuredSelector({
endpoint: getEndpoint,
fixedEndpoint: getFixedEndpoint,
isReloadingSchema: getIsReloadingSchema,
isPollingSchema: getIsPollingSchema,
endpointUnreachable: getEndpointUnreachable,
settings: getSettings,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,10 @@ export const {
REFETCH_SCHEMA: simpleAction(),
SET_ENDPOINT_UNREACHABLE: simpleAction('endpoint'),
SET_SCROLL_TOP: (sessionId, scrollTop) => ({ sessionId, scrollTop }),
SCHEMA_FETCHING_SUCCESS: (endpoint, tracingSupported) => ({
SCHEMA_FETCHING_SUCCESS: (endpoint, tracingSupported, isPollingSchema) => ({
endpoint,
tracingSupported,
isPollingSchema,
}),
/*
this.setState({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ const reducer = handleActions(
if (
response &&
session.responses!.size === 1 &&
(response.date.includes('error') ||
((response.date.includes('error') && !payload.isPollingSchema) ||
response.date.includes('Failed to fetch'))
) {
data.responses = List([])
Expand Down
18 changes: 15 additions & 3 deletions packages/graphql-playground-react/src/state/sessions/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
put,
} from 'redux-saga/effects'
import { delay } from 'redux-saga'
import { getSelectedSession } from './selectors'
import { getSelectedSession, getIsPollingSchema } from './selectors'
import getSelectedOperationName from '../../components/Playground/util/getSelectedOperationName'
import { getQueryFacts } from '../../components/Playground/util/getQueryFacts'
import { fromJS, is } from 'immutable'
Expand Down Expand Up @@ -139,7 +139,13 @@ function* fetchSchemaSaga() {
const session: Session = yield getSessionWithCredentials()
yield schemaFetcher.fetch(session)
try {
yield put(schemaFetchingSuccess(session.endpoint))
yield put(
schemaFetchingSuccess(
session.endpoint,
null,
yield select(getIsPollingSchema),
),
)
} catch (e) {
yield put(schemaFetchingError(session.endpoint))
yield call(delay, 5000)
Expand All @@ -151,7 +157,13 @@ function* refetchSchemaSaga() {
const session: Session = yield getSessionWithCredentials()
yield schemaFetcher.refetch(session)
try {
yield put(schemaFetchingSuccess(session.endpoint))
yield put(
schemaFetchingSuccess(
session.endpoint,
null,
yield select(getIsPollingSchema),
),
)
} catch (e) {
yield put(schemaFetchingError(session.endpoint))
yield call(delay, 5000)
Expand Down
11 changes: 11 additions & 0 deletions packages/graphql-playground-react/src/state/sessions/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ export const getCurrentQueryStartTime = makeSessionSelector(
)
export const getCurrentQueryEndTime = makeSessionSelector('currentQueryEndTime')
export const getIsReloadingSchema = makeSessionSelector('isReloadingSchema')
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
)
},
)

export const getResponseExtensions = makeSessionSelector('responseExtensions')
export const getQueryVariablesActive = makeSessionSelector(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export const defaultSettings: ISettings = {
'prettier.useTabs': false,
'request.credentials': 'omit',
'schema.disableComments': true,
'schema.enablePolling': true,
'schema.polling.enable': true,
'schema.polling.endpointFilter': '*localhost*',
'schema.polling.interval': 2000,
'tracing.hideTracingResponse': true,
}

Expand Down
4 changes: 3 additions & 1 deletion packages/graphql-playground-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface ISettings {
['prettier.useTabs']: boolean
['request.credentials']: 'omit' | 'include' | 'same-origin'
['schema.disableComments']: boolean
['schema.enablePolling']: boolean
['schema.polling.enable']: boolean
['schema.polling.endpointFilter']: string
['schema.polling.interval']: number
['tracing.hideTracingResponse']: boolean
}

0 comments on commit 5fa7d83

Please sign in to comment.