Skip to content

Commit 0c72794

Browse files
authored
Display status of loading tasks for config and Engine stats (#536)
* Display status of loading tasks for config and Engine stats * Simplify config loading code * Remove unused import
1 parent b48e7bf commit 0c72794

6 files changed

Lines changed: 208 additions & 83 deletions

File tree

packages/apollo-language-server/copy-apollo-libs.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#!/bin/bash
22

3+
# When we copy the apollo-language-server into the extension, we also need to make sure
4+
# that the Lerna-managed dependencies are up to date with the local packages, not what's published
5+
# to NPM. So we copy all the local lib folders into the installed NPM packages to get them up to date
6+
# with what we have in the Lerna build.
7+
38
cp -r ../apollo-cli/lib ../apollo-vscode/server/node_modules/apollo
49
cp -r ../apollo-codegen-core/lib ../apollo-vscode/server/node_modules/apollo-codegen-core
510
cp -r ../apollo-codegen-flow-legacy/lib ../apollo-vscode/server/node_modules/apollo-codegen-flow-legacy

packages/apollo-language-server/src/languageProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
InsertTextFormat
1212
} from "vscode-languageserver";
1313

14+
// should eventually be moved into this package, since we're overriding a lot of the existing behavior here
1415
import { getAutocompleteSuggestions } from "@apollographql/graphql-language-service-interface";
1516
import {
1617
getTokenAtPosition,

packages/apollo-language-server/src/project.ts

Lines changed: 88 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export type DocumentUri = string;
5252
import "core-js/fn/array/flat-map";
5353
import { rangeForASTNode } from "./utilities/source";
5454
import { formatMS } from "./format";
55+
import { LoadingHandler } from "./server";
5556
declare global {
5657
interface Array<T> {
5758
flatMap<U>(
@@ -114,11 +115,22 @@ export class GraphQLProject {
114115
Map<string, Map<string, number>>
115116
> = new Map();
116117

117-
constructor(config: ApolloConfig, public configFile: string) {
118+
constructor(
119+
config: ApolloConfig,
120+
public configFile: string,
121+
private loadingHandler: LoadingHandler
122+
) {
118123
this.updateConfig(config);
119124
}
120125

121126
updateConfig(newConfig: ApolloConfig) {
127+
if (
128+
this.config &&
129+
JSON.stringify(this.config) === JSON.stringify(newConfig)
130+
) {
131+
return;
132+
}
133+
122134
this.needsValidation = true;
123135
this.config = newConfig;
124136

@@ -169,85 +181,96 @@ export class GraphQLProject {
169181
}
170182

171183
async loadEngineStats() {
172-
await Promise.all(
173-
Object.values(this.config.schemas!).map(async schemaDef => {
174-
if (schemaDef.engineKey) {
175-
const engineData = await toPromise(
176-
execute(engineLink, {
177-
query: engineStatsQuery,
178-
variables: {
179-
id: getIdFromKey(schemaDef.engineKey!)
180-
},
181-
context: {
182-
headers: { ["x-api-key"]: schemaDef.engineKey },
183-
...(this.config.engineEndpoint && {
184-
uri: this.config.engineEndpoint
185-
})
184+
await this.loadingHandler.handle(
185+
`Loading Apollo Engine stats for ${this.config.name!}`,
186+
Promise.all(
187+
Object.values(this.config.schemas!).map(async schemaDef => {
188+
if (schemaDef.engineKey) {
189+
const engineData = await toPromise(
190+
execute(engineLink, {
191+
query: engineStatsQuery,
192+
variables: {
193+
id: getIdFromKey(schemaDef.engineKey!)
194+
},
195+
context: {
196+
headers: { ["x-api-key"]: schemaDef.engineKey },
197+
...(this.config.engineEndpoint && {
198+
uri: this.config.engineEndpoint
199+
})
200+
}
201+
})
202+
);
203+
204+
const schemaEngineStats = new Map<string, Map<string, number>>();
205+
engineData.data!.service.report.usageStats.types.forEach(
206+
(typ: any) => {
207+
const fieldsMap = new Map<string, number>();
208+
typ.fields.forEach((field: any) => {
209+
fieldsMap.set(
210+
field.name,
211+
field.latencyHistogram.serviceTimeP95
212+
);
213+
});
214+
215+
schemaEngineStats.set(typ.name, fieldsMap);
186216
}
187-
})
188-
);
217+
);
189218

190-
const schemaEngineStats = new Map<string, Map<string, number>>();
191-
engineData.data!.service.report.usageStats.types.forEach(
192-
(typ: any) => {
193-
const fieldsMap = new Map<string, number>();
194-
typ.fields.forEach((field: any) => {
195-
fieldsMap.set(
196-
field.name,
197-
field.latencyHistogram.serviceTimeP95
198-
);
199-
});
200-
201-
schemaEngineStats.set(typ.name, fieldsMap);
202-
}
203-
);
204-
205-
this.engineStats.set(schemaDef.engineKey, schemaEngineStats);
219+
this.engineStats.set(schemaDef.engineKey, schemaEngineStats);
206220

207-
return;
208-
} else {
209-
return;
210-
}
211-
})
221+
return;
222+
} else {
223+
return;
224+
}
225+
})
226+
)
212227
);
213228

214229
return;
215230
}
216231

217232
async scanAllIncludedFiles() {
218-
console.time(`scanAllIncludedFiles - ${this.displayName}`);
219-
220-
this.documentSets = await resolveDocumentSets(this.config, true);
221-
222-
for (const set of this.documentSets) {
223-
if (!set.schema!.getQueryType()!.astNode) {
224-
const schemaSource = printSchema(set.schema!);
225-
226-
set.schema = buildSchema(
227-
new Source(
228-
schemaSource,
229-
`graphql-schema:/schema.graphql?${encodeURIComponent(schemaSource)}`
230-
)
231-
);
232-
}
233+
await this.loadingHandler.handle(
234+
`Loading queries and schemas for ${this.config.name!}`,
235+
(async () => {
236+
console.time(`scanAllIncludedFiles - ${this.displayName}`);
237+
this.documentSets = await resolveDocumentSets(this.config, true);
238+
239+
for (const set of this.documentSets) {
240+
if (!set.schema!.getQueryType()!.astNode) {
241+
const schemaSource = printSchema(set.schema!);
242+
243+
set.schema = buildSchema(
244+
// rebuild the schema from a generated source file and attach the source to a graphql-schema
245+
// URI that can be loaded as an in-memory file by VSCode
246+
new Source(
247+
schemaSource,
248+
`graphql-schema:/schema.graphql?${encodeURIComponent(
249+
schemaSource
250+
)}`
251+
)
252+
);
253+
}
233254

234-
this.setToResolved.set(set.originalSet, set);
255+
this.setToResolved.set(set.originalSet, set);
235256

236-
for (const filePath of set.documentPaths) {
237-
const uri = Uri.file(filePath).toString();
257+
for (const filePath of set.documentPaths) {
258+
const uri = Uri.file(filePath).toString();
238259

239-
// If we already have query documents for this file, that means it was either
240-
// opened or changed before we got a chance to read it.
241-
if (this.documentsByFile.has(uri)) continue;
260+
// If we already have query documents for this file, that means it was either
261+
// opened or changed before we got a chance to read it.
262+
if (this.documentsByFile.has(uri)) continue;
242263

243-
this.fileDidChange(uri, set);
244-
}
245-
}
264+
this.fileDidChange(uri, set);
265+
}
266+
}
246267

247-
console.timeEnd(`scanAllIncludedFiles - ${this.displayName}`);
268+
console.timeEnd(`scanAllIncludedFiles - ${this.displayName}`);
248269

249-
this.isReady = true;
250-
this.validateIfNeeded();
270+
this.isReady = true;
271+
this.validateIfNeeded();
272+
})()
273+
);
251274
}
252275

253276
fileDidChange(uri: DocumentUri, set?: ResolvedDocumentSet) {

packages/apollo-language-server/src/server.ts

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,60 @@ const connection = createConnection(ProposedFeatures.all);
3131

3232
let hasWorkspaceFolderCapability = false;
3333

34-
const workspace = new GraphQLWorkspace();
34+
export class LoadingHandler {
35+
private latestLoadingToken = 0;
36+
async handle<T>(message: string, value: Promise<T>): Promise<T> {
37+
const token = this.latestLoadingToken;
38+
this.latestLoadingToken += 1;
39+
connection.sendNotification(
40+
new NotificationType<any, void>("apollographql/loading"),
41+
{ message, token }
42+
);
43+
44+
try {
45+
const ret = await value;
46+
connection.sendNotification(
47+
new NotificationType<any, void>("apollographql/loadingComplete"),
48+
token
49+
);
50+
return ret;
51+
} catch (e) {
52+
connection.sendNotification(
53+
new NotificationType<any, void>("apollographql/loadingComplete"),
54+
token
55+
);
56+
connection.window.showErrorMessage(`Error in "${message}": ${e}`);
57+
throw e;
58+
}
59+
}
60+
61+
handleSync<T>(message: string, value: () => T): T {
62+
const token = this.latestLoadingToken;
63+
this.latestLoadingToken += 1;
64+
connection.sendNotification(
65+
new NotificationType<any, void>("apollographql/loading"),
66+
{ message, token }
67+
);
68+
69+
try {
70+
const ret = value();
71+
connection.sendNotification(
72+
new NotificationType<any, void>("apollographql/loadingComplete"),
73+
token
74+
);
75+
return ret;
76+
} catch (e) {
77+
connection.sendNotification(
78+
new NotificationType<any, void>("apollographql/loadingComplete"),
79+
token
80+
);
81+
connection.window.showErrorMessage(`Error in "${message}": ${e}`);
82+
throw e;
83+
}
84+
}
85+
}
86+
87+
const workspace = new GraphQLWorkspace(new LoadingHandler());
3588

3689
workspace.onDiagnostics(params => {
3790
connection.sendDiagnostics(params);
@@ -41,6 +94,21 @@ workspace.onDecorations(decs => {
4194
connection.sendNotification("apollographql/engineDecorations", decs);
4295
});
4396

97+
const hasInitializedPromise = new Promise(resolve => {
98+
connection.onInitialized(async () => {
99+
resolve();
100+
101+
if (hasWorkspaceFolderCapability) {
102+
connection.workspace.onDidChangeWorkspaceFolders(event => {
103+
event.removed.forEach(folder =>
104+
workspace.removeProjectsInFolder(folder)
105+
);
106+
event.added.forEach(folder => workspace.addProjectsInFolder(folder));
107+
});
108+
}
109+
});
110+
});
111+
44112
connection.onInitialize(async params => {
45113
let capabilities = params.capabilities;
46114
hasWorkspaceFolderCapability = !!(
@@ -49,7 +117,9 @@ connection.onInitialize(async params => {
49117

50118
const workspaceFolders = params.workspaceFolders;
51119
if (workspaceFolders) {
52-
workspaceFolders.forEach(folder => workspace.addProjectsInFolder(folder));
120+
hasInitializedPromise.then(() => {
121+
workspaceFolders.forEach(folder => workspace.addProjectsInFolder(folder));
122+
});
53123
}
54124

55125
return {
@@ -75,15 +145,6 @@ connection.onInitialize(async params => {
75145
};
76146
});
77147

78-
connection.onInitialized(async () => {
79-
if (hasWorkspaceFolderCapability) {
80-
connection.workspace.onDidChangeWorkspaceFolders(event => {
81-
event.removed.forEach(folder => workspace.removeProjectsInFolder(folder));
82-
event.added.forEach(folder => workspace.addProjectsInFolder(folder));
83-
});
84-
}
85-
});
86-
87148
const documents: TextDocuments = new TextDocuments();
88149

89150
// Make the text document manager listen on the connection

packages/apollo-language-server/src/workspace.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import * as fg from "glob";
1111
import { findAndLoadConfig } from "apollo/lib/config";
1212
import { GraphQLDocument } from "./document";
1313
import { Source, buildSchema } from "graphql";
14+
import { LoadingHandler } from "./server";
1415

1516
export class GraphQLWorkspace {
1617
private _onDiagnostics?: NotificationHandler<PublishDiagnosticsParams>;
1718
private _onDecorations?: (any: any) => void;
1819
public projectsByFolderUri: Map<string, GraphQLProject[]> = new Map();
1920

21+
constructor(private loadingHandler: LoadingHandler) {}
22+
2023
onDiagnostics(handler: NotificationHandler<PublishDiagnosticsParams>) {
2124
this._onDiagnostics = handler;
2225
}
@@ -46,19 +49,25 @@ export class GraphQLWorkspace {
4649

4750
const projectConfigs = Array.from(apolloConfigFolders).flatMap(
4851
configFolder => {
49-
try {
50-
return [findAndLoadConfig(configFolder, false, true)];
51-
} catch (e) {
52-
console.error(e);
53-
return [];
54-
}
52+
return this.loadingHandler.handleSync(
53+
`Loading Apollo Config in folder ${configFolder}`,
54+
() => {
55+
try {
56+
return [findAndLoadConfig(configFolder, false, true)];
57+
} catch (e) {
58+
console.error(e);
59+
return [];
60+
}
61+
}
62+
);
5563
}
5664
);
5765

5866
const projects = projectConfigs.map(projectConfig => {
5967
const project = new GraphQLProject(
6068
projectConfig,
61-
projectConfig.configFile
69+
projectConfig.configFile,
70+
this.loadingHandler
6271
);
6372

6473
project.onDiagnostics(params => {

0 commit comments

Comments
 (0)