Skip to content

Commit 6caa01f

Browse files
authored
Merge branch 'main' into test-schema
2 parents de990d4 + ac7ca0b commit 6caa01f

File tree

8 files changed

+177
-71
lines changed

8 files changed

+177
-71
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix the bug when Data Connect emulator hangs with PGlite. (Issue #9756) #9771

firebase-vscode/CHANGELOG.md

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

3+
- Render GraphQL error debug details and error code in the execution panel nicely. (#9769)
4+
35
## 2.0.1
46

57
- Update internal `firebase-tools` dependency to 15.3.1

firebase-vscode/webviews/data-connect/DataConnectExecutionResultsApp.tsx

Lines changed: 78 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Label } from "../components/ui/Text";
44
import style from "./data-connect-execution-results.entry.scss";
55
import { SerializedError } from "../../common/error";
66
import { ExecutionResult, GraphQLError } from "graphql";
7+
import { GraphqlErrorExtensions } from "../../../src/dataconnect/types";
78
import { isExecutionResult } from "../../common/graphql";
89
import { AuthParamsKind } from '../../common/messaging/protocol';
910
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
@@ -36,7 +37,7 @@ export function DataConnectExecutionResultsApp() {
3637
if (errors && errors.length !== 0) {
3738
errorsDisplay = (
3839
<>
39-
<GraphQLErrorView errors={errors} />
40+
<GraphQLErrorsView errors={errors} />
4041
</>
4142
);
4243
}
@@ -125,69 +126,93 @@ export function DataConnectExecutionResultsApp() {
125126
*/
126127
function InternalErrorView({ error }: { error: SerializedError }) {
127128
return (
128-
<>
129-
<Label>Error</Label>
130-
<p>
131-
{error.message}
129+
<div className={style.errorContainer}>
130+
<div className={style.errorItem}>
131+
<div className={style.errorHeader}>
132+
<i className="codicon codicon-error" />
133+
<span>{error.message}</span>
134+
</div>
132135
{error.cause && (
133-
<>
134-
<br />
135-
<h4>Cause:</h4>
136+
<div style={{ marginLeft: "var(--space-large)" }}>
137+
<Label>Cause:</Label>
136138
<InternalErrorView error={error.cause} />
137-
</>
139+
</div>
138140
)}
139-
</p>
140-
</>
141+
</div>
142+
</div>
141143
);
142144
}
143145

144146
/** A view for when an execution returns status 200 but contains errors. */
145-
function GraphQLErrorView({ errors }: { errors: readonly GraphQLError[] }) {
146-
let pathDisplay: JSX.Element | undefined;
147-
// update path
148-
const errorsWithPathDisplay = errors.map((error) => {
149-
if (error.path) {
150-
// Renders the path as a series of kbd elements separated by commas
151-
return {
152-
...error,
153-
pathDisplay: (
154-
<>
155-
{error.path?.map((path, index) => {
156-
const item = <kbd>{path}</kbd>;
157-
158-
return index === 0 ? item : <>, {item}</>;
159-
})}{" "}
160-
</>
161-
),
162-
};
163-
}
164-
return error;
165-
});
166-
147+
function GraphQLErrorsView({ errors }: { errors: readonly GraphQLError[] }) {
167148
return (
168-
<>
169-
{errorsWithPathDisplay.map((error, index) => {
170-
return (
171-
<p style={{ whiteSpace: "pre-wrap" }} key={index}>
172-
{pathDisplay}
173-
{error.message}
174-
{error.stack && <StackView stack={error.stack} />}
175-
</p>
176-
);
177-
})}
178-
</>
149+
<div className={style.errorContainer}>
150+
{errors.map((error, index) => (
151+
<GraphQLErrorView key={index} error={error} />
152+
))}
153+
</div>
179154
);
180155
}
181156

182-
function StackView({ stack }: { stack: string }) {
157+
function GraphQLErrorView({ error }: { error: GraphQLError }) {
158+
const { message, path, extensions } = error;
159+
const { debugDetails, code, workarounds } = (extensions ?? {}) as GraphqlErrorExtensions;
160+
183161
return (
184-
<span
185-
style={{
186-
// Preserve stacktrace formatting
187-
whiteSpace: "pre-wrap",
188-
}}
189-
>
190-
{stack}
191-
</span>
162+
<div className={style.errorItem}>
163+
<div className={style.errorHeader}>
164+
<i className="codicon codicon-error" />
165+
{code && code !== "OK" && code !== "UNKNOWN" && (
166+
<span className={style.errorCode}>{code}</span>
167+
)}
168+
<span>{message}</span>
169+
</div>
170+
{path && path.length > 0 && (
171+
<div className={style.errorPath}>
172+
at{" "}
173+
{path.map((p: string | number, i: number) => (
174+
<React.Fragment key={i}>
175+
{i > 0 && "."}
176+
<kbd>{p}</kbd>
177+
</React.Fragment>
178+
))}
179+
</div>
180+
)}
181+
{workarounds && workarounds.length > 0 && (
182+
<div className={style.workarounds}>
183+
<label>Workarounds</label>
184+
<pre>
185+
{workarounds
186+
.map((w: any) => {
187+
const yaml = typeof w === "string" ? w : renderYaml(w);
188+
return `- ${yaml.replace(/\n/g, "\n ")}`;
189+
})
190+
.join("\n")}
191+
</pre>
192+
</div>
193+
)}
194+
{debugDetails && (
195+
<div className={style.debugDetails}>
196+
<label>Debug Details</label>
197+
<pre>{debugDetails}</pre>
198+
</div>
199+
)}
200+
</div>
192201
);
193202
}
203+
204+
function renderYaml(obj: any, indent = ""): string {
205+
if (typeof obj !== "object" || obj === null) {
206+
return String(obj);
207+
}
208+
209+
return Object.entries(obj)
210+
.map(([key, value]) => {
211+
const prefix = `${indent}${key}:`;
212+
if (typeof value === "object" && value !== null) {
213+
return `${prefix}\n${renderYaml(value, indent + " ")}`;
214+
}
215+
return `${prefix} ${value}`;
216+
})
217+
.join("\n");
218+
}

firebase-vscode/webviews/data-connect/data-connect-execution-results.entry.scss

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,74 @@ body {
3535
margin-bottom: 1em;
3636
}
3737
}
38+
39+
.error-container {
40+
display: flex;
41+
flex-direction: column;
42+
gap: var(--space-medium);
43+
margin-bottom: var(--space-large);
44+
border-left: 2px solid var(--vscode-errorForeground);
45+
padding-left: var(--space-medium);
46+
}
47+
48+
.error-item {
49+
display: flex;
50+
flex-direction: column;
51+
gap: var(--space-xsmall);
52+
}
53+
54+
.error-header {
55+
display: flex;
56+
align-items: center;
57+
gap: var(--space-small);
58+
color: var(--vscode-errorForeground);
59+
font-weight: bold;
60+
}
61+
62+
.error-path {
63+
font-size: 0.9em;
64+
opacity: 0.8;
65+
margin-left: calc(16px + var(--space-small)); // Align with message text
66+
67+
kbd {
68+
background-color: var(--vscode-keybindingLabel-background);
69+
color: var(--vscode-keybindingLabel-foreground);
70+
border: 1px solid var(--vscode-keybindingLabel-border);
71+
border-bottom-color: var(--vscode-keybindingLabel-bottomBorder);
72+
border-radius: 3px;
73+
padding: 0 3px;
74+
}
75+
}
76+
77+
.debug-details,
78+
.workarounds {
79+
margin-left: calc(16px + var(--space-small));
80+
margin-top: var(--space-small);
81+
82+
label {
83+
display: block;
84+
font-size: 0.85em;
85+
font-weight: bold;
86+
margin-bottom: var(--space-xsmall);
87+
opacity: 0.8;
88+
}
89+
90+
pre {
91+
margin: 0;
92+
padding: var(--space-small);
93+
white-space: pre-wrap;
94+
word-break: break-all;
95+
background-color: var(--vscode-editor-background);
96+
border: 1px solid var(--vscode-panel-border);
97+
}
98+
}
99+
100+
.error-code {
101+
font-family: monospace;
102+
background-color: var(--vscode-badge-background);
103+
color: var(--vscode-badge-foreground);
104+
padding: 0 4px;
105+
border-radius: 2px;
106+
font-size: 0.85em;
107+
font-weight: normal;
108+
}

scripts/publish/firebase-docker-image/package-lock.json

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/apphosting/rollout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export async function orchestrateRollout(
206206
);
207207
}
208208
throw new FirebaseError(
209-
`Failed to build your app. Please inspect the build logs at ${build.buildLogsUri}.`,
209+
`Failed to build your app. Please inspect the build logs at ${build.buildLogsUri}`,
210210
{ children: [build.error] },
211211
);
212212
}

src/dataconnect/types.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,18 @@ export interface GraphqlError {
9595
line: number;
9696
column: number;
9797
}[];
98-
extensions?: {
99-
file?: string;
100-
warningLevel?: WarningLevel;
101-
workarounds?: Workaround[];
102-
[key: string]: any;
103-
};
98+
extensions?: GraphqlErrorExtensions;
99+
}
100+
101+
export interface GraphqlErrorExtensions {
102+
file?: string;
103+
code?: string;
104+
debugDetails?: string;
105+
warningLevel?: WarningLevel;
106+
workarounds?: Workaround[];
107+
[key: string]: any;
104108
}
109+
105110
export interface BuildResult {
106111
errors?: GraphqlError[];
107112
metadata?: DeploymentMetadata;

src/emulator/dataconnectEmulator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@ export class DataConnectEmulator implements EmulatorInstance {
333333
connectionString: connectionString.toString(),
334334
database,
335335
serviceId,
336-
maxOpenConnections: 1, // PGlite only supports a single open connection at a time - otherwise, prepared statements will misbehave.
336+
// NOTE: Previously, we set `maxOpenConnections: 1` to get around PGlite's limitation with prepared statements.
337+
// Since we switched emulator to avoid prepared statement, multiple connections are OK.
338+
// Transactional operations (`mutation @transaction`) actually requires multiple connections.
337339
});
338340
this.logger.logLabeled(
339341
"DEBUG",

0 commit comments

Comments
 (0)