Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/grid-ui/src/screens/Overview/Overview.tsx
2887 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
import Grid from '@mui/material/Grid'
19
import Paper from '@mui/material/Paper'
20
import { loader } from 'graphql.macro'
21
import React from 'react'
22
import { useState, useEffect, useMemo } from 'react'
23
import {
24
Box,
25
Checkbox,
26
FormControl,
27
FormControlLabel,
28
InputLabel,
29
MenuItem,
30
Select
31
} from '@mui/material'
32
import Node from '../../components/Node/Node'
33
import { useQuery } from '@apollo/client'
34
import NodeInfo from '../../models/node-info'
35
import OsInfo from '../../models/os-info'
36
import NoData from '../../components/NoData/NoData'
37
import Loading from '../../components/Loading/Loading'
38
import Error from '../../components/Error/Error'
39
import StereotypeInfo from '../../models/stereotype-info'
40
import browserVersion from '../../util/browser-version'
41
import Capabilities from '../../models/capabilities'
42
import { GridConfig } from '../../config'
43
import { NODES_QUERY } from '../../graphql/nodes'
44
import { GRID_SESSIONS_QUERY } from '../../graphql/sessions'
45
46
function Overview (): JSX.Element {
47
const { loading, error, data } = useQuery(NODES_QUERY, {
48
pollInterval: GridConfig.status.xhrPollingIntervalMillis,
49
fetchPolicy: 'network-only'
50
})
51
52
const { data: sessionsData } = useQuery(GRID_SESSIONS_QUERY, {
53
pollInterval: GridConfig.status.xhrPollingIntervalMillis,
54
fetchPolicy: 'network-only'
55
})
56
57
function compareSlotStereotypes(a: NodeInfo, b: NodeInfo, attribute: string): number {
58
const joinA = a.slotStereotypes.length === 1
59
? a.slotStereotypes[0][attribute]
60
: a.slotStereotypes.slice().map(st => st[attribute]).reverse().join(',')
61
const joinB = b.slotStereotypes.length === 1
62
? b.slotStereotypes[0][attribute]
63
: b.slotStereotypes.slice().map(st => st[attribute]).reverse().join(',')
64
return joinA.localeCompare(joinB)
65
}
66
67
const sortProperties = {
68
'platformName': (a, b) => compareSlotStereotypes(a, b, 'platformName'),
69
'status': (a, b) => (a.status ?? '').localeCompare(b.status ?? ''),
70
'uri': (a, b) => (a.uri ?? '').localeCompare(b.uri ?? ''),
71
'browserName': (a, b) => compareSlotStereotypes(a, b, 'browserName'),
72
'browserVersion': (a, b) => compareSlotStereotypes(a, b, 'browserVersion'),
73
'slotCount': (a, b) => {
74
const valueA = a.slotStereotypes.reduce((sum, st) => sum + st.slotCount, 0)
75
const valueB = b.slotStereotypes.reduce((sum, st) => sum + st.slotCount, 0)
76
return valueA < valueB ? -1 : 1
77
},
78
'id': (a, b) => (a.id < b.id ? -1 : 1)
79
}
80
81
const sortPropertiesLabel = {
82
'platformName': 'Platform Name',
83
'status': 'Status',
84
'uri': 'URI',
85
'browserName': 'Browser Name',
86
'browserVersion': 'Browser Version',
87
'slotCount': 'Slot Count',
88
'id': 'ID'
89
}
90
91
const [sortOption, setSortOption] = useState(Object.keys(sortProperties)[0])
92
const [sortOrder, setSortOrder] = useState(1)
93
const [sortedNodes, setSortedNodes] = useState<NodeInfo[]>([])
94
const [isDescending, setIsDescending] = useState(false)
95
96
const handleSortChange = (event: React.ChangeEvent<{ value: unknown }>) => {
97
setSortOption(event.target.value as string)
98
}
99
100
const handleOrderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
101
setIsDescending(event.target.checked)
102
setSortOrder(event.target.checked ? -1 : 1)
103
}
104
105
const sortNodes = useMemo(() => {
106
return (nodes: NodeInfo[], option: string, order: number) => {
107
const sortFn = sortProperties[option] || (() => 0)
108
return nodes.sort((a, b) => order * sortFn(a, b))
109
}
110
}, [sortOption, sortOrder])
111
112
useEffect(() => {
113
if (data) {
114
const unSortedNodes = data.nodesInfo.nodes.map((node) => {
115
const osInfo: OsInfo = {
116
name: node.osInfo.name,
117
version: node.osInfo.version,
118
arch: node.osInfo.arch
119
}
120
121
interface StereoTypeData {
122
stereotype: Capabilities;
123
slots: number;
124
}
125
126
const slotStereotypes = (JSON.parse(
127
node.stereotypes) as StereoTypeData[]).map((item) => {
128
const slotStereotype: StereotypeInfo = {
129
browserName: item.stereotype.browserName ?? '',
130
browserVersion: browserVersion(
131
item.stereotype.browserVersion ?? item.stereotype.version),
132
platformName: (item.stereotype.platformName
133
?? item.stereotype.platform) ?? '',
134
slotCount: item.slots,
135
rawData: item
136
}
137
return slotStereotype
138
})
139
140
const newNode: NodeInfo = {
141
uri: node.uri,
142
id: node.id,
143
status: node.status,
144
maxSession: node.maxSession,
145
slotCount: node.slotCount,
146
version: node.version,
147
osInfo: osInfo,
148
sessionCount: node.sessionCount ?? 0,
149
slotStereotypes: slotStereotypes
150
}
151
return newNode
152
})
153
154
setSortedNodes(sortNodes(unSortedNodes, sortOption, sortOrder))
155
}
156
}, [data, sortOption, sortOrder])
157
158
if (error !== undefined) {
159
const message = 'There has been an error while loading the Nodes from the Grid.'
160
const errorMessage = error?.networkError?.message
161
return (
162
<Grid container spacing={3}>
163
<Error message={message} errorMessage={errorMessage}/>
164
</Grid>
165
)
166
}
167
168
if (loading) {
169
return (
170
<Grid container spacing={3}>
171
<Loading/>
172
</Grid>
173
)
174
}
175
176
if (sortedNodes.length === 0) {
177
const shortMessage = 'The Grid has no registered Nodes yet.'
178
return (
179
<Grid container spacing={3}>
180
<NoData message={shortMessage}/>
181
</Grid>
182
)
183
}
184
185
return (
186
<Grid container>
187
<Grid item xs={12}
188
style={{ display: 'flex', justifyContent: 'flex-start' }}>
189
<FormControl variant="outlined" style={{ marginBottom: '16px' }}>
190
<InputLabel>Sort By</InputLabel>
191
<Box display="flex" alignItems="center">
192
<Select value={sortOption} onChange={handleSortChange}
193
label="Sort By" style={{ minWidth: '170px' }}>
194
{Object.keys(sortProperties).map((key) => (
195
<MenuItem key={key} value={key}>
196
{sortPropertiesLabel[key]}
197
</MenuItem>
198
))}
199
</Select>
200
<FormControlLabel
201
control={<Checkbox checked={isDescending}
202
onChange={handleOrderChange}/>}
203
label="Descending"
204
style={{ marginLeft: '8px' }}
205
/>
206
</Box>
207
</FormControl>
208
</Grid>
209
{sortedNodes.map((node, index) => {
210
return (
211
<Grid
212
item
213
lg={6}
214
sm={12}
215
xl={4}
216
xs={12}
217
key={index}
218
paddingX={1}
219
paddingY={1}
220
>
221
<Paper
222
sx={{
223
display: 'flex',
224
overflow: 'auto',
225
flexDirection: 'column'
226
}}
227
>
228
<Node
229
node={node}
230
sessions={sessionsData?.sessionsInfo?.sessions?.filter(
231
session => session.nodeId === node.id
232
) || []}
233
origin={window.location.origin}
234
/>
235
</Paper>
236
</Grid>
237
)
238
})}
239
</Grid>
240
)
241
}
242
243
export default Overview
244
245