Skip to content

Commit c99fa8a

Browse files
authored
adhoc queries and mutations support list and enum (#8855)
* adhoc queries and mutations support list and enum * fix changelog * format
1 parent 67ebf87 commit c99fa8a

File tree

3 files changed

+266
-87
lines changed

3 files changed

+266
-87
lines changed

firebase-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## NEXT
22

33
- [Fixed] Language server now properly recognizes nested Dataconnect folders
4+
- [Fixed] Add Data and Read Data now properly support enum and list types
45

56
## 1.5.0
67

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

Lines changed: 112 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
ValueNode,
1111
buildClientSchema,
1212
getNamedType,
13+
isEnumType,
1314
isInputObjectType,
15+
isListType,
1416
print,
1517
} from "graphql";
1618
import { upsertFile } from "./file-utils";
@@ -34,9 +36,7 @@ export function registerAdHoc(
3436
ast: ObjectTypeDefinitionNode,
3537
documentPath: string,
3638
) {
37-
// TODO(rrousselGit) - this is a temporary solution due to the lack of a "schema".
38-
// As such, we hardcoded the list of allowed primitives.
39-
// We should ideally refactor this to allow any scalar type.
39+
// TODO(hlshen): Revamp makeQuery to utilize live schema, and built out an AST instead of construction the query through string builder
4040
const primitiveTypes = new Set([
4141
"String",
4242
"Int",
@@ -67,9 +67,12 @@ export function registerAdHoc(
6767
var hasField = false;
6868
let query = "{\n";
6969
for (const field of ast.fields!) {
70-
// We unwrap NonNullType to obtain the actual type
70+
// We unwrap NonNullType and ListType to obtain the actual type
7171
let fieldType = field.type;
72-
if (fieldType.kind === Kind.NON_NULL_TYPE) {
72+
while (
73+
fieldType.kind === Kind.LIST_TYPE ||
74+
fieldType.kind === Kind.NON_NULL_TYPE
75+
) {
7376
fieldType = fieldType.type;
7477
}
7578

@@ -83,6 +86,17 @@ export function registerAdHoc(
8386
continue;
8487
}
8588

89+
const isEnum = document.definitions.some(
90+
(def) =>
91+
def.kind === Kind.ENUM_TYPE_DEFINITION &&
92+
def.name.value === targetType.name.value,
93+
);
94+
if (isEnum) {
95+
query += ` ${indent}${field.name.value}\n`;
96+
hasField = true;
97+
continue;
98+
}
99+
86100
// Check relational types.
87101
// Since we lack a schema, we can only build queries for types that are defined in the same document.
88102
const targetTypeDefinition = document.definitions.find(
@@ -106,18 +120,16 @@ export function registerAdHoc(
106120
}
107121

108122
query += `${indent}}`;
123+
if (!hasField) {
124+
return undefined;
125+
}
109126
return query;
110127
}
111128

112129
await upsertFile(filePath, () => {
113130
const queryName = `${ast.name.value.charAt(0).toLowerCase()}${ast.name.value.slice(1)}s`;
114131

115-
return `
116-
# This is a file for you to write an un-named query.
117-
# Only one un-named query is allowed per file.
118-
query {
119-
${queryName}${buildRecursiveObjectQuery(ast)!}
120-
}`;
132+
return `\n# This is a file for you to write an un-named query.\n# Only one un-named query is allowed per file.\nquery {\n ${queryName}${buildRecursiveObjectQuery(ast)!}\n}`;
121133
});
122134
}
123135

@@ -164,82 +176,6 @@ query {
164176
});
165177
}
166178

167-
function makeAdHocMutation(
168-
fields: GraphQLInputField[],
169-
singularName: string,
170-
): OperationDefinitionNode {
171-
const argumentFields: ObjectFieldNode[] = [];
172-
173-
for (const field of fields) {
174-
const type = getNamedType(field.type);
175-
const defaultValue = getDefaultScalarValueNode(type.name);
176-
if (!defaultValue) {
177-
continue;
178-
}
179-
180-
argumentFields.push({
181-
kind: Kind.OBJECT_FIELD,
182-
name: { kind: Kind.NAME, value: field.name },
183-
value: defaultValue,
184-
});
185-
}
186-
187-
return {
188-
kind: Kind.OPERATION_DEFINITION,
189-
operation: OperationTypeNode.MUTATION,
190-
selectionSet: {
191-
kind: Kind.SELECTION_SET,
192-
selections: [
193-
{
194-
kind: Kind.FIELD,
195-
name: {
196-
kind: Kind.NAME,
197-
value: `${singularName.charAt(0).toLowerCase()}${singularName.slice(1)}_insert`,
198-
},
199-
arguments: [
200-
{
201-
kind: Kind.ARGUMENT,
202-
name: { kind: Kind.NAME, value: "data" },
203-
value: {
204-
kind: Kind.OBJECT,
205-
fields: argumentFields,
206-
},
207-
},
208-
],
209-
},
210-
],
211-
},
212-
};
213-
}
214-
function getDefaultScalarValueNode(type: string): ValueNode | undefined {
215-
switch (type) {
216-
case "Any":
217-
return { kind: Kind.OBJECT, fields: [] };
218-
case "Boolean":
219-
return { kind: Kind.BOOLEAN, value: false };
220-
case "Date":
221-
return {
222-
kind: Kind.STRING,
223-
value: new Date().toISOString().substring(0, 10),
224-
};
225-
case "Float":
226-
return { kind: Kind.FLOAT, value: "0" };
227-
case "Int":
228-
return { kind: Kind.INT, value: "0" };
229-
case "Int64":
230-
return { kind: Kind.INT, value: "0" };
231-
case "String":
232-
return { kind: Kind.STRING, value: "" };
233-
case "Timestamp":
234-
return { kind: Kind.STRING, value: new Date().toISOString() };
235-
case "UUID":
236-
return { kind: Kind.STRING, value: "11111111222233334444555555555555" };
237-
case "Vector":
238-
return { kind: Kind.LIST, values: [] };
239-
default:
240-
return undefined;
241-
}
242-
}
243179
return Disposable.from(
244180
vscode.commands.registerCommand(
245181
"firebase.dataConnect.schemaAddData",
@@ -258,6 +194,95 @@ query {
258194
);
259195
}
260196

197+
export function makeAdHocMutation(
198+
fields: GraphQLInputField[],
199+
singularName: string,
200+
): OperationDefinitionNode {
201+
const argumentFields: ObjectFieldNode[] = [];
202+
203+
for (const field of fields) {
204+
const type = getNamedType(field.type);
205+
let defaultValue: ValueNode | undefined;
206+
if (isEnumType(type)) {
207+
const enumValues = type.getValues();
208+
if (enumValues.length > 0) {
209+
defaultValue = { kind: Kind.ENUM, value: enumValues[0].name };
210+
}
211+
} else {
212+
defaultValue = getDefaultScalarValueNode(type.name);
213+
}
214+
if (!defaultValue) {
215+
continue;
216+
}
217+
218+
// convert it back to a list
219+
if (isListType(field.type)) {
220+
defaultValue = { kind: Kind.LIST, values: [defaultValue] };
221+
}
222+
223+
argumentFields.push({
224+
kind: Kind.OBJECT_FIELD,
225+
name: { kind: Kind.NAME, value: field.name },
226+
value: defaultValue,
227+
});
228+
}
229+
230+
return {
231+
kind: Kind.OPERATION_DEFINITION,
232+
operation: OperationTypeNode.MUTATION,
233+
selectionSet: {
234+
kind: Kind.SELECTION_SET,
235+
selections: [
236+
{
237+
kind: Kind.FIELD,
238+
name: {
239+
kind: Kind.NAME,
240+
value: `${singularName.charAt(0).toLowerCase()}${singularName.slice(1)}_insert`,
241+
},
242+
arguments: [
243+
{
244+
kind: Kind.ARGUMENT,
245+
name: { kind: Kind.NAME, value: "data" },
246+
value: {
247+
kind: Kind.OBJECT,
248+
fields: argumentFields,
249+
},
250+
},
251+
],
252+
},
253+
],
254+
},
255+
};
256+
}
257+
function getDefaultScalarValueNode(type: string): ValueNode | undefined {
258+
switch (type) {
259+
case "Any":
260+
return { kind: Kind.OBJECT, fields: [] };
261+
case "Boolean":
262+
return { kind: Kind.BOOLEAN, value: false };
263+
case "Date":
264+
return {
265+
kind: Kind.STRING,
266+
value: new Date().toISOString().substring(0, 10),
267+
};
268+
case "Float":
269+
return { kind: Kind.FLOAT, value: "0" };
270+
case "Int":
271+
return { kind: Kind.INT, value: "0" };
272+
case "Int64":
273+
return { kind: Kind.INT, value: "0" };
274+
case "String":
275+
return { kind: Kind.STRING, value: "" };
276+
case "Timestamp":
277+
return { kind: Kind.STRING, value: new Date().toISOString() };
278+
case "UUID":
279+
return { kind: Kind.STRING, value: "11111111222233334444555555555555" };
280+
case "Vector":
281+
return { kind: Kind.LIST, values: [] };
282+
default:
283+
return undefined;
284+
}
285+
}
261286

262287
export function getDefaultScalarValue(type: string): string {
263288
switch (type) {

0 commit comments

Comments
 (0)