Skip to content

Commit

Permalink
Add boolean option and configuration for tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Zionts committed Jul 6, 2018
1 parent 0e329b9 commit e54f9be
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 44 deletions.
66 changes: 56 additions & 10 deletions packages/apollo-server-express/src/ApolloServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe('apollo-server-express', () => {
});
});

it('accepts dynamic GraphQL Playground Options', async () => {
it('accepts dynamic partial GraphQL Playground Options', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

Expand All @@ -197,9 +197,7 @@ describe('apollo-server-express', () => {
if (!!req.get('Foo')) {
return defaultGuiOptions;
} else {
return {
enabled: false,
};
return false;
}
},
},
Expand Down Expand Up @@ -230,7 +228,7 @@ describe('apollo-server-express', () => {
});
});

it('accepts dynamic GraphQL Playground Options 2', async () => {
it('accepts dynamic partial GraphQL Playground Options 2', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

Expand All @@ -244,9 +242,7 @@ describe('apollo-server-express', () => {
if (!!req.get('Foo')) {
return defaultGuiOptions;
} else {
return {
enabled: false,
};
return false;
}
},
},
Expand Down Expand Up @@ -281,17 +277,26 @@ describe('apollo-server-express', () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

const defaultQuery = 'query { foo { bar } }';
const endpoint = '/fumanchupacabra';
const { url } = await createServer(
{
typeDefs,
resolvers,
},
{
gui: {
enabled: true,
playgroundSettings: {
playgroundThemeOptions: {
'editor.theme': 'light',
},
playgroundTabOptions: [
{
query: defaultQuery,
},
{
endpoint,
},
],
},
},
);
Expand All @@ -312,8 +317,11 @@ describe('apollo-server-express', () => {
if (error) {
reject(error);
} else {
console.log('body', body);
expect(body).to.contain('GraphQLPlayground');
expect(body).to.contain(`"editor.theme": "light"`);
expect(body).to.contain(defaultQuery);
expect(body).to.contain(endpoint);
expect(response.statusCode).to.equal(200);
resolve();
}
Expand All @@ -322,6 +330,44 @@ describe('apollo-server-express', () => {
});
});

it('accepts gui options as a boolean', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

const { url } = await createServer(
{
typeDefs,
resolvers,
},
{
gui: false,
},
);

return new Promise<http.Server>((resolve, reject) => {
request(
{
url,
method: 'GET',
headers: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
},
},
(error, response, body) => {
process.env.NODE_ENV = nodeEnv;
if (error) {
reject(error);
} else {
expect(body).not.to.contain('GraphQLPlayground');
expect(response.statusCode).not.to.equal(200);
resolve();
}
},
);
});
});

it('accepts cors configuration', async () => {
const { url: uri } = await createServer(
{
Expand Down
96 changes: 62 additions & 34 deletions packages/apollo-server-express/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
renderPlaygroundPage,
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html';
import { ISettings } from '@apollographql/graphql-playground-html/dist/render-playground-page';
import {
ISettings,
Tab,
} from '@apollographql/graphql-playground-html/dist/render-playground-page';
import { ApolloServerBase, formatApolloErrors } from 'apollo-server-core';
import * as accepts from 'accepts';
import * as typeis from 'type-is';
Expand All @@ -30,17 +33,21 @@ export interface ServerRegistration {
bodyParserConfig?: OptionsJson | boolean;
onHealthCheck?: (req: express.Request) => Promise<any>;
disableHealthCheck?: boolean;
gui?: ((req: express.Request) => Partial<GuiOptions>) | Partial<GuiOptions>;
gui?: GuiConfig | boolean;
}

type GuiConfig =
| NonDynamicGuiConfig
| ((req: express.Request) => NonDynamicGuiConfig);
type NonDynamicGuiConfig = Partial<GuiOptions> | boolean;

export interface GuiOptions {
enabled: boolean;
playgroundSettings: Partial<ISettings>;
playgroundThemeOptions: Partial<ISettings>;
playgroundTabOptions: Partial<Tab>[];
}

export const defaultGuiOptions: GuiOptions = {
enabled: process.env.NODE_ENV !== 'production',
playgroundSettings: {
playgroundThemeOptions: {
'general.betaUpdates': false,
'editor.theme': 'dark',
'editor.reuseHeaders': true,
Expand All @@ -49,6 +56,7 @@ export const defaultGuiOptions: GuiOptions = {
'editor.fontFamily': `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace`,
'request.credentials': 'omit',
},
playgroundTabOptions: [],
};

const fileUploadMiddleware = (
Expand Down Expand Up @@ -162,36 +170,45 @@ export class ApolloServer extends ApolloServerBase {
// ApolloServer constructor; by default, the introspection query is only
// enabled in dev.
app.use(path, (req, res, next) => {
let partialGuiOverrides: Partial<GuiOptions>;
if (!gui) {
partialGuiOverrides = {};
} else if (isPartialGui(gui)) {
partialGuiOverrides = gui;
} else {
partialGuiOverrides = gui(req);
}
// We cannot do an existence check on `gui` since it may be a boolean
const nonDynamicGui: NonDynamicGuiConfig =
gui !== undefined && gui !== null
? isNonDynamic(gui)
? gui
: gui(req)
: {};
const enabled: boolean = isBoolean(nonDynamicGui) ? nonDynamicGui : true;
const partialGuiOverrides: Partial<GuiOptions> = isBoolean(nonDynamicGui)
? {}
: nonDynamicGui;

console.log('partial enabled', partialGuiOverrides.enabled);
const enabled =
partialGuiOverrides.enabled !== undefined
? partialGuiOverrides.enabled
: defaultGuiOptions.enabled;
// Disallow endpoints in pre-fill settings
if (
partialGuiOverrides.playgroundTabOptions &&
partialGuiOverrides.playgroundTabOptions.filter(setting => {
return !!setting.endpoint && setting.endpoint !== path;
}).length > 0
) {
// Should we just create the middlewares for any additional endpoints?
// throw Error(`Please create a middleware for each additional endpoint on which you'd like GraphQL playground to be available.`)
}

partialGuiOverrides.enabled
? partialGuiOverrides.enabled && defaultGuiOptions.enabled
: defaultGuiOptions.enabled;
console.log('overall enabled', enabled);
const guiOptions: GuiOptions = {
enabled,
playgroundSettings: enabled
playgroundThemeOptions: enabled
? {
...defaultGuiOptions.playgroundThemeOptions,
...partialGuiOverrides.playgroundThemeOptions,
}
: null,
playgroundTabOptions: enabled
? {
...defaultGuiOptions.playgroundSettings,
...partialGuiOverrides.playgroundSettings,
...defaultGuiOptions.playgroundTabOptions,
...partialGuiOverrides.playgroundTabOptions,
}
: null,
};

if (guiOptions.enabled && req.method === 'GET') {
if (enabled && req.method === 'GET') {
// perform more expensive content-type check only if necessary
// XXX We could potentially move this logic into the GuiOptions lambda,
// but I don't think it needs any overriding
Expand All @@ -207,7 +224,8 @@ export class ApolloServer extends ApolloServerBase {
endpoint: path,
subscriptionEndpoint: this.subscriptionsPath,
version: this.playgroundVersion,
settings: guiOptions.playgroundSettings as ISettings,
settings: guiOptions.playgroundThemeOptions as ISettings,
tabs: guiOptions.playgroundTabOptions as Tab[],
};
res.setHeader('Content-Type', 'text/html');
const playground = renderPlaygroundPage(playgroundRenderPageOptions);
Expand Down Expand Up @@ -235,11 +253,21 @@ export const registerServer = () => {
// XXX It would be great if there was a way to go through all properties of the parent to the Partial
// to perform this check, but that does not exist yet, so we will need to check each one of the properties
// of GuiOptions to see if there is anything set.
function isPartialGui(
gui: Partial<GuiOptions> | ((req: express.Request) => Partial<GuiOptions>),
): gui is Partial<GuiOptions> {
function isPartialGui(gui: NonDynamicGuiConfig): gui is Partial<GuiOptions> {
return (
(<Partial<GuiOptions>>gui).playgroundThemeOptions !== undefined ||
(<Partial<GuiOptions>>gui).playgroundTabOptions !== undefined
);
}

// This is necessary to make TypeScript happy, since it cannot smart-cast types via conditional logic
function isBoolean(gui: NonDynamicGuiConfig): gui is boolean {
return typeof gui === typeof true;
}

function isNonDynamic(gui: GuiConfig): gui is NonDynamicGuiConfig {
return (
(<Partial<GuiOptions>>gui).enabled !== undefined ||
(<Partial<GuiOptions>>gui).playgroundSettings !== undefined
isBoolean(<NonDynamicGuiConfig>gui) ||
isPartialGui(<NonDynamicGuiConfig>gui)
);
}

0 comments on commit e54f9be

Please sign in to comment.