1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one
3+ * or more contributor license agreements. See the NOTICE file
4+ * distributed with this work for additional information
5+ * regarding copyright ownership. The ASF licenses this file
6+ * to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance
8+ * with the License. You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing, software
13+ * distributed under the License is distributed on an "AS IS" BASIS,
14+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+ * See the License for the specific language governing permissions and
16+ * limitations under the License.
17+ */
18+
19+ import React , { useRef } from 'react' ;
20+ import filesize from 'filesize' ;
21+ import { AxiosError } from 'axios' ;
22+ import { Popover , Table } from 'antd' ;
23+ import {
24+ ColumnsType ,
25+ TablePaginationConfig
26+ } from 'antd/es/table' ;
27+ import { NodeIndexOutlined } from '@ant-design/icons' ;
28+
29+ import { getFormattedTime } from '@/v2/utils/momentUtils' ;
30+ import { showDataFetchError } from '@/utils/common' ;
31+ import { AxiosGetHelper } from '@/utils/axiosRequestHelper' ;
32+ import {
33+ Container , ContainerKeysResponse , ContainerReplica ,
34+ ContainerTableProps ,
35+ ExpandedRowState , KeyResponse
36+ } from '@/v2/types/container.types' ;
37+
38+ const size = filesize . partial ( { standard : 'iec' } ) ;
39+
40+ export const COLUMNS : ColumnsType < Container > = [
41+ {
42+ title : 'Container ID' ,
43+ dataIndex : 'containerID' ,
44+ key : 'containerID' ,
45+ sorter : ( a : Container , b : Container ) => a . containerID - b . containerID
46+ } ,
47+ {
48+ title : 'No. of Keys' ,
49+ dataIndex : 'keys' ,
50+ key : 'keys' ,
51+ sorter : ( a : Container , b : Container ) => a . keys - b . keys
52+ } ,
53+ {
54+ title : 'Actual/Expected Replica(s)' ,
55+ dataIndex : 'expectedReplicaCount' ,
56+ key : 'expectedReplicaCount' ,
57+ render : ( expectedReplicaCount : number , record : Container ) => {
58+ const actualReplicaCount = record . actualReplicaCount ;
59+ return (
60+ < span >
61+ { actualReplicaCount } / { expectedReplicaCount }
62+ </ span >
63+ ) ;
64+ }
65+ } ,
66+ {
67+ title : 'Datanodes' ,
68+ dataIndex : 'replicas' ,
69+ key : 'replicas' ,
70+ render : ( replicas : ContainerReplica [ ] ) => {
71+ const renderDatanodes = ( replicas : ContainerReplica [ ] ) => {
72+ return replicas ?. map ( ( replica : any , idx : number ) => (
73+ < div key = { idx } className = 'datanode-container-v2' >
74+ < NodeIndexOutlined /> { replica . datanodeHost }
75+ </ div >
76+ ) )
77+ }
78+
79+ return (
80+ < Popover
81+ content = { renderDatanodes ( replicas ) }
82+ title = 'Datanodes'
83+ placement = 'bottomRight'
84+ trigger = 'hover' >
85+ < strong > { replicas . length } </ strong > datanodes
86+ </ Popover >
87+ )
88+ }
89+ } ,
90+ {
91+ title : 'Pipeline ID' ,
92+ dataIndex : 'pipelineID' ,
93+ key : 'pipelineID'
94+ } ,
95+ {
96+ title : 'Unhealthy Since' ,
97+ dataIndex : 'unhealthySince' ,
98+ key : 'unhealthySince' ,
99+ render : ( unhealthySince : number ) => getFormattedTime ( unhealthySince , 'lll' ) ,
100+ sorter : ( a : Container , b : Container ) => a . unhealthySince - b . unhealthySince
101+ }
102+ ] ;
103+
104+ const KEY_TABLE_COLUMNS : ColumnsType < KeyResponse > = [
105+ {
106+ title : 'Volume' ,
107+ dataIndex : 'Volume' ,
108+ key : 'Volume'
109+ } ,
110+ {
111+ title : 'Bucket' ,
112+ dataIndex : 'Bucket' ,
113+ key : 'Bucket'
114+ } ,
115+ {
116+ title : 'Key' ,
117+ dataIndex : 'Key' ,
118+ key : 'Key'
119+ } ,
120+ {
121+ title : 'Size' ,
122+ dataIndex : 'DataSize' ,
123+ key : 'DataSize' ,
124+ render : ( dataSize : number ) => < div > { size ( dataSize ) } </ div >
125+ } ,
126+ {
127+ title : 'Date Created' ,
128+ dataIndex : 'CreationTime' ,
129+ key : 'CreationTime' ,
130+ render : ( date : string ) => getFormattedTime ( date , 'lll' )
131+ } ,
132+ {
133+ title : 'Date Modified' ,
134+ dataIndex : 'ModificationTime' ,
135+ key : 'ModificationTime' ,
136+ render : ( date : string ) => getFormattedTime ( date , 'lll' )
137+ } ,
138+ {
139+ title : 'Path' ,
140+ dataIndex : 'CompletePath' ,
141+ key : 'path'
142+ }
143+ ] ;
144+
145+ const ContainerTable : React . FC < ContainerTableProps > = ( {
146+ data,
147+ loading,
148+ selectedColumns,
149+ expandedRow,
150+ expandedRowSetter,
151+ searchColumn = 'containerID' ,
152+ searchTerm = ''
153+ } ) => {
154+
155+ const cancelSignal = useRef < AbortController > ( ) ;
156+
157+ function filterSelectedColumns ( ) {
158+ const columnKeys = selectedColumns . map ( ( column ) => column . value ) ;
159+ return COLUMNS . filter (
160+ ( column ) => columnKeys . indexOf ( column . key as string ) >= 0
161+ ) ;
162+ }
163+
164+ function loadRowData ( containerID : number ) {
165+ const { request, controller } = AxiosGetHelper (
166+ `/api/v1/containers/${ containerID } /keys` ,
167+ cancelSignal . current
168+ ) ;
169+ cancelSignal . current = controller ;
170+
171+ request . then ( response => {
172+ const containerKeysResponse : ContainerKeysResponse = response . data ;
173+ expandedRowSetter ( {
174+ ...expandedRow ,
175+ [ containerID ] : {
176+ ...expandedRow [ containerID ] ,
177+ loading : false ,
178+ dataSource : containerKeysResponse . keys ,
179+ totalCount : containerKeysResponse . totalCount
180+ }
181+ } ) ;
182+ } ) . catch ( error => {
183+ expandedRowSetter ( {
184+ ...expandedRow ,
185+ [ containerID ] : {
186+ ...expandedRow [ containerID ] ,
187+ loading : false
188+ }
189+ } ) ;
190+ showDataFetchError ( ( error as AxiosError ) . toString ( ) ) ;
191+ } ) ;
192+ }
193+
194+ function getFilteredData ( data : Container [ ] ) {
195+
196+ return data ?. filter (
197+ ( container : Container ) => {
198+ return ( searchColumn === 'containerID' )
199+ ? container [ searchColumn ] . toString ( ) . includes ( searchTerm )
200+ : container [ searchColumn ] . includes ( searchTerm )
201+ }
202+ ) ?? [ ] ;
203+ }
204+
205+ function onRowExpandClick ( expanded : boolean , record : Container ) {
206+ if ( expanded ) {
207+ loadRowData ( record . containerID ) ;
208+ }
209+ else {
210+ cancelSignal . current && cancelSignal . current . abort ( ) ;
211+ }
212+ }
213+
214+ function expandedRowRender ( record : Container ) {
215+ const containerId = record . containerID
216+ const containerKeys : ExpandedRowState = expandedRow [ containerId ] ;
217+ const dataSource = containerKeys ?. dataSource ?? [ ] ;
218+ const paginationConfig : TablePaginationConfig = {
219+ showTotal : ( total : number , range ) => `${ range [ 0 ] } -${ range [ 1 ] } of ${ total } Keys`
220+ }
221+
222+ return (
223+ < Table
224+ loading = { containerKeys ?. loading ?? true }
225+ dataSource = { dataSource }
226+ columns = { KEY_TABLE_COLUMNS }
227+ pagination = { paginationConfig }
228+ rowKey = { ( record : KeyResponse ) => `${ record . Volume } /${ record . Bucket } /${ record . Key } ` }
229+ locale = { { filterTitle : '' } } />
230+ )
231+ } ;
232+
233+ const paginationConfig : TablePaginationConfig = {
234+ showTotal : ( total : number , range ) => (
235+ `${ range [ 0 ] } -${ range [ 1 ] } of ${ total } Containers`
236+ ) ,
237+ showSizeChanger : true
238+ } ;
239+
240+ return (
241+ < div >
242+ < Table
243+ rowKey = 'containerID'
244+ dataSource = { getFilteredData ( data ) }
245+ columns = { filterSelectedColumns ( ) }
246+ loading = { loading }
247+ pagination = { paginationConfig }
248+ scroll = { { x : 'max-content' , scrollToFirstRowOnChange : true } }
249+ locale = { { filterTitle : '' } }
250+ expandable = { {
251+ expandRowByClick : true ,
252+ expandedRowRender : expandedRowRender ,
253+ onExpand : onRowExpandClick
254+ } } />
255+ </ div >
256+ ) ;
257+ }
258+
259+ export default ContainerTable ;
0 commit comments