diff --git a/catalog/app/containers/Admin/Users/Users.js b/catalog/app/containers/Admin/Users/Users.js
deleted file mode 100644
index fc39586cae4..00000000000
--- a/catalog/app/containers/Admin/Users/Users.js
+++ /dev/null
@@ -1,753 +0,0 @@
-import cx from 'classnames'
-import * as FF from 'final-form'
-import * as R from 'ramda'
-import * as React from 'react'
-import * as RF from 'react-final-form'
-import * as M from '@material-ui/core'
-
-import * as Pagination from 'components/Pagination'
-import * as Notifications from 'containers/Notifications'
-import * as APIConnector from 'utils/APIConnector'
-import * as Dialogs from 'utils/Dialogs'
-import { useQueryS } from 'utils/GraphQL'
-import * as Cache from 'utils/ResourceCache'
-import * as Format from 'utils/format'
-import * as validators from 'utils/validators'
-
-import * as Form from '../Form'
-import * as Table from '../Table'
-import * as data from '../data'
-
-import ROLES_QUERY from './gql/Roles.generated'
-
-const useMonoStyles = M.makeStyles((t) => ({
- root: {
- fontFamily: t.typography.monospace.fontFamily,
- },
-}))
-
-function Mono({ className, children }) {
- const classes = useMonoStyles()
- return {children}
-}
-
-const useInviteStyles = M.makeStyles({
- infoIcon: {
- fontSize: '1.25em',
- verticalAlign: '-3px',
- },
-})
-
-// close: PT.func.isRequired,
-// roles: PT.array.isRequired,
-function Invite({ close, roles, defaultRoleId }) {
- const classes = useInviteStyles()
- const req = APIConnector.use()
- const cache = Cache.use()
- const { push } = Notifications.use()
- const onSubmit = React.useCallback(
- async ({ username, email, roleId }) => {
- const role = roles.find((r) => r.id === roleId)
-
- try {
- await req({
- endpoint: '/users/create',
- method: 'POST',
- body: JSON.stringify({ username, email }),
- })
-
- const user = {
- dateJoined: new Date(),
- email,
- isActive: true,
- isAdmin: false,
- lastLogin: new Date(),
- username,
- }
-
- try {
- await req({
- method: 'POST',
- endpoint: '/users/set_role',
- body: JSON.stringify({ username, role: role.name }),
- })
- user.roleId = role.id
- } catch (e) {
- // eslint-disable-next-line no-console
- console.error('Error setting role', { username, role })
- // eslint-disable-next-line no-console
- console.dir(e)
- }
-
- cache.patchOk(data.UsersResource, null, R.append(user))
- push('User invited')
- close()
- } catch (e) {
- if (APIConnector.HTTPError.is(e, 400, /subscription is invalid/)) {
- return { [FF.FORM_ERROR]: 'subscriptionInvalid' }
- }
-
- if (APIConnector.HTTPError.is(e, 400, /Username is invalid/)) {
- return {
- username: 'invalid',
- }
- }
- if (APIConnector.HTTPError.is(e, 409, /Username already taken/)) {
- return {
- username: 'taken',
- }
- }
- if (APIConnector.HTTPError.is(e, 400, /Invalid email/)) {
- return {
- email: 'invalid',
- }
- }
- if (APIConnector.HTTPError.is(e, 409, /Email already taken/)) {
- return {
- email: 'taken',
- }
- }
- if (APIConnector.HTTPError.is(e, 500, /SMTP.*invalid/)) {
- return {
- [FF.FORM_ERROR]: 'smtp',
- }
- }
- // eslint-disable-next-line no-console
- console.error('Error creating user')
- // eslint-disable-next-line no-console
- console.dir(e)
- return {
- [FF.FORM_ERROR]: 'unexpected',
- }
- }
- },
- [req, cache, push, close, roles],
- )
-
- return (
-
- {({
- handleSubmit,
- submitting,
- submitError,
- submitFailed,
- error,
- hasSubmitErrors,
- hasValidationErrors,
- modifiedSinceLastSubmit,
- }) => (
- <>
- Invite a user
-
-
-
-
- close('cancel')}
- color="primary"
- disabled={submitting}
- >
- Cancel
-
-
- Invite
-
-
- >
- )}
-
- )
-}
-
-// close: PT.func.isRequired,
-// user: PT.object.isRequired,
-function Edit({ close, user: { email: oldEmail, username } }) {
- const req = APIConnector.use()
- const cache = Cache.use()
- const { push } = Notifications.use()
-
- const onSubmit = React.useCallback(
- async ({ email }) => {
- if (email === oldEmail) {
- close()
- return
- }
-
- try {
- await req({
- endpoint: '/users/edit_email',
- method: 'POST',
- body: JSON.stringify({ username, email }),
- })
-
- cache.patchOk(
- data.UsersResource,
- null,
- R.map((u) => (u.username === username ? { ...u, email } : u)),
- )
- push('Changes saved')
- close()
- } catch (e) {
- if (APIConnector.HTTPError.is(e, 400, /Another user already has that email/)) {
- return {
- email: 'taken',
- }
- }
- if (APIConnector.HTTPError.is(e, 400, /Invalid email/)) {
- return {
- email: 'invalid',
- }
- }
- // eslint-disable-next-line no-console
- console.error('Error changing email')
- // eslint-disable-next-line no-console
- console.dir(e)
- return {
- [FF.FORM_ERROR]: 'unexpected',
- }
- }
- },
- [close, username, oldEmail, req, cache, push],
- )
-
- return (
-
- {({
- handleSubmit,
- submitting,
- submitFailed,
- error,
- hasSubmitErrors,
- hasValidationErrors,
- modifiedSinceLastSubmit,
- }) => (
- <>
- Edit user: "{username}"
-
-
- )}
-
-
-
-
- close('cancel')}
- color="primary"
- disabled={submitting}
- >
- Cancel
-
-
- Save
-
-
- >
- )}
-
- )
-}
-
-// user: PT.object.isRequired,
-// close: PT.func.isRequired,
-function Delete({ user, close }) {
- const req = APIConnector.use()
- const cache = Cache.use()
- const { push } = Notifications.use()
- const doDelete = React.useCallback(() => {
- close()
- req({
- endpoint: '/users/delete',
- method: 'POST',
- body: JSON.stringify({ username: user.username }),
- })
- .then(() => {
- push(`User "${user.username}" deleted`)
- })
- .catch((e) => {
- // TODO: handle errors once the endpoint is working
- cache.patchOk(data.UsersResource, null, R.append(user))
- push(`Error deleting user "${user.username}"`)
- // eslint-disable-next-line no-console
- console.error('Error deleting user')
- // eslint-disable-next-line no-console
- console.dir(e)
- })
- // optimistically remove the user from cache
- cache.patchOk(data.UsersResource, null, R.reject(R.propEq('username', user.username)))
- }, [user, close, req, cache, push])
-
- return (
- <>
- Delete a user
-
- You are about to delete user "{user.username}". This operation is
- irreversible.
-
-
- close('cancel')} color="primary">
- Cancel
-
-
- Delete
-
-
- >
- )
-}
-
-// admin: PT.bool.isRequired,
-// username: PT.string.isRequired,
-// close: PT.func.isRequired,
-function AdminRights({ username, admin, close }) {
- const req = APIConnector.use()
- const cache = Cache.use()
- const { push } = Notifications.use()
- const doChange = React.useCallback(
- () =>
- close(
- req({
- method: 'POST',
- endpoint: `/users/${admin ? 'grant' : 'revoke'}_admin`,
- body: JSON.stringify({ username }),
- })
- .then(() => {
- cache.patchOk(
- data.UsersResource,
- null,
- R.map((u) => (u.username === username ? { ...u, isAdmin: admin } : u)),
- )
- return 'ok'
- })
- .catch((e) => {
- push(
- `Error ${admin ? 'granting' : 'revoking'} admin status for "${username}"`,
- )
- // eslint-disable-next-line no-console
- console.error('Error changing user admin status', { username, admin })
- // eslint-disable-next-line no-console
- console.dir(e)
- throw e
- }),
- ),
- [admin, close, username, req, cache, push],
- )
-
- return (
- <>
- {admin ? 'Grant' : 'Revoke'} admin rights
-
- You are about to {admin ? 'grant admin rights to' : 'revoke admin rights from'}{' '}
- "{username}".
-
-
- close('cancel')} color="primary">
- Cancel
-
-
- {admin ? 'Grant' : 'Revoke'}
-
-
- >
- )
-}
-
-const useUsernameStyles = M.makeStyles((t) => ({
- root: {
- alignItems: 'center',
- display: 'flex',
- },
- admin: {
- fontWeight: 600,
- },
- icon: {
- fontSize: '1em',
- marginLeft: `calc(-1em - ${t.spacing(0.5)}px)`,
- marginRight: t.spacing(0.5),
- },
-}))
-
-// admin: PT.bool,
-function Username({ className, admin = false, children, ...props }) {
- const classes = useUsernameStyles()
- return (
-
- {admin && security}
- {children}
-
- )
-}
-
-function Editable({ value, onChange, children }) {
- const [busy, setBusy] = React.useState(false)
- const [savedValue, saveValue] = React.useState(value)
- const change = React.useCallback(
- (newValue) => {
- if (savedValue === newValue) return
- if (busy) return
- setBusy(true)
- saveValue(newValue)
- Promise.resolve(onChange(newValue))
- .then(() => {
- setBusy(false)
- })
- .catch((e) => {
- saveValue(savedValue)
- setBusy(false)
- throw e
- })
- },
- [onChange, busy, setBusy, savedValue, saveValue],
- )
-
- return children({ change, busy, value: savedValue })
-}
-
-// not a valid role name
-const emptyRole = ''
-
-function UsersSkeleton() {
- return (
-
-
-
-
- )
-}
-
-// users: PT.object.isRequired,
-export default function Users({ users }) {
- const rows = Cache.suspend(users)
- const { roles, defaultRole } = useQueryS(ROLES_QUERY)
- const defaultRoleId = defaultRole?.id
-
- const req = APIConnector.use()
- const cache = Cache.use()
- const { push } = Notifications.use()
- const dialogs = Dialogs.use()
- const { open: openDialog } = dialogs
-
- const setRole = React.useCallback(
- (username, role) =>
- req({
- method: 'POST',
- endpoint: '/users/set_role',
- body: JSON.stringify({ username, role }),
- })
- .then(() => {
- cache.patchOk(
- data.UsersResource,
- null,
- R.map((u) => (u.username === username ? { ...u, role } : u)),
- )
- })
- .catch((e) => {
- push(`Error changing role for "${username}"`)
- // eslint-disable-next-line no-console
- console.error('Error chaging role', { username, role })
- // eslint-disable-next-line no-console
- console.dir(e)
- throw e
- }),
- [req, cache, push],
- )
-
- const setIsActive = React.useCallback(
- (username, active) =>
- req({
- method: 'POST',
- endpoint: `/users/${active ? 'enable' : 'disable'}`,
- body: JSON.stringify({ username }),
- })
- .then(() => {
- cache.patchOk(
- data.UsersResource,
- null,
- R.map((u) => (u.username === username ? { ...u, isActive: active } : u)),
- )
- })
- .catch((e) => {
- push(`Error ${active ? 'enabling' : 'disabling'} "${username}"`)
- // eslint-disable-next-line no-console
- console.error('Error (de)activating user', { username, active })
- // eslint-disable-next-line no-console
- console.dir(e)
- throw e
- }),
- [req, cache, push],
- )
-
- const columns = React.useMemo(
- () => [
- {
- id: 'isActive',
- label: 'Enabled',
- getValue: R.prop('isActive'),
- getDisplay: (v, u) => (
- setIsActive(u.username, active)}>
- {({ change, busy, value }) => (
- change(e.target.checked)}
- disabled={busy}
- color="default"
- />
- )}
-
- ),
- },
- {
- id: 'username',
- label: 'Username',
- getValue: R.prop('username'),
- getDisplay: (v, u) => {v},
- props: { component: 'th', scope: 'row' },
- },
- {
- id: 'email',
- label: 'Email',
- getValue: R.prop('email'),
- },
- {
- id: 'role',
- label: 'Role',
- getValue: (u) => u.roleId && (roles.find((r) => r.id === u.roleId) || {}).name,
- getDisplay: (v, u) => (
- setRole(u.username, role)}>
- {({ change, busy, value }) => (
- change(e.target.value)}
- disabled={busy}
- renderValue={R.identity}
- >
- {roles.map((r) => (
-
- {r.name}
-
- ))}
-
- )}
-
- ),
- },
- {
- id: 'dateJoined',
- label: 'Date joined',
- getValue: R.prop('dateJoined'),
- getDisplay: (v) => (
-
-
-
- ),
- },
- {
- id: 'lastLogin',
- label: 'Last login',
- getValue: R.prop('lastLogin'),
- getDisplay: (v) => (
-
-
-
- ),
- },
- {
- id: 'isAdmin',
- label: 'Admin',
- hint: 'Admins can see this page, add/remove users, and make/remove admins',
- getValue: R.prop('isAdmin'),
- getDisplay: (v, u) => (
- {
- const res = await openDialog(({ close }) => (
-
- ))
- if (res !== 'ok') throw new Error('cancelled')
- }}
- >
- {({ change, busy, value }) => (
- change(e.target.checked)}
- disabled={busy}
- color="default"
- />
- )}
-
- ),
- },
- ],
- [roles, openDialog, setIsActive, setRole],
- )
-
- const filtering = Table.useFiltering({
- rows,
- filterBy: ({ email, username }) => email + username,
- })
- const ordering = Table.useOrdering({
- rows: filtering.filtered,
- column: columns[0],
- })
- const pagination = Pagination.use(ordering.ordered, {
- getItemId: R.prop('username'),
- })
-
- const toolbarActions = [
- {
- title: 'Invite',
- icon: add,
- fn: React.useCallback(() => {
- openDialog(({ close }) => )
- }, [roles, defaultRoleId, openDialog]),
- },
- ]
-
- const inlineActions = (user) => [
- {
- title: 'Delete',
- icon: delete,
- fn: () => {
- dialogs.open(({ close }) => )
- },
- },
- {
- title: 'Edit',
- icon: edit,
- fn: () => {
- dialogs.open(({ close }) => )
- },
- },
- ]
-
- return (
- }>
-
- {dialogs.render({ maxWidth: 'xs', fullWidth: true })}
-
-
-
-
-
-
-
- {pagination.paginated.map((i) => (
-
- {columns.map((col) => (
-
- {(col.getDisplay || R.identity)(col.getValue(i), i)}
-
- ))}
-
-
-
-
- ))}
-
-
-
-
-
-
- )
-}
diff --git a/catalog/app/containers/Admin/Users/Users.tsx b/catalog/app/containers/Admin/Users/Users.tsx
new file mode 100644
index 00000000000..7767ca564cb
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/Users.tsx
@@ -0,0 +1,851 @@
+import cx from 'classnames'
+import * as FF from 'final-form'
+import * as R from 'ramda'
+import * as React from 'react'
+import * as redux from 'react-redux'
+import * as RF from 'react-final-form'
+import * as M from '@material-ui/core'
+
+import * as Pagination from 'components/Pagination'
+import * as Notifications from 'containers/Notifications'
+import * as Auth from 'containers/Auth'
+import * as Dialogs from 'utils/GlobalDialogs'
+import * as GQL from 'utils/GraphQL'
+import assertNever from 'utils/assertNever'
+import * as Format from 'utils/format'
+import * as validators from 'utils/validators'
+
+import * as Form from '../Form'
+import * as Table from '../Table'
+
+import USERS_QUERY from './gql/Users.generated'
+import USER_CREATE_MUTATION from './gql/UserCreate.generated'
+import USER_DELETE_MUTATION from './gql/UserDelete.generated'
+import USER_SET_EMAIL_MUTATION from './gql/UserSetEmail.generated'
+import USER_SET_ROLE_MUTATION from './gql/UserSetRole.generated'
+import USER_SET_ACTIVE_MUTATION from './gql/UserSetActive.generated'
+import USER_SET_ADMIN_MUTATION from './gql/UserSetAdmin.generated'
+
+import { UserSelectionFragment as User } from './gql/UserSelection.generated'
+
+type Role = GQL.DataForDoc['roles'][number]
+
+const DIALOG_PROPS: Dialogs.ExtraDialogProps = { maxWidth: 'xs', fullWidth: true }
+
+const useMonoStyles = M.makeStyles((t) => ({
+ root: {
+ fontFamily: t.typography.monospace.fontFamily,
+ },
+}))
+
+interface MonoProps {
+ className?: string
+ children: React.ReactNode
+}
+
+function Mono({ className, children }: MonoProps) {
+ const classes = useMonoStyles()
+ return {children}
+}
+
+const useInviteStyles = M.makeStyles({
+ infoIcon: {
+ fontSize: '1.25em',
+ verticalAlign: '-3px',
+ },
+})
+
+interface InviteProps {
+ close: () => void
+ roles: readonly Role[]
+ defaultRoleName: string | undefined
+}
+
+function Invite({ close, roles, defaultRoleName }: InviteProps) {
+ const classes = useInviteStyles()
+ const create = GQL.useMutation(USER_CREATE_MUTATION)
+ const { push } = Notifications.use()
+
+ const onSubmit = React.useCallback(
+ async ({ username, email, roleName }) => {
+ try {
+ // XXX: use formspec to convert/validate form values into gql input?
+ const input = {
+ name: username,
+ email,
+ roleName,
+ }
+ const data = await create({ input })
+ const r = data.admin.user.create
+ switch (r.__typename) {
+ case 'User':
+ close()
+ push('User invited')
+ return
+ case 'OperationError':
+ switch (r.name) {
+ case 'SubscriptionInvalid':
+ return { [FF.FORM_ERROR]: 'subscriptionInvalid' }
+ case 'MailSendError':
+ return { [FF.FORM_ERROR]: 'smtp' }
+ case 'Conflict':
+ if (r.message.match(/Email already taken/)) {
+ return { email: 'taken' }
+ }
+ if (r.message.match(/Username already taken/)) {
+ return { username: 'taken' }
+ }
+ }
+ throw new Error(`Unexpected operation error: [${r.name}] ${r.message}`)
+ case 'InvalidInput':
+ const errors: Record = {}
+ r.errors.forEach((e) => {
+ switch (e.path) {
+ case 'input.name':
+ // e.name should be one of:
+ // - InvalidUserNameError
+ // - ReservedUserNameError
+ errors.username = 'invalid'
+ break
+ case 'input.email':
+ errors.email = 'invalid'
+ break
+ default:
+ throw new Error(
+ `Unexpected input error at '${e.path}': [${e.name}] ${e.message}`,
+ )
+ }
+ })
+ return errors
+ default:
+ return assertNever(r)
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Error creating user')
+ // eslint-disable-next-line no-console
+ console.dir(e)
+ // TODO: send to sentry?
+ return { [FF.FORM_ERROR]: 'unexpected' }
+ }
+ },
+ [create, push, close],
+ )
+
+ return (
+
+ {({
+ handleSubmit,
+ submitting,
+ submitError,
+ submitFailed,
+ error,
+ hasSubmitErrors,
+ hasValidationErrors,
+ modifiedSinceLastSubmit,
+ }) => (
+ <>
+ Invite a user
+
+
+
+
+
+ Cancel
+
+
+ Invite
+
+
+ >
+ )}
+
+ )
+}
+
+interface EditProps {
+ close: () => void
+ user: User
+}
+
+function Edit({ close, user: { email: oldEmail, name } }: EditProps) {
+ const { push } = Notifications.use()
+ const setEmail = GQL.useMutation(USER_SET_EMAIL_MUTATION)
+
+ const onSubmit = React.useCallback(
+ async ({ email }) => {
+ if (email === oldEmail) {
+ close()
+ return
+ }
+
+ try {
+ const data = await setEmail({ name, email })
+ const r = data.admin.user.mutate?.setEmail
+ if (!r) {
+ throw new Error()
+ }
+ // TODO: switch result
+ switch (r.__typename) {
+ // if (APIConnector.HTTPError.is(e, 400, /Another user already has that email/)) {
+ // return {
+ // email: 'taken',
+ // }
+ // }
+ // if (APIConnector.HTTPError.is(e, 400, /Invalid email/)) {
+ // return {
+ // email: 'invalid',
+ // }
+ // }
+ default:
+ }
+
+ close()
+ push('Changes saved')
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Error changing email')
+ // eslint-disable-next-line no-console
+ console.dir(e)
+ return {
+ [FF.FORM_ERROR]: 'unexpected',
+ }
+ }
+ },
+ [close, name, oldEmail, setEmail, push],
+ )
+
+ return (
+
+ {({
+ handleSubmit,
+ submitting,
+ submitFailed,
+ error,
+ hasSubmitErrors,
+ hasValidationErrors,
+ modifiedSinceLastSubmit,
+ }) => (
+ <>
+ Edit user: "{name}"
+
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+ >
+ )}
+
+ )
+}
+
+function ActionProgress({ children }: React.PropsWithChildren<{}>) {
+ return (
+
+ {children}
+
+ )
+}
+
+interface DeleteProps {
+ close: () => void
+ name: string
+}
+
+function Delete({ name, close }: DeleteProps) {
+ const { push } = Notifications.use()
+ const del = GQL.useMutation(USER_DELETE_MUTATION)
+ const onSubmit = React.useCallback(async () => {
+ try {
+ const data = await del({ name })
+ const r = data.admin.user.mutate?.delete
+ if (!r) return { [FF.FORM_ERROR]: 'notFound' }
+ switch (r.__typename) {
+ case 'Ok':
+ close()
+ push(`User "${name}" deleted`)
+ return
+ case 'InvalidInput':
+ const [e] = r.errors
+ throw new Error(
+ `Unexpected input error at '${e.path}': [${e.name}] ${e.message}`,
+ )
+ case 'OperationError':
+ if (r.name === 'DeleteSelf') {
+ return { [FF.FORM_ERROR]: 'deleteSelf' }
+ }
+ throw new Error(`Unexpected operation error: [${r.name}] ${r.message}`)
+ default:
+ assertNever(r)
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Error deleting user')
+ // eslint-disable-next-line no-console
+ console.dir(e)
+ // TODO: send to sentry?
+ return { [FF.FORM_ERROR]: 'unexpected' }
+ }
+ }, [del, name, close, push])
+
+ return (
+
+ {({ handleSubmit, submitting, submitError }) => (
+ <>
+ Delete a user
+
+ You are about to delete user "{name}".
+
+ This operation is irreversible.
+
+ {!!submitError && (
+
+ )}
+
+
+ {submitting && Deleting...}
+
+ Cancel
+
+
+ Delete
+
+
+ >
+ )}
+
+ )
+}
+
+interface ConfirmAdminRightsProps {
+ admin: boolean
+ name: string
+ close: Dialogs.Close
+}
+
+function ConfirmAdminRights({ name, admin, close }: ConfirmAdminRightsProps) {
+ const { push } = Notifications.use()
+ const setAdmin = GQL.useMutation(USER_SET_ADMIN_MUTATION)
+
+ const doChange = React.useCallback(
+ () =>
+ close(
+ setAdmin({ name, admin })
+ .then((data) => {
+ const r = data.admin.user.mutate?.setAdmin
+ switch (r?.__typename) {
+ case 'User':
+ return true
+ case undefined: // should not happen
+ throw new Error('User not found')
+ case 'InvalidInput': // should not happen
+ const [e] = r.errors
+ throw new Error(
+ `Unexpected input error at '${e.path}': [${e.name}] ${e.message}`,
+ )
+ case 'OperationError': // should not happen
+ throw new Error(`Unexpected operation error: [${r.name}] ${r.message}`)
+ default:
+ assertNever(r)
+ }
+ })
+ .catch((e) => {
+ push(`Could not change admin status for "${name}"`)
+ // eslint-disable-next-line no-console
+ console.error('Could not change user admin status', { name, admin })
+ // eslint-disable-next-line no-console
+ console.dir(e)
+ throw e // revert value change in
+ }),
+ ),
+ [admin, close, name, setAdmin, push],
+ )
+
+ return (
+ <>
+ {admin ? 'Grant' : 'Revoke'} admin rights
+
+ You are about to {admin ? 'grant admin rights to' : 'revoke admin rights from'}{' '}
+ "{name}".
+
+
+ close(false)} color="primary">
+ Cancel
+
+
+ {admin ? 'Grant' : 'Revoke'}
+
+
+ >
+ )
+}
+
+const useUsernameStyles = M.makeStyles((t) => ({
+ root: {
+ alignItems: 'center',
+ display: 'flex',
+ },
+ admin: {
+ fontWeight: 600,
+ },
+ icon: {
+ fontSize: '1em',
+ marginLeft: `calc(-1em - ${t.spacing(0.5)}px)`,
+ marginRight: t.spacing(0.5),
+ },
+}))
+
+interface UsernameProps extends React.HTMLProps {
+ admin?: boolean
+}
+
+function Username({ className, admin = false, children, ...props }: UsernameProps) {
+ const classes = useUsernameStyles()
+ return (
+
+ {admin && security}
+ {children}
+
+ )
+}
+
+interface EditableRenderProps {
+ change: (v: T) => void
+ busy: boolean
+ value: T
+}
+
+interface EditableProps {
+ value: T
+ onChange: (v: T) => void
+ children: (props: EditableRenderProps) => JSX.Element
+}
+
+function Editable({ value, onChange, children }: EditableProps) {
+ const [busy, setBusy] = React.useState(false)
+ const [savedValue, saveValue] = React.useState(value)
+ const change = React.useCallback(
+ (newValue: T) => {
+ if (savedValue === newValue) return
+ if (busy) return
+ setBusy(true)
+ saveValue(newValue)
+ Promise.resolve(onChange(newValue))
+ .catch(() => {
+ saveValue(savedValue)
+ })
+ .finally(() => {
+ setBusy(false)
+ })
+ },
+ [onChange, busy, setBusy, savedValue, saveValue],
+ )
+
+ return children({ change, busy, value: savedValue })
+}
+
+function UsersSkeleton() {
+ return (
+
+
+
+
+ )
+}
+
+// not a valid role name
+const emptyRole = ''
+
+interface ColumnDisplayProps {
+ roles: readonly Role[]
+ setActive: (name: string, active: boolean) => Promise
+ setRole: (name: string, roleName: string) => Promise
+ openDialog: Dialogs.Open
+ isSelf: boolean
+}
+
+const columns: Table.Column[] = [
+ {
+ id: 'isActive',
+ label: 'Enabled',
+ getValue: (u) => u.isActive,
+ getDisplay: (v: boolean, u, { setActive, isSelf }: ColumnDisplayProps) =>
+ isSelf ? (
+
+ ) : (
+ setActive(u.name, active)}>
+ {({ change, busy, value }) => (
+ change(e.target.checked)}
+ disabled={busy}
+ color="default"
+ />
+ )}
+
+ ),
+ },
+ {
+ id: 'username',
+ label: 'Username',
+ getValue: (u) => u.name,
+ getDisplay: (_name: string, u) => {u.name},
+ props: { component: 'th', scope: 'row' },
+ },
+ {
+ id: 'email',
+ label: 'Email',
+ getValue: (u) => u.email,
+ },
+ {
+ id: 'role',
+ label: 'Role',
+ getValue: (u) => u.role?.name,
+ getDisplay: (v: string | undefined, u, { roles, setRole }: ColumnDisplayProps) => (
+ setRole(u.name, role)}>
+ {({ change, busy, value }) => (
+ change(e.target.value as string)}
+ disabled={busy}
+ renderValue={R.identity}
+ >
+ {roles.map((r) => (
+
+ {r.name}
+
+ ))}
+
+ )}
+
+ ),
+ },
+ {
+ id: 'dateJoined',
+ label: 'Date joined',
+ getValue: (u) => u.dateJoined,
+ getDisplay: (v: Date) => (
+
+
+
+ ),
+ },
+ {
+ id: 'lastLogin',
+ label: 'Last login',
+ getValue: (u) => u.lastLogin,
+ getDisplay: (v: Date) => (
+
+
+
+ ),
+ },
+ {
+ id: 'isAdmin',
+ label: 'Admin',
+ hint: 'Admins can see this page, add/remove users, and make/remove admins',
+ getValue: (u) => u.isAdmin,
+ getDisplay: (v: boolean, { name }, { openDialog, isSelf }: ColumnDisplayProps) =>
+ isSelf ? (
+
+ ) : (
+
+ openDialog(
+ ({ close }) => ,
+ DIALOG_PROPS,
+ ).then((res) => {
+ if (!res) throw new Error('cancel')
+ })
+ }
+ >
+ {({ change, busy, value }) => (
+ change(e.target.checked)}
+ disabled={busy}
+ color="default"
+ />
+ )}
+
+ ),
+ },
+]
+
+function useSetActive() {
+ const { push } = Notifications.use()
+ const setActive = GQL.useMutation(USER_SET_ACTIVE_MUTATION)
+
+ return React.useCallback(
+ async (name: string, active: boolean) => {
+ try {
+ const resp = await setActive({ name, active })
+ // TODO: switch
+ switch (resp) {
+ }
+ } catch (e) {
+ push(`Error ${active ? 'enabling' : 'disabling'} "${name}"`)
+ // eslint-disable-next-line no-console
+ console.error('Error (de)activating user', { name, active })
+ // eslint-disable-next-line no-console
+ console.dir(e)
+ // TODO
+ // throw e
+ }
+ },
+ [setActive, push],
+ )
+}
+
+function useSetRole() {
+ const { push } = Notifications.use()
+ const setRole = GQL.useMutation(USER_SET_ROLE_MUTATION)
+
+ return React.useCallback(
+ async (name: string, roleName: string) => {
+ try {
+ const resp = await setRole({ name, roleName })
+ const r = resp.admin.user.mutate?.setRole
+ // TODO: switch result
+ switch (r) {
+ }
+ } catch (e) {
+ push(`Error changing role for "${name}"`)
+ // eslint-disable-next-line no-console
+ console.error('Error chaging role', { name, roleName })
+ // eslint-disable-next-line no-console
+ console.dir(e)
+ throw e
+ }
+ },
+ [setRole, push],
+ )
+}
+
+export default function Users() {
+ const data = GQL.useQueryS(USERS_QUERY)
+ const rows = data.admin.user.list
+ const roles = data.roles
+ const defaultRoleName = data.defaultRole?.name
+
+ const openDialog = Dialogs.use()
+
+ const setActive = useSetActive()
+ const setRole = useSetRole()
+
+ const filtering = Table.useFiltering({
+ rows,
+ filterBy: ({ email, name }) => email + name,
+ })
+ const ordering = Table.useOrdering({
+ rows: filtering.filtered,
+ column: columns[0],
+ })
+ const pagination = Pagination.use(ordering.ordered, {
+ getItemId: (u: User) => u.name,
+ } as $TSFixMe)
+
+ const toolbarActions = [
+ {
+ title: 'Invite',
+ icon: add,
+ fn: React.useCallback(() => {
+ openDialog(
+ ({ close }) => ,
+ DIALOG_PROPS,
+ )
+ }, [roles, defaultRoleName, openDialog]),
+ },
+ ]
+
+ const self: string = redux.useSelector(Auth.selectors.username)
+
+ const inlineActions = (user: User) => [
+ user.name === self
+ ? null
+ : {
+ title: 'Delete',
+ icon: delete,
+ fn: () => {
+ openDialog(
+ ({ close }) => ,
+ DIALOG_PROPS,
+ )
+ },
+ },
+ {
+ title: 'Edit',
+ icon: edit,
+ fn: () => {
+ openDialog(({ close }) => , DIALOG_PROPS)
+ },
+ },
+ ]
+
+ const getDisplayProps = (u: User): ColumnDisplayProps => ({
+ setRole,
+ setActive,
+ roles,
+ openDialog,
+ isSelf: u.name === self,
+ })
+
+ return (
+ }>
+
+
+
+
+
+
+
+
+ {pagination.paginated.map((i: User) => (
+
+ {columns.map((col) => (
+
+ {(col.getDisplay || R.identity)(
+ col.getValue(i),
+ i,
+ getDisplayProps(i),
+ )}
+
+ ))}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ )
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserCreate.generated.ts b/catalog/app/containers/Admin/Users/gql/UserCreate.generated.ts
new file mode 100644
index 00000000000..ca678da00a1
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserCreate.generated.ts
@@ -0,0 +1,104 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+import {
+ UserResultSelection_User_Fragment,
+ UserResultSelection_InvalidInput_Fragment,
+ UserResultSelection_OperationError_Fragment,
+ UserResultSelectionFragmentDoc,
+} from './UserResultSelection.generated'
+
+export type containers_Admin_Users_gql_UserCreateMutationVariables = Types.Exact<{
+ input: Types.UserInput
+}>
+
+export type containers_Admin_Users_gql_UserCreateMutation = {
+ readonly __typename: 'Mutation'
+} & {
+ readonly admin: { readonly __typename: 'AdminMutations' } & {
+ readonly user: { readonly __typename: 'UserAdminMutations' } & {
+ readonly create:
+ | ({ readonly __typename: 'User' } & UserResultSelection_User_Fragment)
+ | ({
+ readonly __typename: 'InvalidInput'
+ } & UserResultSelection_InvalidInput_Fragment)
+ | ({
+ readonly __typename: 'OperationError'
+ } & UserResultSelection_OperationError_Fragment)
+ }
+ }
+}
+
+export const containers_Admin_Users_gql_UserCreateDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'mutation',
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_UserCreate' },
+ variableDefinitions: [
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'UserInput' } },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'create' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'input' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'input' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserResultSelection' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ...UserResultSelectionFragmentDoc.definitions,
+ ],
+} as unknown as DocumentNode<
+ containers_Admin_Users_gql_UserCreateMutation,
+ containers_Admin_Users_gql_UserCreateMutationVariables
+>
+
+export { containers_Admin_Users_gql_UserCreateDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/UserCreate.graphql b/catalog/app/containers/Admin/Users/gql/UserCreate.graphql
new file mode 100644
index 00000000000..287d9b2f0f6
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserCreate.graphql
@@ -0,0 +1,11 @@
+# import UserResultSelection from "./UserResultSelection.graphql"
+
+mutation ($input: UserInput!) {
+ admin {
+ user {
+ create(input: $input) {
+ ...UserResultSelection
+ }
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserDelete.generated.ts b/catalog/app/containers/Admin/Users/gql/UserDelete.generated.ts
new file mode 100644
index 00000000000..7b8a68057d2
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserDelete.generated.ts
@@ -0,0 +1,176 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+export type containers_Admin_Users_gql_UserDeleteMutationVariables = Types.Exact<{
+ name: Types.Scalars['String']
+}>
+
+export type containers_Admin_Users_gql_UserDeleteMutation = {
+ readonly __typename: 'Mutation'
+} & {
+ readonly admin: { readonly __typename: 'AdminMutations' } & {
+ readonly user: { readonly __typename: 'UserAdminMutations' } & {
+ readonly mutate: Types.Maybe<
+ { readonly __typename: 'MutateUserAdminMutations' } & {
+ readonly delete:
+ | { readonly __typename: 'Ok' }
+ | ({ readonly __typename: 'InvalidInput' } & {
+ readonly errors: ReadonlyArray<
+ { readonly __typename: 'InputError' } & Pick<
+ Types.InputError,
+ 'path' | 'message' | 'name' | 'context'
+ >
+ >
+ })
+ | ({ readonly __typename: 'OperationError' } & Pick<
+ Types.OperationError,
+ 'message' | 'name' | 'context'
+ >)
+ }
+ >
+ }
+ }
+}
+
+export const containers_Admin_Users_gql_UserDeleteDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'mutation',
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_UserDelete' },
+ variableDefinitions: [
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'name' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'mutate' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'name' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'name' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'delete' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: '__typename' },
+ },
+ {
+ kind: 'InlineFragment',
+ typeCondition: {
+ kind: 'NamedType',
+ name: { kind: 'Name', value: 'InvalidInput' },
+ },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'errors' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'path' },
+ },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'message' },
+ },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'name' },
+ },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'context' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ kind: 'InlineFragment',
+ typeCondition: {
+ kind: 'NamedType',
+ name: { kind: 'Name', value: 'OperationError' },
+ },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'message' },
+ },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'name' },
+ },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'context' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+} as unknown as DocumentNode<
+ containers_Admin_Users_gql_UserDeleteMutation,
+ containers_Admin_Users_gql_UserDeleteMutationVariables
+>
+
+export { containers_Admin_Users_gql_UserDeleteDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/UserDelete.graphql b/catalog/app/containers/Admin/Users/gql/UserDelete.graphql
new file mode 100644
index 00000000000..78d453b9b9f
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserDelete.graphql
@@ -0,0 +1,24 @@
+mutation ($name: String!) {
+ admin {
+ user {
+ mutate(name: $name) {
+ delete {
+ __typename
+ ... on InvalidInput {
+ errors {
+ path
+ message
+ name
+ context
+ }
+ }
+ ... on OperationError {
+ message
+ name
+ context
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserResultSelection.generated.ts b/catalog/app/containers/Admin/Users/gql/UserResultSelection.generated.ts
new file mode 100644
index 00000000000..1833ee2e578
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserResultSelection.generated.ts
@@ -0,0 +1,103 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+import {
+ UserSelectionFragment,
+ UserSelectionFragmentDoc,
+} from './UserSelection.generated'
+
+export type UserResultSelection_User_Fragment = {
+ readonly __typename: 'User'
+} & UserSelectionFragment
+
+export type UserResultSelection_InvalidInput_Fragment = {
+ readonly __typename: 'InvalidInput'
+} & {
+ readonly errors: ReadonlyArray<
+ { readonly __typename: 'InputError' } & Pick<
+ Types.InputError,
+ 'path' | 'message' | 'name' | 'context'
+ >
+ >
+}
+
+export type UserResultSelection_OperationError_Fragment = {
+ readonly __typename: 'OperationError'
+} & Pick
+
+export type UserResultSelectionFragment =
+ | UserResultSelection_User_Fragment
+ | UserResultSelection_InvalidInput_Fragment
+ | UserResultSelection_OperationError_Fragment
+
+export const UserResultSelectionFragmentDoc = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'FragmentDefinition',
+ name: { kind: 'Name', value: 'UserResultSelection' },
+ typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'UserResult' } },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: '__typename' } },
+ {
+ kind: 'InlineFragment',
+ typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'User' } },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserSelection' },
+ },
+ ],
+ },
+ },
+ {
+ kind: 'InlineFragment',
+ typeCondition: {
+ kind: 'NamedType',
+ name: { kind: 'Name', value: 'InvalidInput' },
+ },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'errors' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'path' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'message' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'context' } },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ kind: 'InlineFragment',
+ typeCondition: {
+ kind: 'NamedType',
+ name: { kind: 'Name', value: 'OperationError' },
+ },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'message' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'context' } },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ...UserSelectionFragmentDoc.definitions,
+ ],
+} as unknown as DocumentNode
diff --git a/catalog/app/containers/Admin/Users/gql/UserResultSelection.graphql b/catalog/app/containers/Admin/Users/gql/UserResultSelection.graphql
new file mode 100644
index 00000000000..86ae6e80d97
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserResultSelection.graphql
@@ -0,0 +1,19 @@
+# import UserSelection from "./UserSelection.graphql"
+
+fragment UserResultSelection on UserResult {
+ __typename
+ ... on User { ...UserSelection }
+ ... on InvalidInput {
+ errors {
+ path
+ message
+ name
+ context
+ }
+ }
+ ... on OperationError {
+ message
+ name
+ context
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserSelection.generated.ts b/catalog/app/containers/Admin/Users/gql/UserSelection.generated.ts
new file mode 100644
index 00000000000..0368d91f12b
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSelection.generated.ts
@@ -0,0 +1,86 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+export type UserSelectionFragment = { readonly __typename: 'User' } & Pick<
+ Types.User,
+ | 'name'
+ | 'email'
+ | 'dateJoined'
+ | 'lastLogin'
+ | 'isActive'
+ | 'isAdmin'
+ | 'isSsoOnly'
+ | 'isService'
+> & {
+ readonly role: Types.Maybe<
+ | ({ readonly __typename: 'UnmanagedRole' } & Pick<
+ Types.UnmanagedRole,
+ 'id' | 'name'
+ >)
+ | ({ readonly __typename: 'ManagedRole' } & Pick)
+ >
+ }
+
+export const UserSelectionFragmentDoc = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'FragmentDefinition',
+ name: { kind: 'Name', value: 'UserSelection' },
+ typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'User' } },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: '__typename' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'email' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'dateJoined' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'lastLogin' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'isActive' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'isAdmin' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'isSsoOnly' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'isService' } },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'role' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: '__typename' } },
+ {
+ kind: 'InlineFragment',
+ typeCondition: {
+ kind: 'NamedType',
+ name: { kind: 'Name', value: 'ManagedRole' },
+ },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ ],
+ },
+ },
+ {
+ kind: 'InlineFragment',
+ typeCondition: {
+ kind: 'NamedType',
+ name: { kind: 'Name', value: 'UnmanagedRole' },
+ },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+} as unknown as DocumentNode
diff --git a/catalog/app/containers/Admin/Users/gql/UserSelection.graphql b/catalog/app/containers/Admin/Users/gql/UserSelection.graphql
new file mode 100644
index 00000000000..349a0304e63
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSelection.graphql
@@ -0,0 +1,22 @@
+fragment UserSelection on User {
+ __typename
+ name
+ email
+ dateJoined
+ lastLogin
+ isActive
+ isAdmin
+ isSsoOnly
+ isService
+ role {
+ __typename
+ ... on ManagedRole {
+ id
+ name
+ }
+ ... on UnmanagedRole {
+ id
+ name
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetActive.generated.ts b/catalog/app/containers/Admin/Users/gql/UserSetActive.generated.ts
new file mode 100644
index 00000000000..343a4cff8fd
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetActive.generated.ts
@@ -0,0 +1,136 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+import {
+ UserResultSelection_User_Fragment,
+ UserResultSelection_InvalidInput_Fragment,
+ UserResultSelection_OperationError_Fragment,
+ UserResultSelectionFragmentDoc,
+} from './UserResultSelection.generated'
+
+export type containers_Admin_Users_gql_UserSetActiveMutationVariables = Types.Exact<{
+ name: Types.Scalars['String']
+ active: Types.Scalars['Boolean']
+}>
+
+export type containers_Admin_Users_gql_UserSetActiveMutation = {
+ readonly __typename: 'Mutation'
+} & {
+ readonly admin: { readonly __typename: 'AdminMutations' } & {
+ readonly user: { readonly __typename: 'UserAdminMutations' } & {
+ readonly mutate: Types.Maybe<
+ { readonly __typename: 'MutateUserAdminMutations' } & {
+ readonly setActive:
+ | ({ readonly __typename: 'User' } & UserResultSelection_User_Fragment)
+ | ({
+ readonly __typename: 'InvalidInput'
+ } & UserResultSelection_InvalidInput_Fragment)
+ | ({
+ readonly __typename: 'OperationError'
+ } & UserResultSelection_OperationError_Fragment)
+ }
+ >
+ }
+ }
+}
+
+export const containers_Admin_Users_gql_UserSetActiveDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'mutation',
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_UserSetActive' },
+ variableDefinitions: [
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'name' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'active' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'Boolean' } },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'mutate' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'name' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'name' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'setActive' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'active' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'active' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserResultSelection' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ...UserResultSelectionFragmentDoc.definitions,
+ ],
+} as unknown as DocumentNode<
+ containers_Admin_Users_gql_UserSetActiveMutation,
+ containers_Admin_Users_gql_UserSetActiveMutationVariables
+>
+
+export { containers_Admin_Users_gql_UserSetActiveDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetActive.graphql b/catalog/app/containers/Admin/Users/gql/UserSetActive.graphql
new file mode 100644
index 00000000000..0c1089ea8a6
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetActive.graphql
@@ -0,0 +1,13 @@
+# import UserResultSelection from "./UserResultSelection.graphql"
+
+mutation ($name: String!, $active: Boolean!) {
+ admin {
+ user {
+ mutate(name: $name) {
+ setActive(active: $active) {
+ ...UserResultSelection
+ }
+ }
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetAdmin.generated.ts b/catalog/app/containers/Admin/Users/gql/UserSetAdmin.generated.ts
new file mode 100644
index 00000000000..87c7a5c85ac
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetAdmin.generated.ts
@@ -0,0 +1,136 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+import {
+ UserResultSelection_User_Fragment,
+ UserResultSelection_InvalidInput_Fragment,
+ UserResultSelection_OperationError_Fragment,
+ UserResultSelectionFragmentDoc,
+} from './UserResultSelection.generated'
+
+export type containers_Admin_Users_gql_UserSetAdminMutationVariables = Types.Exact<{
+ name: Types.Scalars['String']
+ admin: Types.Scalars['Boolean']
+}>
+
+export type containers_Admin_Users_gql_UserSetAdminMutation = {
+ readonly __typename: 'Mutation'
+} & {
+ readonly admin: { readonly __typename: 'AdminMutations' } & {
+ readonly user: { readonly __typename: 'UserAdminMutations' } & {
+ readonly mutate: Types.Maybe<
+ { readonly __typename: 'MutateUserAdminMutations' } & {
+ readonly setAdmin:
+ | ({ readonly __typename: 'User' } & UserResultSelection_User_Fragment)
+ | ({
+ readonly __typename: 'InvalidInput'
+ } & UserResultSelection_InvalidInput_Fragment)
+ | ({
+ readonly __typename: 'OperationError'
+ } & UserResultSelection_OperationError_Fragment)
+ }
+ >
+ }
+ }
+}
+
+export const containers_Admin_Users_gql_UserSetAdminDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'mutation',
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_UserSetAdmin' },
+ variableDefinitions: [
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'name' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'admin' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'Boolean' } },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'mutate' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'name' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'name' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'setAdmin' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'admin' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'admin' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserResultSelection' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ...UserResultSelectionFragmentDoc.definitions,
+ ],
+} as unknown as DocumentNode<
+ containers_Admin_Users_gql_UserSetAdminMutation,
+ containers_Admin_Users_gql_UserSetAdminMutationVariables
+>
+
+export { containers_Admin_Users_gql_UserSetAdminDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetAdmin.graphql b/catalog/app/containers/Admin/Users/gql/UserSetAdmin.graphql
new file mode 100644
index 00000000000..5f400011f04
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetAdmin.graphql
@@ -0,0 +1,13 @@
+# import UserResultSelection from "./UserResultSelection.graphql"
+
+mutation ($name: String!, $admin: Boolean!) {
+ admin {
+ user {
+ mutate(name: $name) {
+ setAdmin(admin: $admin) {
+ ...UserResultSelection
+ }
+ }
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetEmail.generated.ts b/catalog/app/containers/Admin/Users/gql/UserSetEmail.generated.ts
new file mode 100644
index 00000000000..8776fb06f11
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetEmail.generated.ts
@@ -0,0 +1,136 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+import {
+ UserResultSelection_User_Fragment,
+ UserResultSelection_InvalidInput_Fragment,
+ UserResultSelection_OperationError_Fragment,
+ UserResultSelectionFragmentDoc,
+} from './UserResultSelection.generated'
+
+export type containers_Admin_Users_gql_UserSetEmailMutationVariables = Types.Exact<{
+ name: Types.Scalars['String']
+ email: Types.Scalars['String']
+}>
+
+export type containers_Admin_Users_gql_UserSetEmailMutation = {
+ readonly __typename: 'Mutation'
+} & {
+ readonly admin: { readonly __typename: 'AdminMutations' } & {
+ readonly user: { readonly __typename: 'UserAdminMutations' } & {
+ readonly mutate: Types.Maybe<
+ { readonly __typename: 'MutateUserAdminMutations' } & {
+ readonly setEmail:
+ | ({ readonly __typename: 'User' } & UserResultSelection_User_Fragment)
+ | ({
+ readonly __typename: 'InvalidInput'
+ } & UserResultSelection_InvalidInput_Fragment)
+ | ({
+ readonly __typename: 'OperationError'
+ } & UserResultSelection_OperationError_Fragment)
+ }
+ >
+ }
+ }
+}
+
+export const containers_Admin_Users_gql_UserSetEmailDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'mutation',
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_UserSetEmail' },
+ variableDefinitions: [
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'name' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'email' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'mutate' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'name' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'name' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'setEmail' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'email' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'email' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserResultSelection' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ...UserResultSelectionFragmentDoc.definitions,
+ ],
+} as unknown as DocumentNode<
+ containers_Admin_Users_gql_UserSetEmailMutation,
+ containers_Admin_Users_gql_UserSetEmailMutationVariables
+>
+
+export { containers_Admin_Users_gql_UserSetEmailDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetEmail.graphql b/catalog/app/containers/Admin/Users/gql/UserSetEmail.graphql
new file mode 100644
index 00000000000..f64a6a3ffdd
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetEmail.graphql
@@ -0,0 +1,13 @@
+# import UserResultSelection from "./UserResultSelection.graphql"
+
+mutation ($name: String!, $email: String!) {
+ admin {
+ user {
+ mutate(name: $name) {
+ setEmail(email: $email) {
+ ...UserResultSelection
+ }
+ }
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetRole.generated.ts b/catalog/app/containers/Admin/Users/gql/UserSetRole.generated.ts
new file mode 100644
index 00000000000..78b0b118444
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetRole.generated.ts
@@ -0,0 +1,136 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
+import * as Types from '../../../../model/graphql/types.generated'
+
+import {
+ UserResultSelection_User_Fragment,
+ UserResultSelection_InvalidInput_Fragment,
+ UserResultSelection_OperationError_Fragment,
+ UserResultSelectionFragmentDoc,
+} from './UserResultSelection.generated'
+
+export type containers_Admin_Users_gql_UserSetRoleMutationVariables = Types.Exact<{
+ name: Types.Scalars['String']
+ roleName: Types.Scalars['String']
+}>
+
+export type containers_Admin_Users_gql_UserSetRoleMutation = {
+ readonly __typename: 'Mutation'
+} & {
+ readonly admin: { readonly __typename: 'AdminMutations' } & {
+ readonly user: { readonly __typename: 'UserAdminMutations' } & {
+ readonly mutate: Types.Maybe<
+ { readonly __typename: 'MutateUserAdminMutations' } & {
+ readonly setRole:
+ | ({ readonly __typename: 'User' } & UserResultSelection_User_Fragment)
+ | ({
+ readonly __typename: 'InvalidInput'
+ } & UserResultSelection_InvalidInput_Fragment)
+ | ({
+ readonly __typename: 'OperationError'
+ } & UserResultSelection_OperationError_Fragment)
+ }
+ >
+ }
+ }
+}
+
+export const containers_Admin_Users_gql_UserSetRoleDocument = {
+ kind: 'Document',
+ definitions: [
+ {
+ kind: 'OperationDefinition',
+ operation: 'mutation',
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_UserSetRole' },
+ variableDefinitions: [
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'name' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ {
+ kind: 'VariableDefinition',
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'roleName' } },
+ type: {
+ kind: 'NonNullType',
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'mutate' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'name' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'name' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'setRole' },
+ arguments: [
+ {
+ kind: 'Argument',
+ name: { kind: 'Name', value: 'roleName' },
+ value: {
+ kind: 'Variable',
+ name: { kind: 'Name', value: 'roleName' },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserResultSelection' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ...UserResultSelectionFragmentDoc.definitions,
+ ],
+} as unknown as DocumentNode<
+ containers_Admin_Users_gql_UserSetRoleMutation,
+ containers_Admin_Users_gql_UserSetRoleMutationVariables
+>
+
+export { containers_Admin_Users_gql_UserSetRoleDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/UserSetRole.graphql b/catalog/app/containers/Admin/Users/gql/UserSetRole.graphql
new file mode 100644
index 00000000000..e6da78863d6
--- /dev/null
+++ b/catalog/app/containers/Admin/Users/gql/UserSetRole.graphql
@@ -0,0 +1,13 @@
+# import UserResultSelection from "./UserResultSelection.graphql"
+
+mutation ($name: String!, $roleName: String!) {
+ admin {
+ user {
+ mutate(name: $name) {
+ setRole(roleName: $roleName) {
+ ...UserResultSelection
+ }
+ }
+ }
+ }
+}
diff --git a/catalog/app/containers/Admin/Users/gql/Roles.generated.ts b/catalog/app/containers/Admin/Users/gql/Users.generated.ts
similarity index 56%
rename from catalog/app/containers/Admin/Users/gql/Roles.generated.ts
rename to catalog/app/containers/Admin/Users/gql/Users.generated.ts
index 69e8efd8687..80f5a711c7c 100644
--- a/catalog/app/containers/Admin/Users/gql/Roles.generated.ts
+++ b/catalog/app/containers/Admin/Users/gql/Users.generated.ts
@@ -2,11 +2,23 @@
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
import * as Types from '../../../../model/graphql/types.generated'
-export type containers_Admin_Users_gql_RolesQueryVariables = Types.Exact<{
+import {
+ UserSelectionFragment,
+ UserSelectionFragmentDoc,
+} from './UserSelection.generated'
+
+export type containers_Admin_Users_gql_UsersQueryVariables = Types.Exact<{
[key: string]: never
}>
-export type containers_Admin_Users_gql_RolesQuery = { readonly __typename: 'Query' } & {
+export type containers_Admin_Users_gql_UsersQuery = { readonly __typename: 'Query' } & {
+ readonly admin: { readonly __typename: 'AdminQueries' } & {
+ readonly user: { readonly __typename: 'UserAdminQueries' } & {
+ readonly list: ReadonlyArray<
+ { readonly __typename: 'User' } & UserSelectionFragment
+ >
+ }
+ }
readonly roles: ReadonlyArray<
| ({ readonly __typename: 'UnmanagedRole' } & Pick<
Types.UnmanagedRole,
@@ -15,21 +27,55 @@ export type containers_Admin_Users_gql_RolesQuery = { readonly __typename: 'Quer
| ({ readonly __typename: 'ManagedRole' } & Pick)
>
readonly defaultRole: Types.Maybe<
- | ({ readonly __typename: 'UnmanagedRole' } & Pick)
- | ({ readonly __typename: 'ManagedRole' } & Pick)
+ | ({ readonly __typename: 'UnmanagedRole' } & Pick<
+ Types.UnmanagedRole,
+ 'id' | 'name'
+ >)
+ | ({ readonly __typename: 'ManagedRole' } & Pick)
>
}
-export const containers_Admin_Users_gql_RolesDocument = {
+export const containers_Admin_Users_gql_UsersDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
- name: { kind: 'Name', value: 'containers_Admin_Users_gql_Roles' },
+ name: { kind: 'Name', value: 'containers_Admin_Users_gql_Users' },
selectionSet: {
kind: 'SelectionSet',
selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'admin' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'user' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'list' },
+ selectionSet: {
+ kind: 'SelectionSet',
+ selections: [
+ {
+ kind: 'FragmentSpread',
+ name: { kind: 'Name', value: 'UserSelection' },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
{
kind: 'Field',
name: { kind: 'Name', value: 'roles' },
@@ -81,7 +127,10 @@ export const containers_Admin_Users_gql_RolesDocument = {
},
selectionSet: {
kind: 'SelectionSet',
- selections: [{ kind: 'Field', name: { kind: 'Name', value: 'id' } }],
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ ],
},
},
{
@@ -92,7 +141,10 @@ export const containers_Admin_Users_gql_RolesDocument = {
},
selectionSet: {
kind: 'SelectionSet',
- selections: [{ kind: 'Field', name: { kind: 'Name', value: 'id' } }],
+ selections: [
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
+ ],
},
},
],
@@ -101,10 +153,11 @@ export const containers_Admin_Users_gql_RolesDocument = {
],
},
},
+ ...UserSelectionFragmentDoc.definitions,
],
} as unknown as DocumentNode<
- containers_Admin_Users_gql_RolesQuery,
- containers_Admin_Users_gql_RolesQueryVariables
+ containers_Admin_Users_gql_UsersQuery,
+ containers_Admin_Users_gql_UsersQueryVariables
>
-export { containers_Admin_Users_gql_RolesDocument as default }
+export { containers_Admin_Users_gql_UsersDocument as default }
diff --git a/catalog/app/containers/Admin/Users/gql/Roles.graphql b/catalog/app/containers/Admin/Users/gql/Users.graphql
similarity index 59%
rename from catalog/app/containers/Admin/Users/gql/Roles.graphql
rename to catalog/app/containers/Admin/Users/gql/Users.graphql
index 72109c76ff5..1d87f58c480 100644
--- a/catalog/app/containers/Admin/Users/gql/Roles.graphql
+++ b/catalog/app/containers/Admin/Users/gql/Users.graphql
@@ -1,4 +1,13 @@
+# import UserSelection from "./UserSelection.graphql"
+
query {
+ admin {
+ user {
+ list {
+ ...UserSelection
+ }
+ }
+ }
roles {
... on UnmanagedRole {
id
@@ -12,9 +21,11 @@ query {
defaultRole {
... on UnmanagedRole {
id
+ name
}
... on ManagedRole {
id
+ name
}
}
}
diff --git a/catalog/app/containers/Admin/UsersAndRoles.js b/catalog/app/containers/Admin/UsersAndRoles.tsx
similarity index 61%
rename from catalog/app/containers/Admin/UsersAndRoles.js
rename to catalog/app/containers/Admin/UsersAndRoles.tsx
index f7141628385..97cc31cc945 100644
--- a/catalog/app/containers/Admin/UsersAndRoles.js
+++ b/catalog/app/containers/Admin/UsersAndRoles.tsx
@@ -1,23 +1,18 @@
import * as React from 'react'
import * as M from '@material-ui/core'
-import * as APIConnector from 'utils/APIConnector'
import MetaTitle from 'utils/MetaTitle'
-import * as Cache from 'utils/ResourceCache'
+// XXX: move the components imported below into a single module
import { Roles, Policies } from './RolesAndPolicies'
import Users from './Users'
-import * as data from './data'
export default function UsersAndRoles() {
- const req = APIConnector.use()
- // TODO: use gql for querying users when implemented
- const users = Cache.useData(data.UsersResource, { req })
return (
<>
{['Users, Roles and Policies', 'Admin']}
-
+
diff --git a/catalog/app/containers/Admin/data.js b/catalog/app/containers/Admin/data.js
deleted file mode 100644
index c1ae65b434f..00000000000
--- a/catalog/app/containers/Admin/data.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as R from 'ramda'
-
-import * as Cache from 'utils/ResourceCache'
-
-// TODO: remove after migrating this data to gql
-export const UsersResource = Cache.createResource({
- name: 'Admin.data.users',
- fetch: ({ req }) =>
- req({ endpoint: `/users/list?_cachebust=${Math.random()}` }).then(
- R.pipe(
- R.prop('results'),
- R.map((u) => ({
- dateJoined: new Date(u.date_joined),
- email: u.email,
- isActive: u.is_active,
- isAdmin: u.is_superuser,
- lastLogin: new Date(u.last_login),
- username: u.username,
- roleId: u.role_id,
- })),
- ),
- ),
- key: () => null,
-})
diff --git a/catalog/app/containers/Admin/index.js b/catalog/app/containers/Admin/index.ts
similarity index 100%
rename from catalog/app/containers/Admin/index.js
rename to catalog/app/containers/Admin/index.ts