Conversation
ravicious
left a comment
There was a problem hiding this comment.
I'll take a look at the rest of the UI code and the backend code tomorrow.
ravicious
left a comment
There was a problem hiding this comment.
The keyboard navigation works fine now, thanks for fixing this.
|
I didn't get around to testing if the Web UI file transfer still works correctly but I did test it for Connect. Also, did you compare performance of file transfer through Connect vs Web UI and tsh? I won't be able to do that today but it'd be good to make sure we're not sluggish compared to them for some reason. |
ravicious
left a comment
There was a problem hiding this comment.
As I mentioned in DMs, I somehow totally forgot to take a look at the non-UI code. I might have more followups soon, right now I'm looking at the problem with server ID on the backend side of this.
| abortSignal.addEventListener(() => call.cancel()); | ||
|
|
||
| call.on('data', (data: api.FileTransferProgress) => | ||
| listeners.onProgress(data.getPercentage()) | ||
| ); | ||
| call.on('end', () => { | ||
| listeners.onComplete(); | ||
| call.removeAllListeners(); | ||
| }); | ||
| call.on('error', error => { | ||
| listeners.onError(error); | ||
| call.removeAllListeners(); | ||
| }); |
There was a problem hiding this comment.
I wonder if perhaps a better idea would be to make the tshd client return just the call object and then contain the rest of the logic within FileTransferClient.
Right now all FileTransferClient does is just pass some stuff further down – at this point one might wonder why we even bother creating this class at all.
See how I organized the stream stuff in my initial draft PR for tshd-initiated communication. I'm talking about the files in services/tshd. Though here you probably don't want to call FileTransferClient from within tshd client, but rather as I suggested, tsdh client would return the call object and then FileTransferClient could operate on it.
File transfer doesn't need to follow it 1:1. I think the files you created so far are alright it's just a matter of moving the logic around a bit.
There was a problem hiding this comment.
Unfortunately, context bridge doesn't allow us to return call object, but I followed the structure from linked PR, so now we have a function createFileTransferStream that is returned from tshd client.
Also, as discussed in DM's, we decided not to pass FileTransferListeners as an argument, but instead we return it from the tshd/http calls. It makes the code easier to understand, especially getDownloader/getUploader parts (less arguments, one callback instead of a callback returning another callback).
I also added createFileTransferEventsEmitter utility function that makes working with the events/listeners more convenient.
ravicious
left a comment
There was a problem hiding this comment.
Getting close to ready here.
| constructor(private tshClient: TshClient) {} | ||
|
|
||
| transferFile( | ||
| options: FileTransferRequest.AsObject, |
There was a problem hiding this comment.
From what I've seen, we typically "reexport" those AsObject types:
webapps/packages/teleterm/src/services/tshd/types.ts
Lines 10 to 13 in 10e90a7
Could you do the same for those few places where you use FileTransferRequest.AsObject?
| ): void { | ||
| ): FileTransferListeners { | ||
| const server = appContext.clustersService.getServer(file.serverUri); | ||
| const eventsEmitter = createFileTransferEventsEmitter(); |
There was a problem hiding this comment.
Why do we need that eventsEmitter here?
appContext.fileTransferClient.transferFile already returns an object that has all the properties that FileTransferListeners requires so unless I'm missing something, it seems that we can pass whatever we get from transferFile directly.
Once that event emitter is removed, we can remove createFileTransferEventsEmitter altogether by bringing back the inline usage of event emitter in httpFileTransferHandlers.
There was a problem hiding this comment.
Yes, appContext.fileTransferClient.transferFile already returns an object that we can use, but I need the event emitter to create a layer between tshClient and FileTransfer. Events returned from the tshClient are used inside useTshFileTransferHandlers and FileTransfer uses these returned from useTshFileTransferHandlers.
It is needed, because the errors needs to go through retryWithRelogin which decides what to do with them first.
For example, when there is ssh: cert has expired error, it should show to the user only when he reject to log in.
I wanted to avoid this, but I don't see any other way not to create an event emitter.
| }; | ||
|
|
||
| return this.tshClient.transferFile(options, listeners, abortSignal); | ||
| return this.tshClient.transferFile(options, abortSignal); |
There was a problem hiding this comment.
It looks like FileTransferClient ended up with very few responsibilities after all. Still, I think it's better to encapsulate calls to tshClient (which has big surface area) within small services like that (which have very small surface area) rather than passing the big tshClient throughout the app.
There was a problem hiding this comment.
Totally agree, we should avoid using tshClient directly.
| import { TshClient } from 'teleterm/services/tshd/types'; | ||
| import { FileTransferRequest } from 'teleterm/services/tshd/v1/service_pb'; | ||
|
|
||
| export class FileTransferClient { |
There was a problem hiding this comment.
This could probably be named FileTransferService rather than Client, no? It'd fit better with the rest of the classes.
There was a problem hiding this comment.
Yes, the name comes from the first iteration and doesn't have much sense now, changed.
(cherry picked from commit f614b7f)
Closes https://github.com/gravitational/webapps.e/issues/179
This PR refactors
FileTransferso it can be used in both WebUI and Connect.The biggest change from the previous version is that
FileTransferno longer makes HTTP requests on its own. Instead, it allows you to provide your own implementations for upload/download functions.There's also a refreshed UI and, as we discussed at the Design Meeting, there's no longer an Upload button. Uploading starts as soon as you select files.
Additionally,
FileTransferhas been moved fromteleportto thesharedpackage.How Web UI works
Upload/download in the WebUI works as before, they use HTTP (XHR) to transfer the files. When a user downloads a file, it is stored in the browser's memory, and when it is finished, the browser's download is triggered.
How Connect works
The upload/download for Connect is quite different. It doesn't physically move the files, but delegates this task to tshd (SFTP). We just specify the source/destination paths via gRPC server-side stream, and all the dirty work is done for us :) Server-side streaming is used to send the transfer progress back to the UI. To specify the destination path to download the file, we use the native OS save dialog.
Info for the reviewers
Review commit-by-commit.
At first I didn't plan to do a refactor, but then I realized that the changes would be significant anyway, so this is a great opportunity to refactor the whole module to modern React, add full TS support and simplify the final usage. I also added tests and better stories.
Although the PR added/updated/removed many files (sorry 😞), I tried to keep the logic in one place. The main logic of
FileTransferis inuseFilesStore.tsxandFileTransfer.tsx.Similarly
DocumentSsh,DocumentTerminal,getHttpFileTransferHandlers(the XHR orchestration here is 1:1 to the previousTransferservice) are the places where most of the callers work is done.