@apollo-annotation/jbrowse-plugin-apollo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/index.esm.js +10248 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
- package/package.json +130 -0
- package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
- package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
- package/src/ApolloInternetAccount/configSchema.ts +26 -0
- package/src/ApolloInternetAccount/index.ts +2 -0
- package/src/ApolloInternetAccount/model.ts +448 -0
- package/src/ApolloJobModel.ts +117 -0
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
- package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
- package/src/ApolloSequenceAdapter/index.ts +21 -0
- package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
- package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
- package/src/ApolloSixFrameRenderer/index.ts +3 -0
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
- package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
- package/src/ApolloTextSearchAdapter/index.ts +18 -0
- package/src/BackendDrivers/BackendDriver.ts +31 -0
- package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
- package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
- package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
- package/src/BackendDrivers/index.ts +4 -0
- package/src/ChangeManager.ts +148 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
- package/src/LinearApolloDisplay/components/index.ts +1 -0
- package/src/LinearApolloDisplay/configSchema.ts +16 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
- package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
- package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
- package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
- package/src/LinearApolloDisplay/index.ts +2 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
- package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
- package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
- package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
- package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
- package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
- package/src/LinearApolloDisplay/types.ts +1 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
- package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
- package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
- package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
- package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
- package/src/OntologyManager/OntologyStore/index.ts +526 -0
- package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
- package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
- package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
- package/src/OntologyManager/index.ts +173 -0
- package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
- package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
- package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
- package/src/SixFrameFeatureDisplay/index.ts +2 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
- package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
- package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
- package/src/TabularEditor/HybridGrid/index.ts +1 -0
- package/src/TabularEditor/TabularEditorPane.tsx +34 -0
- package/src/TabularEditor/index.ts +3 -0
- package/src/TabularEditor/model.ts +44 -0
- package/src/TabularEditor/types.ts +3 -0
- package/src/components/AddAssembly.tsx +464 -0
- package/src/components/AddChildFeature.tsx +247 -0
- package/src/components/AddFeature.tsx +252 -0
- package/src/components/CopyFeature.tsx +328 -0
- package/src/components/DeleteAssembly.tsx +185 -0
- package/src/components/DeleteFeature.tsx +90 -0
- package/src/components/Dialog.tsx +47 -0
- package/src/components/DownloadGFF3.tsx +213 -0
- package/src/components/ImportFeatures.tsx +295 -0
- package/src/components/ManageChecks.tsx +280 -0
- package/src/components/ManageUsers.tsx +218 -0
- package/src/components/ModifyFeatureAttribute.tsx +457 -0
- package/src/components/OntologyTermAutocomplete.tsx +240 -0
- package/src/components/OntologyTermMultiSelect.tsx +349 -0
- package/src/components/OpenLocalFile.tsx +178 -0
- package/src/components/ViewChangeLog.tsx +208 -0
- package/src/components/ViewCheckResults.tsx +151 -0
- package/src/components/index.ts +12 -0
- package/src/config.ts +10 -0
- package/src/declare.d.ts +3 -0
- package/src/extensions/annotationFromPileup.ts +208 -0
- package/src/extensions/index.ts +1 -0
- package/src/index.ts +394 -0
- package/src/makeDisplayComponent.tsx +244 -0
- package/src/session/ClientDataStore.ts +282 -0
- package/src/session/index.ts +1 -0
- package/src/session/session.ts +373 -0
- package/src/types.ts +10 -0
- package/src/util/index.ts +31 -0
- package/src/util/loadAssemblyIntoClient.ts +291 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
|
|
2
|
+
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Checkbox,
|
|
6
|
+
DialogActions,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogContentText,
|
|
9
|
+
MenuItem,
|
|
10
|
+
Paper,
|
|
11
|
+
Select,
|
|
12
|
+
SelectChangeEvent,
|
|
13
|
+
Table,
|
|
14
|
+
TableBody,
|
|
15
|
+
TableCell,
|
|
16
|
+
TableContainer,
|
|
17
|
+
TableHead,
|
|
18
|
+
TableRow,
|
|
19
|
+
} from '@mui/material'
|
|
20
|
+
import { getRoot } from 'mobx-state-tree'
|
|
21
|
+
import React, { useEffect, useState } from 'react'
|
|
22
|
+
|
|
23
|
+
import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
|
|
24
|
+
import {
|
|
25
|
+
ApolloInternetAccount,
|
|
26
|
+
CollaborationServerDriver,
|
|
27
|
+
} from '../BackendDrivers'
|
|
28
|
+
import { ApolloSessionModel } from '../session'
|
|
29
|
+
import { ApolloRootModel } from '../types'
|
|
30
|
+
import { createFetchErrorMessage } from '../util'
|
|
31
|
+
import { Dialog } from './Dialog'
|
|
32
|
+
|
|
33
|
+
interface ManageChecksProps {
|
|
34
|
+
session: ApolloSessionModel
|
|
35
|
+
handleClose(): void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface AssemblyDocument {
|
|
39
|
+
_id: string
|
|
40
|
+
name: string
|
|
41
|
+
checks: string[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface CheckDocument {
|
|
45
|
+
_id: string
|
|
46
|
+
name: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function ManageChecks({ handleClose, session }: ManageChecksProps) {
|
|
50
|
+
const { internetAccounts } = getRoot<ApolloRootModel>(session)
|
|
51
|
+
const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
|
|
52
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
53
|
+
const [submitted, setSubmitted] = useState(false)
|
|
54
|
+
const apolloInternetAccounts = internetAccounts.filter(
|
|
55
|
+
(ia) => ia.type === 'ApolloInternetAccount',
|
|
56
|
+
) as ApolloInternetAccountModel[]
|
|
57
|
+
if (apolloInternetAccounts.length === 0) {
|
|
58
|
+
throw new Error('No Apollo internet account found')
|
|
59
|
+
}
|
|
60
|
+
const [selectedInternetAccount, setSelectedInternetAccount] = useState(
|
|
61
|
+
apolloInternetAccounts[0],
|
|
62
|
+
)
|
|
63
|
+
const [checks, setChecks] = useState<CheckDocument[]>([])
|
|
64
|
+
const [selectedChecks, setSelectedChecks] = useState<string[]>([])
|
|
65
|
+
|
|
66
|
+
const { collaborationServerDriver } = session.apolloDataStore as {
|
|
67
|
+
collaborationServerDriver: CollaborationServerDriver
|
|
68
|
+
getInternetAccount(
|
|
69
|
+
assemblyName?: string,
|
|
70
|
+
internetAccountId?: string,
|
|
71
|
+
): ApolloInternetAccount
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const assemblies = collaborationServerDriver.getAssemblies()
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
async function getChecks() {
|
|
78
|
+
const { baseURL, getFetcher } = selectedInternetAccount
|
|
79
|
+
const uri = new URL('/checks/types', baseURL).href
|
|
80
|
+
const apolloFetch = getFetcher({ locationType: 'UriLocation', uri })
|
|
81
|
+
const response = await apolloFetch(uri, { method: 'GET' })
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
const newErrorMessage = await createFetchErrorMessage(
|
|
84
|
+
response,
|
|
85
|
+
'Error when retrieving checks from server',
|
|
86
|
+
)
|
|
87
|
+
setErrorMessage(newErrorMessage)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
const data = (await response.json()) as CheckDocument[]
|
|
91
|
+
setChecks(data)
|
|
92
|
+
}
|
|
93
|
+
getChecks().catch((error) => setErrorMessage(String(error)))
|
|
94
|
+
}, [selectedInternetAccount])
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (assemblies.length > 0 && selectedAssembly === undefined) {
|
|
98
|
+
setSelectedAssembly(assemblies[0])
|
|
99
|
+
}
|
|
100
|
+
}, [assemblies, selectedAssembly])
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
async function getChecks() {
|
|
104
|
+
if (!selectedAssembly) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
const { baseURL, getFetcher } = selectedInternetAccount
|
|
108
|
+
const uri = new URL(`/assemblies/${selectedAssembly.name}`, baseURL).href
|
|
109
|
+
const apolloFetch = getFetcher({ locationType: 'UriLocation', uri })
|
|
110
|
+
const response = await apolloFetch(uri, { method: 'GET' })
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const newErrorMessage = await createFetchErrorMessage(
|
|
113
|
+
response,
|
|
114
|
+
'Error when retrieving assembly from server',
|
|
115
|
+
)
|
|
116
|
+
setErrorMessage(newErrorMessage)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
const assembly = (await response.json()) as AssemblyDocument
|
|
120
|
+
setSelectedChecks(assembly.checks)
|
|
121
|
+
}
|
|
122
|
+
getChecks().catch((error) => setErrorMessage(String(error)))
|
|
123
|
+
}, [selectedAssembly, selectedInternetAccount])
|
|
124
|
+
|
|
125
|
+
function handleChangeAssembly(e: SelectChangeEvent<string>) {
|
|
126
|
+
const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
|
|
127
|
+
setSelectedAssembly(newAssembly)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
131
|
+
event.preventDefault()
|
|
132
|
+
if (!selectedAssembly) {
|
|
133
|
+
setErrorMessage('Must select assembly!')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
const { notify } = session as unknown as AbstractSessionModel
|
|
137
|
+
const { baseURL, getFetcher } = selectedInternetAccount
|
|
138
|
+
const uri = new URL('/assemblies/checks', baseURL).href
|
|
139
|
+
const apolloFetch = getFetcher({
|
|
140
|
+
locationType: 'UriLocation',
|
|
141
|
+
uri,
|
|
142
|
+
})
|
|
143
|
+
const response = await apolloFetch(uri, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
_id: selectedAssembly.name,
|
|
147
|
+
checks: selectedChecks,
|
|
148
|
+
name: '',
|
|
149
|
+
}),
|
|
150
|
+
headers: { 'Content-Type': 'application/json' },
|
|
151
|
+
})
|
|
152
|
+
if (response.ok) {
|
|
153
|
+
notify('Assembly checks updated successfully', 'success')
|
|
154
|
+
handleClose()
|
|
155
|
+
} else {
|
|
156
|
+
const newErrorMessage = await createFetchErrorMessage(
|
|
157
|
+
response,
|
|
158
|
+
'Error when updating assembly checks',
|
|
159
|
+
)
|
|
160
|
+
setErrorMessage(newErrorMessage)
|
|
161
|
+
}
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function handleCheckboxChange(
|
|
166
|
+
e: React.ChangeEvent<HTMLInputElement>,
|
|
167
|
+
checked: boolean,
|
|
168
|
+
): void {
|
|
169
|
+
const checks = [...selectedChecks]
|
|
170
|
+
const _id = e.target.value
|
|
171
|
+
if (checked) {
|
|
172
|
+
if (!checks.includes(_id)) {
|
|
173
|
+
checks.push(_id)
|
|
174
|
+
setSelectedChecks(checks)
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
const index = checks.indexOf(_id, 0)
|
|
178
|
+
if (index > -1) {
|
|
179
|
+
checks.splice(index, 1)
|
|
180
|
+
}
|
|
181
|
+
setSelectedChecks(checks)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleChangeInternetAccount(e: SelectChangeEvent<string>) {
|
|
186
|
+
setSubmitted(false)
|
|
187
|
+
const newlySelectedInternetAccount = apolloInternetAccounts.find(
|
|
188
|
+
(ia) => ia.internetAccountId === e.target.value,
|
|
189
|
+
)
|
|
190
|
+
if (!newlySelectedInternetAccount) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Could not find internetAccount with ID "${e.target.value}"`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
setSelectedInternetAccount(newlySelectedInternetAccount)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<Dialog
|
|
200
|
+
open
|
|
201
|
+
title="Manage Checks"
|
|
202
|
+
handleClose={handleClose}
|
|
203
|
+
data-testid="manage-checks"
|
|
204
|
+
>
|
|
205
|
+
<form onSubmit={onSubmit}>
|
|
206
|
+
<DialogContent>
|
|
207
|
+
{apolloInternetAccounts.length > 1 ? (
|
|
208
|
+
<>
|
|
209
|
+
<DialogContentText>Select account</DialogContentText>
|
|
210
|
+
<Select
|
|
211
|
+
value={selectedInternetAccount.internetAccountId}
|
|
212
|
+
onChange={handleChangeInternetAccount}
|
|
213
|
+
disabled={submitted && !errorMessage}
|
|
214
|
+
>
|
|
215
|
+
{internetAccounts.map((option) => (
|
|
216
|
+
<MenuItem key={option.id} value={option.internetAccountId}>
|
|
217
|
+
{option.name}
|
|
218
|
+
</MenuItem>
|
|
219
|
+
))}
|
|
220
|
+
</Select>
|
|
221
|
+
</>
|
|
222
|
+
) : null}
|
|
223
|
+
<DialogContentText>Select assembly</DialogContentText>
|
|
224
|
+
<Select
|
|
225
|
+
style={{ width: 300 }}
|
|
226
|
+
labelId="label"
|
|
227
|
+
value={selectedAssembly?.name ?? ''}
|
|
228
|
+
onChange={handleChangeAssembly}
|
|
229
|
+
disabled={assemblies.length === 0}
|
|
230
|
+
>
|
|
231
|
+
{assemblies.map((option) => (
|
|
232
|
+
<MenuItem key={option.name} value={option.name}>
|
|
233
|
+
{option.displayName ?? option.name}
|
|
234
|
+
</MenuItem>
|
|
235
|
+
))}
|
|
236
|
+
</Select>
|
|
237
|
+
<br />
|
|
238
|
+
<br />
|
|
239
|
+
<TableContainer component={Paper}>
|
|
240
|
+
<Table>
|
|
241
|
+
<TableHead>
|
|
242
|
+
<TableRow>
|
|
243
|
+
<TableCell>Check name</TableCell>
|
|
244
|
+
<TableCell>Use check</TableCell>
|
|
245
|
+
</TableRow>
|
|
246
|
+
</TableHead>
|
|
247
|
+
<TableBody>
|
|
248
|
+
{checks.map((check) => (
|
|
249
|
+
<TableRow key={check._id}>
|
|
250
|
+
<TableCell>{check.name}</TableCell>
|
|
251
|
+
<TableCell>
|
|
252
|
+
<Checkbox
|
|
253
|
+
value={check._id}
|
|
254
|
+
checked={selectedChecks.includes(check._id)}
|
|
255
|
+
onChange={handleCheckboxChange}
|
|
256
|
+
/>
|
|
257
|
+
</TableCell>
|
|
258
|
+
</TableRow>
|
|
259
|
+
))}
|
|
260
|
+
</TableBody>
|
|
261
|
+
</Table>
|
|
262
|
+
</TableContainer>
|
|
263
|
+
</DialogContent>
|
|
264
|
+
<DialogActions>
|
|
265
|
+
<Button variant="contained" type="submit">
|
|
266
|
+
Submit
|
|
267
|
+
</Button>
|
|
268
|
+
<Button variant="outlined" type="submit" onClick={handleClose}>
|
|
269
|
+
Cancel
|
|
270
|
+
</Button>
|
|
271
|
+
</DialogActions>
|
|
272
|
+
</form>
|
|
273
|
+
{errorMessage ? (
|
|
274
|
+
<DialogContent>
|
|
275
|
+
<DialogContentText color="error">{errorMessage}</DialogContentText>
|
|
276
|
+
</DialogContent>
|
|
277
|
+
) : null}
|
|
278
|
+
</Dialog>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { AbstractRootModel } from '@jbrowse/core/util'
|
|
2
|
+
import DeleteIcon from '@mui/icons-material/Delete'
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
DialogActions,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogContentText,
|
|
8
|
+
MenuItem,
|
|
9
|
+
Select,
|
|
10
|
+
SelectChangeEvent,
|
|
11
|
+
} from '@mui/material'
|
|
12
|
+
import {
|
|
13
|
+
DataGrid,
|
|
14
|
+
GridActionsCellItem,
|
|
15
|
+
GridCellParams,
|
|
16
|
+
GridColDef,
|
|
17
|
+
GridRowId,
|
|
18
|
+
GridRowModel,
|
|
19
|
+
GridRowParams,
|
|
20
|
+
GridToolbar,
|
|
21
|
+
} from '@mui/x-data-grid'
|
|
22
|
+
import { DeleteUserChange, UserChange } from 'apollo-shared'
|
|
23
|
+
import { getRoot } from 'mobx-state-tree'
|
|
24
|
+
import React, { useCallback, useEffect, useState } from 'react'
|
|
25
|
+
|
|
26
|
+
import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
|
|
27
|
+
import { ChangeManager } from '../ChangeManager'
|
|
28
|
+
import { ApolloSessionModel } from '../session'
|
|
29
|
+
import { createFetchErrorMessage } from '../util'
|
|
30
|
+
import { Dialog } from './Dialog'
|
|
31
|
+
|
|
32
|
+
interface UserResponse {
|
|
33
|
+
_id: string
|
|
34
|
+
username: string
|
|
35
|
+
email: string
|
|
36
|
+
role?: '' | 'admin' | 'user' | 'readOnly'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ManageUsersProps {
|
|
40
|
+
session: ApolloSessionModel
|
|
41
|
+
handleClose(): void
|
|
42
|
+
changeManager: ChangeManager
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ApolloRootModel extends AbstractRootModel {
|
|
46
|
+
internetAccounts: ApolloInternetAccountModel[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function ManageUsers({
|
|
50
|
+
changeManager,
|
|
51
|
+
handleClose,
|
|
52
|
+
session,
|
|
53
|
+
}: ManageUsersProps) {
|
|
54
|
+
const { internetAccounts } = getRoot<ApolloRootModel>(session)
|
|
55
|
+
const apolloInternetAccounts = internetAccounts.filter(
|
|
56
|
+
(ia) => ia.type === 'ApolloInternetAccount' && ia.role?.includes('admin'),
|
|
57
|
+
)
|
|
58
|
+
if (apolloInternetAccounts.length === 0) {
|
|
59
|
+
throw new Error('No Apollo internet account found')
|
|
60
|
+
}
|
|
61
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
62
|
+
const [selectedInternetAccount, setSelectedInternetAccount] = useState(
|
|
63
|
+
apolloInternetAccounts[0],
|
|
64
|
+
)
|
|
65
|
+
const [users, setUsers] = useState<UserResponse[]>([])
|
|
66
|
+
|
|
67
|
+
const getUsers = useCallback(async () => {
|
|
68
|
+
const { baseURL } = selectedInternetAccount
|
|
69
|
+
const uri = new URL('/users', baseURL).href
|
|
70
|
+
const apolloFetch = selectedInternetAccount?.getFetcher({
|
|
71
|
+
locationType: 'UriLocation',
|
|
72
|
+
uri,
|
|
73
|
+
})
|
|
74
|
+
if (apolloFetch) {
|
|
75
|
+
const response = await apolloFetch(uri, { method: 'GET' })
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const newErrorMessage = await createFetchErrorMessage(
|
|
78
|
+
response,
|
|
79
|
+
'Error when getting user data from db',
|
|
80
|
+
)
|
|
81
|
+
setErrorMessage(newErrorMessage)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const data = (await response.json()) as UserResponse[]
|
|
85
|
+
setUsers(data.map((u) => (u.role === undefined ? { ...u, role: '' } : u)))
|
|
86
|
+
}
|
|
87
|
+
}, [selectedInternetAccount])
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
getUsers().catch((error) => setErrorMessage(String(error)))
|
|
91
|
+
}, [getUsers])
|
|
92
|
+
|
|
93
|
+
async function deleteUser(id: GridRowId) {
|
|
94
|
+
const change = new DeleteUserChange({
|
|
95
|
+
typeName: 'DeleteUserChange',
|
|
96
|
+
userId: id as string,
|
|
97
|
+
})
|
|
98
|
+
await changeManager.submit(change, {
|
|
99
|
+
internetAccountId: selectedInternetAccount.internetAccountId,
|
|
100
|
+
})
|
|
101
|
+
setUsers((prevUsers) => prevUsers.filter((row) => row._id !== id))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isCurrentUser(id: GridRowId) {
|
|
105
|
+
if (id === selectedInternetAccount.getUserId()) {
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const gridColumns: GridColDef[] = [
|
|
112
|
+
{ field: 'username', headerName: 'User', width: 140 },
|
|
113
|
+
{ field: 'email', headerName: 'Email', width: 160 },
|
|
114
|
+
{
|
|
115
|
+
field: 'role',
|
|
116
|
+
headerName: 'Role',
|
|
117
|
+
width: 140,
|
|
118
|
+
type: 'singleSelect',
|
|
119
|
+
valueOptions: ['', 'readOnly', 'user', 'admin'],
|
|
120
|
+
editable: true,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
field: 'actions',
|
|
124
|
+
type: 'actions',
|
|
125
|
+
getActions: (params: GridRowParams) => [
|
|
126
|
+
<GridActionsCellItem
|
|
127
|
+
key={`delete-${params.id}`}
|
|
128
|
+
icon={<DeleteIcon />}
|
|
129
|
+
onClick={async () => {
|
|
130
|
+
if (window.confirm('Delete this user?')) {
|
|
131
|
+
await deleteUser(params.id)
|
|
132
|
+
}
|
|
133
|
+
}}
|
|
134
|
+
disabled={isCurrentUser(params.id)}
|
|
135
|
+
label="Delete"
|
|
136
|
+
/>,
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
function handleChangeInternetAccount(e: SelectChangeEvent<string>) {
|
|
142
|
+
const newlySelectedInternetAccount = apolloInternetAccounts.find(
|
|
143
|
+
(ia) => ia.internetAccountId === e.target.value,
|
|
144
|
+
)
|
|
145
|
+
if (!newlySelectedInternetAccount) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Could not find internetAccount with ID "${e.target.value}"`,
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
setSelectedInternetAccount(newlySelectedInternetAccount)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function processRowUpdate(newRow: GridRowModel) {
|
|
154
|
+
const change = new UserChange({
|
|
155
|
+
typeName: 'UserChange',
|
|
156
|
+
role: newRow.role,
|
|
157
|
+
userId: newRow._id,
|
|
158
|
+
})
|
|
159
|
+
await changeManager.submit(change, {
|
|
160
|
+
internetAccountId: selectedInternetAccount.internetAccountId,
|
|
161
|
+
})
|
|
162
|
+
return newRow
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<Dialog
|
|
167
|
+
open
|
|
168
|
+
fullScreen
|
|
169
|
+
title="Manage users"
|
|
170
|
+
handleClose={handleClose}
|
|
171
|
+
data-testid="manage-users"
|
|
172
|
+
>
|
|
173
|
+
<DialogContent>
|
|
174
|
+
{apolloInternetAccounts.length > 1 ? (
|
|
175
|
+
<>
|
|
176
|
+
<DialogContentText>Select account</DialogContentText>
|
|
177
|
+
<Select
|
|
178
|
+
value={selectedInternetAccount.internetAccountId}
|
|
179
|
+
onChange={handleChangeInternetAccount}
|
|
180
|
+
disabled={!errorMessage}
|
|
181
|
+
>
|
|
182
|
+
{internetAccounts.map((option) => (
|
|
183
|
+
<MenuItem key={option.id} value={option.internetAccountId}>
|
|
184
|
+
{option.name}
|
|
185
|
+
</MenuItem>
|
|
186
|
+
))}
|
|
187
|
+
</Select>
|
|
188
|
+
</>
|
|
189
|
+
) : null}
|
|
190
|
+
<div style={{ height: '100%', width: '100%' }}>
|
|
191
|
+
<DataGrid
|
|
192
|
+
pagination
|
|
193
|
+
rows={users}
|
|
194
|
+
columns={gridColumns}
|
|
195
|
+
getRowId={(row) => row._id}
|
|
196
|
+
slots={{ toolbar: GridToolbar }}
|
|
197
|
+
getRowHeight={() => 'auto'}
|
|
198
|
+
isCellEditable={(params: GridCellParams) =>
|
|
199
|
+
!isCurrentUser(params.id)
|
|
200
|
+
}
|
|
201
|
+
processRowUpdate={processRowUpdate}
|
|
202
|
+
onProcessRowUpdateError={(error) => setErrorMessage(String(error))}
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
</DialogContent>
|
|
206
|
+
<DialogActions>
|
|
207
|
+
<Button variant="outlined" type="submit" onClick={handleClose}>
|
|
208
|
+
Close
|
|
209
|
+
</Button>
|
|
210
|
+
</DialogActions>
|
|
211
|
+
{errorMessage ? (
|
|
212
|
+
<DialogContent>
|
|
213
|
+
<DialogContentText color="error">{errorMessage}</DialogContentText>
|
|
214
|
+
</DialogContent>
|
|
215
|
+
) : null}
|
|
216
|
+
</Dialog>
|
|
217
|
+
)
|
|
218
|
+
}
|