@@ -2,7 +2,7 @@ import { Text, Tooltip, ValueCopyable } from '@siafoundation/design-system'
2
2
import { HostContextMenuFromKey } from '../../components/Hosts/HostContextMenuFromKey'
3
3
import { ContractContextMenuFromId } from '../../components/Contracts/ContractContextMenuFromId'
4
4
import { humanBytes } from '@siafoundation/units'
5
- import { format } from 'date-fns'
5
+ import { formatRelative } from 'date-fns'
6
6
import { useMemo } from 'react'
7
7
import { Add16 , Subtract16 } from '@siafoundation/react-icons'
8
8
import { cx } from 'class-variance-authority'
@@ -50,36 +50,103 @@ export function SetChangesField({
50
50
...Object . keys ( setAdditions ) ,
51
51
...Object . keys ( setRemovals ) ,
52
52
] )
53
- return contractIds . map ( ( contractId ) => {
54
- const additions = setAdditions [ contractId ] ?. additions || [ ]
55
- const removals = setRemovals [ contractId ] ?. removals || [ ]
56
- return {
57
- contractId,
58
- hostKey :
59
- setAdditions [ contractId ] ?. hostKey || setRemovals [ contractId ] ?. hostKey ,
60
- events : [
61
- ...additions . map ( ( a ) => ( {
62
- type : 'addition' ,
63
- size : a . size ,
64
- time : a . time ,
65
- } ) ) ,
66
- ...removals . map ( ( r ) => ( {
67
- type : 'removal' ,
68
- size : r . size ,
69
- time : r . time ,
70
- reasons : r . reasons ,
71
- } ) ) ,
72
- ] . sort ( ( a , b ) =>
73
- new Date ( a . time ) . getTime ( ) > new Date ( b . time ) . getTime ( ) ? 1 : - 1
74
- ) as ChangeEvent [ ] ,
75
- }
76
- } )
53
+ return contractIds
54
+ . map ( ( contractId ) => {
55
+ const additions = setAdditions [ contractId ] ?. additions || [ ]
56
+ const removals = setRemovals [ contractId ] ?. removals || [ ]
57
+ return {
58
+ contractId,
59
+ hostKey :
60
+ setAdditions [ contractId ] ?. hostKey ||
61
+ setRemovals [ contractId ] ?. hostKey ,
62
+ events : [
63
+ ...additions . map ( ( a ) => ( {
64
+ type : 'addition' ,
65
+ size : a . size ,
66
+ time : a . time ,
67
+ } ) ) ,
68
+ ...removals . map ( ( r ) => ( {
69
+ type : 'removal' ,
70
+ size : r . size ,
71
+ time : r . time ,
72
+ reasons : r . reasons ,
73
+ } ) ) ,
74
+ ] . sort ( ( a , b ) =>
75
+ new Date ( a . time ) . getTime ( ) < new Date ( b . time ) . getTime ( ) ? 1 : - 1
76
+ ) as ChangeEvent [ ] ,
77
+ }
78
+ } )
79
+ . sort ( ( a , b ) => {
80
+ // size in latest event
81
+ const aSize = a . events [ 0 ] . size
82
+ const bSize = b . events [ 0 ] . size
83
+ return aSize < bSize ? 1 : - 1
84
+ } )
77
85
} , [ setAdditions , setRemovals ] )
86
+
87
+ // calculate churn %: contracts removed size / total size
88
+ const totalSize = useMemo (
89
+ ( ) => changes . reduce ( ( acc , { events } ) => acc + events [ 0 ] . size , 0 ) ,
90
+ [ changes ]
91
+ )
92
+ const removals = useMemo (
93
+ ( ) => changes . filter ( ( { events } ) => events [ 0 ] . type === 'removal' ) ,
94
+ [ changes ]
95
+ )
96
+ const additions = useMemo (
97
+ ( ) => changes . filter ( ( { events } ) => events [ 0 ] . type === 'addition' ) ,
98
+ [ changes ]
99
+ )
100
+ const removedSize = useMemo (
101
+ ( ) => removals . reduce ( ( acc , { events } ) => acc + events [ 0 ] . size , 0 ) ,
102
+ [ removals ]
103
+ )
104
+ const churn = useMemo (
105
+ ( ) => ( removedSize / totalSize ) * 100 ,
106
+ [ removedSize , totalSize ]
107
+ )
108
+
78
109
return (
79
110
< div className = "flex flex-col gap-2" >
80
- < Text size = "12" color = "subtle" ellipsis >
81
- contract set changes
82
- </ Text >
111
+ < div className = "flex gap-2 items-center pr-1" >
112
+ < Text size = "12" color = "subtle" ellipsis >
113
+ contract set changes
114
+ </ Text >
115
+ < div className = "flex-1" />
116
+ < Tooltip
117
+ content = { `${ humanBytes ( removedSize ) } of ${ humanBytes (
118
+ totalSize
119
+ ) } contract size removed`}
120
+ >
121
+ < div className = "flex gap-1 items-center" >
122
+ < Text size = "12" color = "contrast" ellipsis >
123
+ churn: { churn . toFixed ( 2 ) } %
124
+ </ Text >
125
+ < Text size = "12" color = "subtle" ellipsis >
126
+ ({ humanBytes ( removedSize ) } / { humanBytes ( totalSize ) } )
127
+ </ Text >
128
+ </ div >
129
+ </ Tooltip >
130
+ < div className = "flex gap-1 items-center" >
131
+ < Tooltip content = { `${ additions . length } contracts added` } >
132
+ < Text
133
+ size = "12"
134
+ color = "green"
135
+ ellipsis
136
+ className = "flex items-center"
137
+ >
138
+ < Add16 />
139
+ { additions . length }
140
+ </ Text >
141
+ </ Tooltip >
142
+ < Tooltip content = { `${ removals . length } contracts removed` } >
143
+ < Text size = "12" color = "red" ellipsis className = "flex items-center" >
144
+ < Subtract16 />
145
+ { removals . length }
146
+ </ Text >
147
+ </ Tooltip >
148
+ </ div >
149
+ </ div >
83
150
< div className = "flex flex-col gap-3 mb-2" >
84
151
{ changes . map ( ( { contractId, hostKey, events } , i ) => (
85
152
< ContractSetChange
@@ -104,11 +171,12 @@ function ContractSetChange({
104
171
i : number
105
172
} ) {
106
173
return (
107
- < div className = "flex flex-col gap-2 " >
108
- < div className = "flex gap-2 justify-between items-center" >
109
- < Text size = "12" ellipsis >
174
+ < div className = "flex flex-col gap-[3px] " >
175
+ < div className = "flex gap-2 items-center px-[3px] " >
176
+ < Text size = "12" weight = "medium" ellipsis >
110
177
{ i + 1 } .
111
178
</ Text >
179
+ < div className = "flex-1" />
112
180
< div className = "flex gap-2 items-center" >
113
181
< Text size = "12" color = "subtle" ellipsis >
114
182
contract
@@ -119,6 +187,9 @@ function ContractSetChange({
119
187
contextMenu = {
120
188
< ContractContextMenuFromId
121
189
id = { contractId }
190
+ buttonProps = { {
191
+ size : 'none' ,
192
+ } }
122
193
contentProps = { {
123
194
align : 'end' ,
124
195
} }
@@ -137,6 +208,9 @@ function ContractSetChange({
137
208
contextMenu = {
138
209
< HostContextMenuFromKey
139
210
hostKey = { hostKey }
211
+ buttonProps = { {
212
+ size : 'none' ,
213
+ } }
140
214
contentProps = { {
141
215
align : 'end' ,
142
216
} }
@@ -145,7 +219,7 @@ function ContractSetChange({
145
219
/>
146
220
</ div >
147
221
</ div >
148
- { events . map ( ( { type, reasons, size, time } ) => (
222
+ { events . map ( ( { type, reasons, size, time } , i ) => (
149
223
< Tooltip
150
224
key = { type + reasons + time }
151
225
content = { type === 'addition' ? 'added' : `removed: ${ reasons } ` }
@@ -154,8 +228,12 @@ function ContractSetChange({
154
228
>
155
229
< div
156
230
className = { cx (
157
- 'flex gap-2 justify-between' ,
158
- type === 'addition' ? 'bg-green-400/20' : 'bg-red-400/20'
231
+ 'flex gap-2 justify-between mr-2 pr-1' ,
232
+ i === 0
233
+ ? type === 'addition'
234
+ ? 'bg-green-400/20'
235
+ : 'bg-red-400/20'
236
+ : 'opacity-50'
159
237
) }
160
238
>
161
239
< div className = "flex gap-1 items-center overflow-hidden" >
@@ -168,15 +246,15 @@ function ContractSetChange({
168
246
</ div >
169
247
< div className = "flex-1" />
170
248
< div className = "flex gap-2" >
171
- < Text size = "12" color = "subtle " ellipsis >
249
+ < Text color = "subtle" size = "12 " ellipsis >
172
250
time
173
251
</ Text >
174
252
< Text size = "12" ellipsis >
175
- { format ( new Date ( time ) , 'yyyy-MM-dd HH:mm a' ) }
253
+ { formatRelative ( new Date ( time ) , new Date ( ) ) }
176
254
</ Text >
177
255
</ div >
178
256
< div className = "flex gap-2" >
179
- < Text size = "12" color = "subtle " ellipsis >
257
+ < Text color = "subtle" size = "12 " ellipsis >
180
258
size
181
259
</ Text >
182
260
< Text size = "12" ellipsis >
0 commit comments