Conversation
I noticed that we pass both argsList and args to the PtyProcess constructor. While TypeScript allows that, it is a bit confusing when inspecting the actual values received in the constructor.
4ec7ba5 to
da3e408
Compare
There was a problem hiding this comment.
Just adding some comments to the old code.
There was a problem hiding this comment.
Rust & Go made me write code like this. 😭 It doesn't create any intermediate data structures unlike [...this.state.gateways.entries()].find(…).
There was a problem hiding this comment.
Why not
for (const gateway of this.state.gateways.values()) {?
edit: ohhhh you were talking about the approach of the loop not the code style lol
There was a problem hiding this comment.
In a situation where DocumentGatewayCliClient times out when waiting for the gateway to be created, it updates the status to error. When the gateway ends up being created, we want to update the state back to connecting when we attempt to connect with the client.
This is what this call accomplishes.
| let title = `${command} · ${doc.targetUser}@${doc.targetName}`; | ||
|
|
||
| if (doc.targetSubresourceName) { | ||
| title += `/${doc.targetSubresourceName}`; |
There was a problem hiding this comment.
I've decided to remove targetSubresourceName from the title. Technically it was possible to set the subresource name to foo, create a CLI client tab connected to foo, change the subresource name to bar, restart the app and have the tab now connect to bar while the title said foo.
Working around this wouldn't be possible without bigger changes to the app. But even more so, in many CLI clients it's possible to change the current database from within the CLI anyway.
| // target, but the user might still want to inspect the output. | ||
| if (gateway || hasRenderedTerminal) { | ||
| if (!hasRenderedTerminal) { | ||
| setHasRenderedTerminal(true); |
There was a problem hiding this comment.
I'm not sure about calling setState in render, it looks "hacky". SO says that is not a good practice https://stackoverflow.com/a/35296725.
I know we want to avoid useEffect when possible, but it seems to me that it is a completely valid use case:
useEffect(() => {
if (gateway) {
setHasRenderedTerminal(true);
}}, [gateway]);AFAIK calling setState with the same value doesn't cause a re-render.
There was a problem hiding this comment.
I'm not sure about calling
setStateinrender
I think this use case falls under Adjusting some state when a prop changes. gateway is not prop in the traditional sense here but it does come from outside of the component.
There was a problem hiding this comment.
Ok, I didn't know it is allowed :p
| </StyledText> | ||
| )} | ||
|
|
||
| <ButtonPrimary onClick={openConnection}>Open the connection</ButtonPrimary> |
There was a problem hiding this comment.
WDYT about disabling the button while waiting for a db connection?
Or even showing it only after timeout?
IMO the current behavior with the button always active is slightly confusing.
There was a problem hiding this comment.
WDYT about disabling the button while waiting for a db connection?
What if you know that the connection won't be opened because the tab with the gateway is not present? I'd rather let the user click the button immediately. Also, clicking this button, even repeatedly, is not going to cause any side effects besides switching to the gateway tab.
All the "waiting" here is fake after all.
| A connection to <strong>{doc.targetName}</strong> as{' '} | ||
| <strong>{doc.targetUser}</strong> has not been opened up within{' '} | ||
| {TIMEOUT_SECONDS} seconds. |
There was a problem hiding this comment.
I think it'd be better to have a more explicit error message here. Something like
Error connecting to {targetName} as {targetUser}: Connection timed out after {timeout_seconds} seconds.
I won't die on this hill tho. I remember talk about changing the way our error messages sound, but I don't remember if it was supposed to be more or less "machine-y".
There was a problem hiding this comment.
The message you proposed is more direct and uses the active voice which I like. However, in this particular case there's no connection that times out and there's no error. As I mentioned in the previous comment, all waiting here is fake – we assume that when the DocumentGatewayCliClient is open, there's DocumentGateway open as well. When the user restarts the app, we wait for DocumentGateway to create the gateway before DocumentGatewayCliClient attempts to connect to the gateway.
What's more, it might take DocumentGateway more than 10 seconds to open the gateway, in that case DocumentGatewayCliClient is going to switched from the "timed out" state to starting a terminal session automatically.
With that in mind, I don't know how I could transform the existing message from the passive voice to the active voice.
|
I'll see how much of a hassle it is to backport this to v12 and v11. The issue with the command injection is not as relevant in v11 and v12 since those versions have the command bar. But backporting this will improve the gateway CLI client experience in all three versions and reduce discrepancies between the versions. |
|
@ravicious See the table below for backport results.
|
|
@ravicious Could you change the number and the field name of |
|
@espadolini Sure thing, I'll make a PR to address this. I'm waiting for #26581 before backporting anyway. What would be the best place to document that this API is not stable? How did you notice that I made a breaking change, as in is there a check that I could avoid tripping up in the future? |
|
I noticed because I was running As part of #26688 I'll have As far as where the indication that this API is subject to breakage should be added, maybe at the (proto) package level or maybe at the service level, I'd say. |
|
Addressed the breaking change in protos in #26705. |
* Return os.exec.Cmd as gateway CLI command * Remove separate Props type from DocumentTerminal * Refactor Kind type exported from documentsService * Export makeRuntimeSettings from MainProcess mock * PtyProcess: Join args in logger name * ptyHostService: Pass ptyOptions explicitly instead of using spread I noticed that we pass both argsList and args to the PtyProcess constructor. While TypeScript allows that, it is a bit confusing when inspecting the actual values received in the constructor. * Add empty DocumentGatewayCliClient * Start terminal from DocumentGatewayCliClient * Add waiting state for DocumentGatewayCliClient * Remove targetSubresourceName from DocumentGatewayCliClient title
* Return os.exec.Cmd as gateway CLI command * Remove separate Props type from DocumentTerminal * Refactor Kind type exported from documentsService * Export makeRuntimeSettings from MainProcess mock * PtyProcess: Join args in logger name * ptyHostService: Pass ptyOptions explicitly instead of using spread I noticed that we pass both argsList and args to the PtyProcess constructor. While TypeScript allows that, it is a bit confusing when inspecting the actual values received in the constructor. * Add empty DocumentGatewayCliClient * Start terminal from DocumentGatewayCliClient * Add waiting state for DocumentGatewayCliClient * Remove targetSubresourceName from DocumentGatewayCliClient title
* Return os.exec.Cmd as gateway CLI command * Remove separate Props type from DocumentTerminal * Refactor Kind type exported from documentsService * Export makeRuntimeSettings from MainProcess mock * PtyProcess: Join args in logger name * ptyHostService: Pass ptyOptions explicitly instead of using spread I noticed that we pass both argsList and args to the PtyProcess constructor. While TypeScript allows that, it is a bit confusing when inspecting the actual values received in the constructor. * Add empty DocumentGatewayCliClient * Start terminal from DocumentGatewayCliClient * Add waiting state for DocumentGatewayCliClient * Remove targetSubresourceName from DocumentGatewayCliClient title
What
This PR makes it so that when the user clicks "Run" next to a CLI command in a DocumentGateway tab, we spawn that process directly rather than spawning a shell first and then writing the CLI command to its PTY.
The number of additions is actually half as big, I had to update some proto messages.
Why
This fixes two problems:
targetSubresourceName) field could be set to something likenonexistent-database; touch /tmp/whoopsand upon clicking "Run" an arbitrary command could have been executed.spawnfunction so it's not possible to inject commands.initCommandfromShellCommandwhich will be done in a separate PR.How
The What section explains the general approach. One problem to solve here was that on app restart, we cannot spawn the CLI client until we know that the gateway is ready. This is dealt with by storing
targetUriandtargetUseron the new doc and waiting for the gateway with these params to be created.When it comes to connections, gateway CLI client tabs are treated as regular terminal tabs here and are not added to connections.
What isn't addressed
This PR doesn't address a situation in which the user doesn't have the CLI client but clicks "Run" anyway. Handling this is a little more complex than I assumed and will be done in a separate PR that is in the works.