@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.
Files changed (116) hide show
  1. package/README.md +76 -0
  2. package/dist/index.esm.js +10248 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +7 -0
  5. package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
  6. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
  7. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
  8. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
  9. package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
  10. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
  11. package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
  12. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
  13. package/package.json +130 -0
  14. package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
  15. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
  16. package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
  17. package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
  18. package/src/ApolloInternetAccount/configSchema.ts +26 -0
  19. package/src/ApolloInternetAccount/index.ts +2 -0
  20. package/src/ApolloInternetAccount/model.ts +448 -0
  21. package/src/ApolloJobModel.ts +117 -0
  22. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
  23. package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
  24. package/src/ApolloSequenceAdapter/index.ts +21 -0
  25. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
  26. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
  27. package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
  28. package/src/ApolloSixFrameRenderer/index.ts +3 -0
  29. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
  30. package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
  31. package/src/ApolloTextSearchAdapter/index.ts +18 -0
  32. package/src/BackendDrivers/BackendDriver.ts +31 -0
  33. package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
  34. package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
  35. package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
  36. package/src/BackendDrivers/index.ts +4 -0
  37. package/src/ChangeManager.ts +148 -0
  38. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
  39. package/src/LinearApolloDisplay/components/index.ts +1 -0
  40. package/src/LinearApolloDisplay/configSchema.ts +16 -0
  41. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
  42. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
  43. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
  44. package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
  45. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
  46. package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
  47. package/src/LinearApolloDisplay/index.ts +2 -0
  48. package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
  49. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
  50. package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
  51. package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
  52. package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
  53. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
  54. package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
  55. package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
  56. package/src/LinearApolloDisplay/types.ts +1 -0
  57. package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
  58. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
  59. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
  60. package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
  61. package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
  62. package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
  63. package/src/OntologyManager/OntologyStore/index.ts +526 -0
  64. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
  65. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
  66. package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
  67. package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
  68. package/src/OntologyManager/index.ts +173 -0
  69. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
  70. package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
  71. package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
  72. package/src/SixFrameFeatureDisplay/index.ts +2 -0
  73. package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
  74. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
  75. package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
  76. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
  77. package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
  78. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
  79. package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
  80. package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
  81. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
  82. package/src/TabularEditor/HybridGrid/index.ts +1 -0
  83. package/src/TabularEditor/TabularEditorPane.tsx +34 -0
  84. package/src/TabularEditor/index.ts +3 -0
  85. package/src/TabularEditor/model.ts +44 -0
  86. package/src/TabularEditor/types.ts +3 -0
  87. package/src/components/AddAssembly.tsx +464 -0
  88. package/src/components/AddChildFeature.tsx +247 -0
  89. package/src/components/AddFeature.tsx +252 -0
  90. package/src/components/CopyFeature.tsx +328 -0
  91. package/src/components/DeleteAssembly.tsx +185 -0
  92. package/src/components/DeleteFeature.tsx +90 -0
  93. package/src/components/Dialog.tsx +47 -0
  94. package/src/components/DownloadGFF3.tsx +213 -0
  95. package/src/components/ImportFeatures.tsx +295 -0
  96. package/src/components/ManageChecks.tsx +280 -0
  97. package/src/components/ManageUsers.tsx +218 -0
  98. package/src/components/ModifyFeatureAttribute.tsx +457 -0
  99. package/src/components/OntologyTermAutocomplete.tsx +240 -0
  100. package/src/components/OntologyTermMultiSelect.tsx +349 -0
  101. package/src/components/OpenLocalFile.tsx +178 -0
  102. package/src/components/ViewChangeLog.tsx +208 -0
  103. package/src/components/ViewCheckResults.tsx +151 -0
  104. package/src/components/index.ts +12 -0
  105. package/src/config.ts +10 -0
  106. package/src/declare.d.ts +3 -0
  107. package/src/extensions/annotationFromPileup.ts +208 -0
  108. package/src/extensions/index.ts +1 -0
  109. package/src/index.ts +394 -0
  110. package/src/makeDisplayComponent.tsx +244 -0
  111. package/src/session/ClientDataStore.ts +282 -0
  112. package/src/session/index.ts +1 -0
  113. package/src/session/session.ts +373 -0
  114. package/src/types.ts +10 -0
  115. package/src/util/index.ts +31 -0
  116. 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
+ }