Skip to content

Commit 611930a

Browse files
authored
[VS Code] Edit missing variables / auth (#9301)
1 parent 41b7642 commit 611930a

File tree

20 files changed

+511
-407
lines changed

20 files changed

+511
-407
lines changed

firebase-vscode/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ resources/dist
66
.wdio-vscode-service
77
logs
88
!*.tgz
9-
prebuilt-extensions
9+
prebuilt-extensions
10+
data-connect-test-*/

firebase-vscode/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## NEXT
22

3+
- [Fixed] Populate correct default values of missing required variables.
4+
- [Added] Display the execution variables and auth params used.
5+
- [Added] Allow rerun any executions in the history.
6+
37
## 1.9.0
48

59
- [Added] Refine / Generate Operation Code Lens.

firebase-vscode/common/messaging/protocol.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,21 @@ import { EmulatorsStatus, RunningEmulatorInfo } from "./types";
1111
import { ExecutionResult } from "graphql";
1212
import { SerializedError } from "../error";
1313

14-
export enum UserMockKind {
14+
export enum AuthParamsKind {
1515
ADMIN = "admin",
1616
UNAUTHENTICATED = "unauthenticated",
1717
AUTHENTICATED = "authenticated",
1818
}
19-
export type UserMock =
20-
| { kind: UserMockKind.ADMIN | UserMockKind.UNAUTHENTICATED }
19+
20+
export const EXAMPLE_CLAIMS = `{
21+
"email_verified": true,
22+
"sub": "exampleUserId"
23+
}`;
24+
25+
export type AuthParams =
26+
| { kind: AuthParamsKind.ADMIN | AuthParamsKind.UNAUTHENTICATED }
2127
| {
22-
kind: UserMockKind.AUTHENTICATED;
28+
kind: AuthParamsKind.AUTHENTICATED;
2329
claims: string;
2430
};
2531

@@ -84,13 +90,13 @@ export interface WebviewToExtensionParamsMap {
8490

8591
selectEmulatorImportFolder: {};
8692

87-
definedDataConnectArgs: string;
93+
/** Execution parameters */
94+
defineVariables: string;
95+
defineAuthParams: AuthParams;
8896

8997
/** Prompts the user to select a directory in which to place the quickstart */
9098
chooseQuickstartDir: {};
9199

92-
notifyAuthUserMockChange: UserMock;
93-
94100
/** Deploy connectors/services to production */
95101
"fdc.deploy": void;
96102

@@ -130,10 +136,11 @@ export interface WebviewToExtensionParamsMap {
130136
}
131137

132138
export interface DataConnectResults {
133-
query: string;
134139
displayName: string;
135-
results?: ExecutionResult | SerializedError;
136-
args?: string;
140+
query: string;
141+
results: ExecutionResult | SerializedError;
142+
variables: string;
143+
auth: AuthParams;
137144
}
138145

139146
export type ValueOrError<T> =
@@ -185,13 +192,11 @@ export interface ExtensionToWebviewParamsMap {
185192
*/
186193
notifyPreviewChannelResponse: { id: string };
187194

188-
// data connect specific
189-
notifyDataConnectArgs: string;
190-
195+
/** Update execution parameters and results panels */
196+
notifyVariables: { variables: string, fixes: string[] };
197+
notifyAuthParams: AuthParams;
191198
notifyDataConnectResults: DataConnectResults;
192199

193-
notifyLastOperation: string;
194-
195200
notifyIsLoadingUser: boolean;
196201

197202
notifyDocksLink: string;

firebase-vscode/package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,21 @@
158158
"firebase-data-connect-execution-view": [
159159
{
160160
"type": "webview",
161-
"id": "data-connect-execution-configuration",
162-
"name": "Configuration",
163-
"when": "firebase-vscode.fdc.enabled"
164-
},
165-
{
166-
"id": "data-connect-execution-history",
167-
"name": "History",
161+
"id": "data-connect-execution-parameters",
162+
"name": "Parameters",
168163
"when": "firebase-vscode.fdc.enabled"
169164
},
170165
{
171166
"type": "webview",
172167
"id": "data-connect-execution-results",
173168
"name": "Results",
174169
"when": "firebase-vscode.fdc.enabled"
170+
},
171+
{
172+
"id": "data-connect-execution-history",
173+
"name": "History",
174+
"when": "firebase-vscode.fdc.enabled",
175+
"visibility": "collapsed"
175176
}
176177
]
177178
},

firebase-vscode/src/analytics.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export enum DATA_CONNECT_EVENT_NAME {
2525
RUN_PROD_MUTATION_WARNING_REJECTED = "run_prod_mutation_warning_rejected",
2626
RUN_PROD_MUTATION_WARNING_ACKED = "run_prod_mutation_warning_acked",
2727
RUN_PROD_MUTATION_WARNING_ACKED_ALWAYS = "run_prod_mutation_warning_acked_always",
28-
MISSING_VARIABLES = "missing_variables",
28+
RUN_AUTH_ADMIN = "run_auth_admin",
29+
RUN_AUTH_UNAUTHENTICATED = "run_auth_unauthenticated",
30+
RUN_AUTH_AUTHENTICATED = "run_auth_authenticated",
31+
RUN_UNDEFINED_VARIABLES = "run_undefined_variables",
32+
RUN_MISSING_VARIABLES = "run_missing_variables",
2933
GENERATE_OPERATION = "generate_operation",
3034
GIF_TOS_MODAL = "gif_tos_modal",
3135
GIF_TOS_MODAL_ACKED = "gif_tos_modal_acked",

firebase-vscode/src/auth/service.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

firebase-vscode/src/data-connect/ad-hoc-mutations.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,16 +284,16 @@ function getDefaultScalarValueNode(type: string): ValueNode | undefined {
284284
}
285285
}
286286

287-
export function getDefaultScalarValue(type: string): string {
287+
export function getDefaultScalarValue(type: string): any {
288288
switch (type) {
289289
case "Boolean":
290-
return "false";
290+
return false;
291291
case "Date":
292292
return new Date().toISOString().substring(0, 10);
293293
case "Float":
294-
return "0";
294+
return 0.0;
295295
case "Int":
296-
return "0";
296+
return 0;
297297
case "Int64":
298298
return "0";
299299
case "String":
@@ -303,8 +303,8 @@ export function getDefaultScalarValue(type: string): string {
303303
case "UUID":
304304
return "11111111222233334444555555555555";
305305
case "Vector":
306-
return "[]";
306+
return [1.1, 2.2, 3.3];
307307
default:
308-
return "";
308+
return undefined;
309309
}
310310
}

firebase-vscode/src/data-connect/code-lens-provider.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import * as vscode from "vscode";
22
import { Kind, parse } from "graphql";
3-
import { OperationLocation } from "./types";
43
import { Disposable } from "vscode";
54

65
import { Signal } from "@preact/signals-core";
76
import { dataConnectConfigs, firebaseRC } from "./config";
87
import { EmulatorsController } from "../core/emulators";
9-
import { GenerateOperationInput } from "./execution/execution";
8+
import { ExecutionInput, GenerateOperationInput } from "./execution/execution";
109
import { findCommentsBlocks } from "../utils/find_comments";
1110

1211
export enum InstanceType {
@@ -91,31 +90,40 @@ export class OperationCodeLensProvider extends ComputedCodeLensProvider {
9190
const line = x.loc.startToken.line - 1;
9291
const range = new vscode.Range(line, 0, line, 0);
9392
const position = new vscode.Position(line, 0);
94-
const operationLocation: OperationLocation = {
95-
document: documentText,
96-
documentPath: document.fileName,
97-
position: position,
98-
};
99-
const service = fdcConfigs.findEnclosingServiceForPath(
100-
document.fileName,
101-
);
93+
const service = fdcConfigs.findEnclosingServiceForPath(document.fileName);
10294
if (service) {
103-
codeLenses.push(
104-
new vscode.CodeLens(range, {
105-
title: `$(play) Run (local)`,
106-
command: "firebase.dataConnect.executeOperation",
107-
tooltip: "Execute the operation (⌘+enter or Ctrl+Enter)",
108-
arguments: [x, operationLocation, InstanceType.LOCAL],
109-
}),
110-
);
95+
{
96+
const arg: ExecutionInput = {
97+
operationAst: x,
98+
document: documentText,
99+
documentPath: document.fileName,
100+
position: position,
101+
instance: InstanceType.LOCAL,
102+
};
103+
codeLenses.push(
104+
new vscode.CodeLens(range, {
105+
title: `$(play) Run (local)`,
106+
command: "firebase.dataConnect.executeOperation",
107+
tooltip: "Execute the operation (⌘+enter or Ctrl+Enter)",
108+
arguments: [arg],
109+
}),
110+
);
111+
}
111112

112113
if (projectId) {
114+
const arg: ExecutionInput = {
115+
operationAst: x,
116+
document: documentText,
117+
documentPath: document.fileName,
118+
position: position,
119+
instance: InstanceType.PRODUCTION,
120+
};
113121
codeLenses.push(
114122
new vscode.CodeLens(range, {
115123
title: `$(play) Run (Production – Project: ${projectId})`,
116124
command: "firebase.dataConnect.executeOperation",
117125
tooltip: "Execute the operation (⌘+enter or Ctrl+Enter)",
118-
arguments: [x, operationLocation, InstanceType.PRODUCTION],
126+
arguments: [arg],
119127
}),
120128
);
121129
}

firebase-vscode/src/data-connect/execution/execution-history-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class ExecutionTreeItem extends vscode.TreeItem {
1818
this.item = item;
1919

2020
// Renders arguments in a single line
21-
const prettyArgs = this.item.args?.replaceAll(/[\n \t]+/g, " ");
21+
const prettyArgs = this.item.variables?.replaceAll(/[\n \t]+/g, " ");
2222
this.description = `${timeFormatter.format(
2323
item.timestamp
2424
)} | Arguments: ${prettyArgs}`;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { print, Kind, OperationDefinitionNode } from "graphql";
2+
import { globalSignal } from "../../utils/globals";
3+
import { getDefaultScalarValue } from "../ad-hoc-mutations";
4+
import { AuthParams, AuthParamsKind } from "../../../common/messaging/protocol";
5+
import { Impersonation } from "../../dataconnect/types";
6+
import { Disposable } from "vscode";
7+
import { ExtensionBrokerImpl } from "../../extension-broker";
8+
import { AnalyticsLogger, DATA_CONNECT_EVENT_NAME } from "../../analytics";
9+
10+
/**
11+
* Contains the unparsed JSON object mutation/query variables.
12+
* The JSON may be invalid.
13+
*/
14+
export const executionVarsJSON = globalSignal("{}");
15+
export const executionAuthParams = globalSignal<AuthParams>({kind: AuthParamsKind.ADMIN});
16+
17+
export class ExecutionParamsService implements Disposable {
18+
constructor(readonly broker: ExtensionBrokerImpl, readonly analyticsLogger: AnalyticsLogger) {
19+
this.disposable.push({
20+
dispose: broker.on(
21+
"defineAuthParams",
22+
(auth) => (executionAuthParams.value = auth)
23+
),
24+
});
25+
this.disposable.push({
26+
dispose: broker.on(
27+
"defineVariables",
28+
(value) => (executionVarsJSON.value = value),
29+
)
30+
});
31+
}
32+
33+
disposable: Disposable[] = [];
34+
35+
dispose() {
36+
for (const disposable of this.disposable) {
37+
disposable.dispose();
38+
}
39+
}
40+
41+
executeGraphqlVariables(): Record<string, any> {
42+
const variables = executionVarsJSON.value;
43+
if (!variables) {
44+
return {};
45+
}
46+
try {
47+
return JSON.parse(variables);
48+
} catch (e: any) {
49+
throw new Error(
50+
"Unable to parse variables as JSON. Check the variables pane.\n" + e.message,
51+
);
52+
}
53+
}
54+
55+
executeGraphqlExtensions(): { impersonate?: Impersonation } {
56+
const auth = executionAuthParams.value;
57+
switch (auth.kind) {
58+
case AuthParamsKind.ADMIN:
59+
this.analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.RUN_AUTH_ADMIN);
60+
return {};
61+
case AuthParamsKind.UNAUTHENTICATED:
62+
this.analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.RUN_AUTH_UNAUTHENTICATED);
63+
return { impersonate: { unauthenticated: true, includeDebugDetails: true } };
64+
case AuthParamsKind.AUTHENTICATED:
65+
this.analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.RUN_AUTH_AUTHENTICATED);
66+
try {
67+
return {
68+
impersonate:
69+
{ authClaims: JSON.parse(auth.claims), includeDebugDetails: true }
70+
};
71+
} catch (e: any) {
72+
throw new Error(
73+
"Unable to parse auth claims as JSON. Check the authentication panel.\n" + e.message,
74+
);
75+
}
76+
default:
77+
throw new Error(`Unknown auth params kind: ${auth}`);
78+
}
79+
}
80+
81+
async applyDetectedFixes(ast: OperationDefinitionNode): Promise<void> {
82+
const userVars = this.executeGraphqlVariables();
83+
const fixes = [];
84+
{
85+
const undefinedVars = [];
86+
for (const varName in userVars) {
87+
if (!ast.variableDefinitions?.find((v) => v.variable.name.value === varName)) {
88+
delete userVars[varName];
89+
undefinedVars.push(varName);
90+
}
91+
}
92+
if (undefinedVars.length > 0) {
93+
this.analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.RUN_UNDEFINED_VARIABLES);
94+
fixes.push(`Removed undefined variables: ${undefinedVars.map((v) => "$" + v).join(", ")}`);
95+
}
96+
}
97+
{
98+
const missingRequiredVars = [];
99+
for (const variable of ast.variableDefinitions || []) {
100+
const varName = variable.variable.name.value;
101+
if (variable.type.kind === Kind.NON_NULL_TYPE && userVars[varName] === undefined) {
102+
userVars[varName] = getDefaultScalarValue(print(variable.type.type));
103+
missingRequiredVars.push(varName);
104+
}
105+
}
106+
if (missingRequiredVars.length > 0) {
107+
this.analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.RUN_MISSING_VARIABLES);
108+
fixes.push(`Included required variables: ${missingRequiredVars.map((v) => "$" + v).join(", ")}`);
109+
}
110+
}
111+
if (fixes.length === 0) {
112+
return;
113+
}
114+
executionVarsJSON.value = JSON.stringify(userVars, null, 2);
115+
this.broker.send("notifyVariables", { variables: executionVarsJSON.value, fixes });
116+
return;
117+
}
118+
}

0 commit comments

Comments
 (0)