Skip to content

Commit 1b34e65

Browse files
authored
Support status bar command and service stats logging (#840)
* Added action to vscode to request stats * Linked status bar onclick to run stats action * Added stats printing utils for output pane * Added languageProvider method to request stats from projects * Added ability to run commands from a parent dir for monorepo support in editors * Change FileSet to accept and use URIs instead of string paths * Added configDirURI to the ApolloConfig class
1 parent fa7341a commit 1b34e65

14 files changed

Lines changed: 223 additions & 35 deletions

File tree

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as cosmiconfig from "cosmiconfig";
22
import { LoaderEntry } from "cosmiconfig";
33
import TypeScriptLoader from "@endemolshinegroup/cosmiconfig-typescript-loader";
4-
import { resolve } from "path";
4+
import { resolve, dirname } from "path";
55
import { readFileSync, existsSync } from "fs";
66
import { merge } from "lodash/fp";
77

@@ -153,13 +153,14 @@ export type ConfigResult<Config> = {
153153
// take a config with multiple project types and return
154154
// an array of individual types
155155
export const projectsFromConfig = (
156-
config: ApolloConfigFormat
156+
config: ApolloConfigFormat,
157+
configURI?: URI
157158
): Array<ClientConfig | ServiceConfig> => {
158159
const configs = [];
159160
const { client, service, ...rest } = config;
160161
// XXX use casting detection
161-
if (client) configs.push(new ClientConfig(config));
162-
if (service) configs.push(new ServiceConfig(config));
162+
if (client) configs.push(new ClientConfig(config, configURI));
163+
if (service) configs.push(new ServiceConfig(config, configURI));
163164
return configs;
164165
};
165166

@@ -204,8 +205,14 @@ export class ApolloConfig {
204205
this.service = rawConfig.service;
205206
}
206207

208+
get configDirURI() {
209+
return this.configURI && this.configURI.fsPath.includes(".js")
210+
? URI.parse(dirname(this.configURI.fsPath))
211+
: this.configURI;
212+
}
213+
207214
get projects() {
208-
return projectsFromConfig(this.rawConfig);
215+
return projectsFromConfig(this.rawConfig, this.configURI);
209216
}
210217

211218
set tag(tag: string) {

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,33 @@ import { relative } from "path";
22
import minimatch = require("minimatch");
33
import * as glob from "glob";
44
import { invariant } from "@apollographql/apollo-tools";
5+
import URI from "vscode-uri";
56

67
export class FileSet {
7-
private rootPath: string;
8+
private rootURI: URI;
89
private includes: string[];
910
private excludes: string[];
1011

1112
constructor({
12-
rootPath,
13+
rootURI,
1314
includes,
1415
excludes
1516
}: {
16-
rootPath: string;
17+
rootURI: URI;
1718
includes: string[];
1819
excludes: string[];
1920
}) {
20-
invariant(rootPath, `Must provide "rootPath".`);
21+
invariant(rootURI, `Must provide "rootURI".`);
2122
invariant(includes, `Must provide "includes".`);
2223
invariant(excludes, `Must provide "excludes".`);
2324

24-
this.rootPath = rootPath;
25+
this.rootURI = rootURI;
2526
this.includes = includes;
2627
this.excludes = excludes;
2728
}
2829

2930
includesFile(filePath: string): boolean {
30-
filePath = relative(this.rootPath, filePath);
31+
filePath = relative(this.rootURI.fsPath, filePath);
3132

3233
return (
3334
this.includes.some(include => minimatch(filePath, include)) &&
@@ -38,12 +39,12 @@ export class FileSet {
3839
allFiles(): string[] {
3940
return this.includes
4041
.flatMap(include =>
41-
glob.sync(include, { cwd: this.rootPath, absolute: true })
42+
glob.sync(include, { cwd: this.rootURI.fsPath, absolute: true })
4243
)
4344
.filter(
4445
filePath =>
4546
!this.excludes.some(exclude =>
46-
minimatch(relative(this.rootPath, filePath), exclude)
47+
minimatch(relative(this.rootURI.fsPath, filePath), exclude)
4748
)
4849
);
4950
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ function symbolForFieldDefinition(
9393
export class GraphQLLanguageProvider {
9494
constructor(public workspace: GraphQLWorkspace) {}
9595

96+
async provideStats(uri?: DocumentUri) {
97+
if (this.workspace.projects.length && uri) {
98+
const project = this.workspace.projectForFile(uri);
99+
return project ? project.getProjectStats() : { loaded: false };
100+
}
101+
102+
return { loaded: false };
103+
}
104+
96105
async provideCompletionItems(
97106
uri: DocumentUri,
98107
position: Position,

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { extname } from "path";
22
import { readFileSync } from "fs";
3-
import Uri from "vscode-uri";
3+
import URI from "vscode-uri";
44

55
import {
66
TypeSystemDefinitionNode,
@@ -48,6 +48,22 @@ export interface GraphQLProjectConfig {
4848
fileSet: FileSet;
4949
loadingHandler: LoadingHandler;
5050
}
51+
52+
export interface TypeStats {
53+
service?: number;
54+
client?: number;
55+
total?: number;
56+
}
57+
58+
export interface ProjectStats {
59+
type: string;
60+
loaded: boolean;
61+
serviceId?: string;
62+
types?: TypeStats;
63+
tag?: string;
64+
lastFetch?: number;
65+
}
66+
5167
export abstract class GraphQLProject implements GraphQLSchemaProvider {
5268
public schemaProvider: GraphQLSchemaProvider;
5369
protected _onDiagnostics?: NotificationHandler<PublishDiagnosticsParams>;
@@ -65,6 +81,8 @@ export abstract class GraphQLProject implements GraphQLSchemaProvider {
6581
private fileSet: FileSet;
6682
protected loadingHandler: LoadingHandler;
6783

84+
protected lastLoadDate?: number;
85+
6886
constructor({
6987
config,
7088
fileSet,
@@ -106,6 +124,8 @@ export abstract class GraphQLProject implements GraphQLSchemaProvider {
106124

107125
protected abstract initialize(): Promise<void>[];
108126

127+
abstract getProjectStats(): ProjectStats;
128+
109129
get isReady(): boolean {
110130
return this._isReady;
111131
}
@@ -124,10 +144,12 @@ export abstract class GraphQLProject implements GraphQLSchemaProvider {
124144
}
125145

126146
public resolveSchema(config: SchemaResolveConfig): Promise<GraphQLSchema> {
147+
this.lastLoadDate = +new Date();
127148
return this.schemaProvider.resolveSchema(config);
128149
}
129150

130151
public onSchemaChange(handler: NotificationHandler<GraphQLSchema>) {
152+
this.lastLoadDate = +new Date();
131153
return this.schemaProvider.onSchemaChange(handler);
132154
}
133155

@@ -136,15 +158,15 @@ export abstract class GraphQLProject implements GraphQLSchemaProvider {
136158
}
137159

138160
includesFile(uri: DocumentUri) {
139-
return this.fileSet.includesFile(Uri.parse(uri).fsPath);
161+
return this.fileSet.includesFile(URI.parse(uri).fsPath);
140162
}
141163

142164
async scanAllIncludedFiles() {
143165
await this.loadingHandler.handle(
144166
`Loading queries for ${this.displayName}`,
145167
(async () => {
146168
for (const filePath of this.fileSet.allFiles()) {
147-
const uri = Uri.file(filePath).toString();
169+
const uri = URI.file(filePath).toString();
148170

149171
// If we already have query documents for this file, that means it was either
150172
// opened or changed before we got a chance to read it.
@@ -157,7 +179,7 @@ export abstract class GraphQLProject implements GraphQLSchemaProvider {
157179
}
158180

159181
fileDidChange(uri: DocumentUri) {
160-
const filePath = Uri.parse(uri).fsPath;
182+
const filePath = URI.parse(uri).fsPath;
161183
const extension = extname(filePath);
162184
const languageId = fileAssociations[extension];
163185

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ export class GraphQLClientProject extends GraphQLProject {
9191
clientIdentity
9292
}: GraphQLClientProjectConfig) {
9393
const fileSet = new FileSet({
94-
rootPath: rootURI.fsPath,
94+
// the URI of the folder _containing_ the apollo.config.js is the true project's root.
95+
// if a config doesn't have a uri associated, we can assume the `rootURI` is the project's root.
96+
rootURI: config.configDirURI || rootURI,
9597
includes: config.client.includes,
9698
excludes: config.client.excludes
9799
});
@@ -111,6 +113,33 @@ export class GraphQLClientProject extends GraphQLProject {
111113
return [this.scanAllIncludedFiles(), this.loadServiceSchema()];
112114
}
113115

116+
public getProjectStats() {
117+
// use this to remove primitives and internal fields for stats
118+
const filterTypes = (type: string) =>
119+
!/^__|Boolean|ID|Int|String|Float/.test(type);
120+
121+
// filter out primitives and internal Types for type stats to match engine
122+
const serviceTypes = this.serviceSchema
123+
? Object.keys(this.serviceSchema.getTypeMap()).filter(filterTypes).length
124+
: 0;
125+
const totalTypes = this.schema
126+
? Object.keys(this.schema.getTypeMap()).filter(filterTypes).length
127+
: 0;
128+
129+
return {
130+
type: "client",
131+
serviceId: this.serviceID,
132+
types: {
133+
service: serviceTypes,
134+
client: totalTypes - serviceTypes,
135+
total: totalTypes
136+
},
137+
tag: this.config.tag,
138+
loaded: this.serviceID ? true : false,
139+
lastFetch: this.lastLoadDate
140+
};
141+
}
142+
114143
onDecorations(handler: (any: any) => void) {
115144
this._onDecorations = handler;
116145
}
@@ -213,6 +242,7 @@ export class GraphQLClientProject extends GraphQLProject {
213242
] = await engineClient.loadSchemaTagsAndFieldStats(serviceID);
214243
this._onSchemaTags && this._onSchemaTags([serviceID, schemaTags]);
215244
this.fieldStats = fieldStats;
245+
this.lastLoadDate = +new Date();
216246

217247
this.generateDecorations();
218248
})()

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class GraphQLServiceProject extends GraphQLProject {
2525
loadingHandler
2626
}: GraphQLServiceProjectConfig) {
2727
const fileSet = new FileSet({
28-
rootPath: rootURI.fsPath,
28+
rootURI: config.configDirURI || rootURI,
2929
includes: config.service.includes,
3030
excludes: config.service.excludes
3131
});
@@ -43,4 +43,8 @@ export class GraphQLServiceProject extends GraphQLProject {
4343
}
4444

4545
validate() {}
46+
47+
getProjectStats() {
48+
return { loaded: true, type: "service" };
49+
}
4650
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,5 +212,10 @@ connection.onNotification(
212212
(selection: QuickPickItem) => workspace.updateSchemaTag(selection)
213213
);
214214

215+
connection.onNotification("apollographql/getStats", async ({ uri }) => {
216+
const status = await languageProvider.provideStats(uri);
217+
connection.sendNotification("apollographql/statsLoaded", status);
218+
});
219+
215220
// Listen on the connection
216221
connection.listen();

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
NotificationHandler,
44
PublishDiagnosticsParams
55
} from "vscode-languageserver";
6-
import Uri from "vscode-uri";
76
import { QuickPickItem } from "vscode";
87

98
import { GraphQLProject, DocumentUri } from "./project/base";
@@ -54,13 +53,13 @@ export class GraphQLWorkspace {
5453
? new GraphQLClientProject({
5554
config,
5655
loadingHandler: this.LanguageServerLoadingHandler,
57-
rootURI: URI.file(folder.uri),
56+
rootURI: URI.parse(folder.uri),
5857
clientIdentity
5958
})
6059
: new GraphQLServiceProject({
6160
config: config as ServiceConfig,
6261
loadingHandler: this.LanguageServerLoadingHandler,
63-
rootURI: URI.file(folder.uri),
62+
rootURI: URI.parse(folder.uri),
6463
clientIdentity
6564
});
6665

@@ -98,14 +97,14 @@ export class GraphQLWorkspace {
9897
9998
*/
10099
const apolloConfigFiles: string[] = fg.sync("**/apollo.config.@(js|ts)", {
101-
cwd: Uri.parse(folder.uri).fsPath,
100+
cwd: URI.parse(folder.uri).fsPath,
102101
absolute: true,
103102
ignore: "**/node_modules/**"
104103
});
105104

106105
apolloConfigFiles.push(
107106
...fg.sync("**/package.json", {
108-
cwd: Uri.parse(folder.uri).fsPath,
107+
cwd: URI.parse(folder.uri).fsPath,
109108
absolute: true,
110109
ignore: "**/node_modules/**"
111110
})

packages/apollo/src/commands/client/codegen.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ import * as path from "path";
44
import { Kind, DocumentNode } from "graphql";
55
import * as tty from "tty";
66
import { Gaze } from "gaze";
7-
8-
import Uri from "vscode-uri";
7+
import URI from "vscode-uri";
98

109
import { TargetType, default as generate } from "../../generate";
1110

1211
import { ClientCommand } from "../../Command";
13-
import URI from "vscode-uri";
1412

1513
const waitForKey = async () => {
1614
console.log("Press any key to stop.");
@@ -218,7 +216,7 @@ export default class Generate extends ClientCommand {
218216
const watcher = new Gaze(this.project.config.client.includes);
219217
watcher.on("all", (event, file) => {
220218
// console.log("\nChange detected, generating types...");
221-
this.project.fileDidChange(Uri.file(file).toString());
219+
this.project.fileDidChange(URI.file(file).toString());
222220
});
223221
if (tty.isatty((process.stdin as any).fd)) {
224222
await waitForKey();

packages/apollo/src/generate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { fs } from "apollo-codegen-core/lib/localfs";
22
import * as path from "path";
33
import { GraphQLSchema, DocumentNode, print } from "graphql";
4-
import Uri from "vscode-uri";
4+
import URI from "vscode-uri";
55

66
import {
77
compileToIR,
@@ -42,7 +42,7 @@ export type GenerationOptions = CompilerOptions &
4242
};
4343

4444
function toPath(uri: string): string {
45-
return Uri.parse(uri).path;
45+
return URI.parse(uri).path;
4646
}
4747

4848
export default function generate(

0 commit comments

Comments
 (0)