1
1
import { Button , WithTooltip } from "@rivet-gg/components" ;
2
2
import { Icon , faSave } from "@rivet-gg/icons" ;
3
- import saveAs from "file-saver" ;
4
- import {
5
- type Settings ,
6
- useActorDetailsSettings ,
7
- } from "./actor-details-settings" ;
8
- import { type LogsTypeFilter , filterLogs } from "./actor-logs" ;
9
- import type { ActorAtom , LogsAtom } from "./actor-context" ;
10
- import { selectAtom } from "jotai/utils" ;
11
- import { type Atom , atom , useAtom } from "jotai" ;
3
+ import { type LogsTypeFilter } from "./actor-logs" ;
4
+ import type { ActorAtom } from "./actor-context" ;
5
+ import { actorEnvironmentAtom , exportLogsHandlerAtom } from "./actor-context" ;
6
+ import { atom , useAtom , useAtomValue } from "jotai" ;
7
+ import { useState } from "react" ;
12
8
13
9
const downloadLogsAtom = atom (
14
10
null ,
15
- (
11
+ async (
16
12
get ,
17
13
_set ,
18
14
{
15
+ actorId,
19
16
typeFilter,
20
17
filter,
21
- settings,
22
- logs : logsAtom ,
23
18
} : {
19
+ actorId : string ;
24
20
typeFilter ?: LogsTypeFilter ;
25
21
filter ?: string ;
26
- settings : Settings ;
27
- logs : Atom < LogsAtom > ;
28
22
} ,
29
23
) => {
30
- const { logs } = get ( get ( logsAtom ) ) ;
24
+ const environment = get ( actorEnvironmentAtom ) ;
25
+ const exportHandler = get ( exportLogsHandlerAtom ) ;
31
26
32
- const combined = filterLogs ( {
33
- typeFilter : typeFilter ?? "all" ,
34
- filter : filter ?? "" ,
35
- logs,
36
- } ) ;
27
+ if ( ! environment || ! exportHandler ) {
28
+ throw new Error ( "Environment or export handler not available" ) ;
29
+ }
37
30
38
- const lines = combined . map ( ( log ) => {
39
- const timestamp = new Date ( log . timestamp ) . toISOString ( ) ;
40
- if ( settings . showTimestamps ) {
41
- return `[${ timestamp } ] ${ log . message || log . line } ` ;
42
- }
43
- return log . message || log . line ;
31
+ // Build query JSON for the API
32
+ // Based on the GET logs endpoint usage, we need to build a query
33
+ const query : any = {
34
+ actorIds : [ actorId ] ,
35
+ } ;
36
+
37
+ // Add stream filter based on typeFilter
38
+ if ( typeFilter === "output" ) {
39
+ query . stream = 0 ; // stdout
40
+ } else if ( typeFilter === "errors" ) {
41
+ query . stream = 1 ; // stderr
42
+ }
43
+
44
+ // Add text search if filter is provided
45
+ if ( filter ) {
46
+ query . searchText = filter ;
47
+ }
48
+
49
+ const result = await exportHandler ( {
50
+ projectNameId : environment . projectNameId ,
51
+ environmentNameId : environment . environmentNameId ,
52
+ queryJson : JSON . stringify ( query ) ,
44
53
} ) ;
45
54
46
- saveAs (
47
- new Blob ( [ lines . join ( "\n" ) ] , {
48
- type : "text/plain;charset=utf-8" ,
49
- } ) ,
50
- "logs.txt" ,
51
- ) ;
55
+ // Open the presigned URL in a new tab to download
56
+ window . open ( result . url , "_blank" ) ;
52
57
} ,
53
58
) ;
54
59
@@ -63,29 +68,41 @@ export function ActorDownloadLogsButton({
63
68
typeFilter,
64
69
filter,
65
70
} : ActorDownloadLogsButtonProps ) {
66
- const [ settings ] = useActorDetailsSettings ( ) ;
67
-
71
+ const [ isDownloading , setIsDownloading ] = useState ( false ) ;
68
72
const [ , downloadLogs ] = useAtom ( downloadLogsAtom ) ;
73
+ const actorData = useAtomValue ( actor ) ;
74
+
75
+ const handleDownload = async ( ) => {
76
+ try {
77
+ setIsDownloading ( true ) ;
78
+ await downloadLogs ( {
79
+ actorId : actorData . id ,
80
+ typeFilter,
81
+ filter,
82
+ } ) ;
83
+ } catch ( error ) {
84
+ console . error ( "Failed to download logs:" , error ) ;
85
+ } finally {
86
+ setIsDownloading ( false ) ;
87
+ }
88
+ } ;
69
89
70
90
return (
71
91
< WithTooltip
72
- content = "Download logs"
92
+ content = "Export logs"
73
93
trigger = {
74
94
< Button
75
95
className = "ml-2 place-self-center"
76
96
variant = "outline"
77
- aria-label = "Download logs"
97
+ aria-label = "Export logs"
78
98
size = "icon-sm"
79
- onClick = { ( ) =>
80
- downloadLogs ( {
81
- typeFilter,
82
- filter,
83
- settings,
84
- logs : selectAtom ( actor , ( a ) => a . logs ) ,
85
- } )
86
- }
99
+ onClick = { handleDownload }
100
+ disabled = { isDownloading }
87
101
>
88
- < Icon icon = { faSave } />
102
+ < Icon
103
+ icon = { faSave }
104
+ className = { isDownloading ? "animate-pulse" : "" }
105
+ />
89
106
</ Button >
90
107
}
91
108
/>
0 commit comments