Skip to content

Commit

Permalink
Support anonymous mutations. (#806)
Browse files Browse the repository at this point in the history
The problem was that `OperationDefinitionNode.name` is undefined when the mutation is anonymous.
Yes undefined values were not handled correctly.

This also changes usages of "executeQuery/executeMutation" in favour of a "executeGraphQL" that takes the source of a request directly.
  • Loading branch information
rrousselGit authored Dec 4, 2023
1 parent f463149 commit 54cc8e5
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 78 deletions.
15 changes: 12 additions & 3 deletions firebase-vscode/src/firemat/code-lens-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,31 @@ export class CodeLensProvider implements vscode.CodeLensProvider {
): Promise<vscode.CodeLens[]> {
const codeLenses: vscode.CodeLens[] = [];

const documentText = document.getText();

// TODO: replace w/ online-parser to work with malformed documents
const documentNode = parse(document.getText());
const documentNode = parse(documentText);

for (const x of documentNode.definitions) {
if (x.kind === Kind.OPERATION_DEFINITION && x.loc) {
const line = x.loc.startToken.line - 1;
const range = new vscode.Range(line, 0, line, 0);
const position = new vscode.Position(line, 0);
const operationLocation = { documentPath: document.fileName, position: position };
const operationLocation = {
document: documentText,
documentPath: document.fileName,
position: position,
};

codeLenses.push(
new vscode.CodeLens(range, {
title: `$(play) Execute ${x.operation as string}`,
command: "firebase.firemat.executeOperation",
tooltip: "Execute the operation (⌘+enter or Ctrl+Enter)",
arguments: [x, operationLocation],
arguments: [
x,
operationLocation,
],
})
);
}
Expand Down
33 changes: 17 additions & 16 deletions firebase-vscode/src/firemat/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function registerExecution(
const item = selectedExecution.value;
if (item) {
broker.send("notifyFirematResults", {
args: item.args ?? {},
args: item.args ?? "{}",
query: print(item.operation),
results: item.results ?? {},
displayName: item.operation.operation + ": " + item.label,
Expand All @@ -53,10 +53,14 @@ export function registerExecution(

async function executeOperation(
ast: OperationDefinitionNode,
{ documentPath, position }
{
document,
documentPath,
position,
}: { documentPath: string; position: vscode.Position; document: string }
) {
const item = createExecution({
label: ast.name.value,
label: ast.name?.value ?? "anonymous",
timestamp: Date.now(),
state: ExecutionState.RUNNING,
operation: ast,
Expand All @@ -67,19 +71,16 @@ export function registerExecution(

let results;
try {
// execute query or mutation
results =
ast.operation === (OPERATION_TYPE.query as string)
? await firematService.executeQuery({
operation_name: ast.name.value,
query: print(ast),
variables: executionArgsJSON.value,
})
: await firematService.executeMutation({
operation_name: ast.name.value,
mutation: print(ast),
variables: executionArgsJSON.value,
});
// Execute queries/mutations from their source code.
// That ensures that we can execute queries in unsaved files.
const results = await firematService.executeGraphQL({
operationName: ast.name?.value,
// We send the whole unparsed document to guarantee
// that there are no formatting differences between the real document
// and the document that is sent to the emulator.
query: document,
variables: executionArgsJSON.value,
});

// We always update the execution item, no matter what happens beforehand.
} finally {
Expand Down
91 changes: 32 additions & 59 deletions firebase-vscode/src/firemat/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export class FirematService {
}

/** Encode a body while handling the fact that "variables" is raw JSON.
*
* If the JSON is invalid, will throw.
*/
*
* If the JSON is invalid, will throw.
*/
private _serializeBody(body: { variables?: string; [key: string]: unknown }) {
if (!body.variables) {
return JSON.stringify(body);
Expand All @@ -67,60 +67,6 @@ export class FirematService {
});
}

async executeMutation(params: {
operation_name: String;
mutation: String;
variables: string;
}) {
// TODO: get operationSet name from firemat.yaml
const body = this._serializeBody({
...params,
name: "projects/p/locations/l/services/local/operationSets/crud/revisions/r",
});
const resp = await fetch(
(await this.getFirematEndpoint()) +
"/v0/projects/p/locations/l/services/local/operationSets/crud/revisions/r:executeMutation",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-mantle-admin": "all",
},
body,
}
);
const result = await resp.json().catch(() => resp.text());
return result;
}

async executeQuery(params: {
operation_name: String;
query: String;
variables: string;
}) {
// TODO: get operationSet name from firemat.yaml
const body = this._serializeBody({
...params,
name: "projects/p/locations/l/services/local/operationSets/crud/revisions/r",
});
const resp = await fetch(
(await this.getFirematEndpoint()) +
"/v0/projects/p/locations/l/services/local/operationSets/crud/revisions/r:executeQuery",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-mantle-admin": "all",
},
body,
}
);
const result = await resp.json().catch(() => resp.text());
return result;
}

// This introspection is used to generate a basic graphql schema
// It will not include our predefined operations, which requires a Firemat specific introspection query
async introspect(): Promise<{ data?: IntrospectionQuery }> {
Expand Down Expand Up @@ -149,8 +95,8 @@ export class FirematService {
}

async executeGraphQLRead(params: {
query: String;
operationName: String;
query: string;
operationName: string;
variables: string;
}) {
try {
Expand Down Expand Up @@ -180,4 +126,31 @@ export class FirematService {
return null;
}
}

async executeGraphQL(params: {
query: string;
operationName?: string;
variables: string;
}) {
// TODO: get name programmatically
const body = this._serializeBody({
...params,
name: "projects/p/locations/l/services/local",
});
const resp = await fetch(
(await this.getFirematEndpoint()) +
"/v0/projects/p/locations/l/services/local:executeGraphql",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-mantle-admin": "all",
},
body: body,
}
);
const result = await resp.json().catch(() => resp.text());
return result;
}
}

0 comments on commit 54cc8e5

Please sign in to comment.