diff --git a/catalog/app/app.tsx b/catalog/app/app.tsx index 4caf240de51..0e23af5bc87 100644 --- a/catalog/app/app.tsx +++ b/catalog/app/app.tsx @@ -39,6 +39,7 @@ import * as APIConnector from 'utils/APIConnector' import * as GraphQL from 'utils/GraphQL' import { BucketCacheProvider } from 'utils/BucketCache' import GlobalAPI from 'utils/GlobalAPI' +import WithGlobalDialogs from 'utils/GlobalDialogs' import log from 'utils/Logging' import * as NamedRoutes from 'utils/NamedRoutes' import { PFSCookieManager } from 'utils/PFSCookieManager' @@ -123,6 +124,7 @@ const render = () => { AWS.Athena.Provider, AWS.S3.Provider, Notifications.WithNotifications, + WithGlobalDialogs, Errors.ErrorBoundary, BucketCacheProvider, PFSCookieManager, diff --git a/catalog/app/containers/Admin/Form.tsx b/catalog/app/containers/Admin/Form.tsx index 2d9e8ccb66a..be0edabb2c0 100644 --- a/catalog/app/containers/Admin/Form.tsx +++ b/catalog/app/containers/Admin/Form.tsx @@ -1,14 +1,17 @@ import * as React from 'react' -import type * as RF from 'react-final-form' +import * as RF from 'react-final-form' import * as M from '@material-ui/core' -export interface FieldProps { - errors: Record - input: RF.FieldInputProps - meta: RF.FieldMetaState +type ErrorMessageMap = Record + +interface FieldOwnProps { + errors: ErrorMessageMap } +export type FieldProps = FieldOwnProps & RF.FieldRenderProps & M.TextFieldProps + // TODO: re-use components/Form/TextField +// XXX: extract all custom logic into a function and use MUI's TextField (and other components) explicitly export function Field({ input, meta, @@ -16,8 +19,9 @@ export function Field({ helperText, InputLabelProps, ...rest -}: FieldProps & M.TextFieldProps) { - const error = meta.submitFailed && (meta.error || meta.submitError) +}: FieldProps) { + const error = + meta.submitFailed && (meta.error || (!meta.dirtySinceLastSubmit && meta.submitError)) const props = { error: !!error, helperText: error ? errors[error] || error : helperText, @@ -37,7 +41,7 @@ const useCheckboxStyles = M.makeStyles({ }) export interface CheckboxProps { - errors?: Record + errors?: ErrorMessageMap input?: RF.FieldInputProps meta: RF.FieldMetaState label?: React.ReactNode @@ -80,16 +84,12 @@ const useFormErrorStyles = M.makeStyles((t) => ({ }, })) -interface FormErrorProps { - errors: Record +interface FormErrorProps extends M.TypographyProps { error?: string + errors: ErrorMessageMap } -export function FormError({ - error, - errors, - ...rest -}: FormErrorProps & M.TypographyProps) { +export function FormError({ error, errors, ...rest }: FormErrorProps) { const classes = useFormErrorStyles() if (!error) return null return ( @@ -98,3 +98,19 @@ export function FormError({ ) } + +interface FormErrorAutoProps extends M.TypographyProps { + children: ErrorMessageMap +} + +export function FormErrorAuto({ children: errors, ...props }: FormErrorAutoProps) { + const state = RF.useFormState({ + subscription: { + error: true, + submitError: true, + submitFailed: true, + }, + }) + const error = state.submitFailed && (state.submitError || state.error) + return +} diff --git a/catalog/app/containers/Admin/RFForm.tsx b/catalog/app/containers/Admin/RFForm.tsx deleted file mode 100644 index bf971094fc9..00000000000 --- a/catalog/app/containers/Admin/RFForm.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from 'react' -import * as RF from 'react-final-form' -import * as M from '@material-ui/core' - -interface FieldOwnProps { - error?: string - errors: Record - helperText?: React.ReactNode - validating?: boolean -} - -type FieldProps = FieldOwnProps & RF.FieldRenderProps & M.TextFieldProps - -export function Field({ input, meta, errors, helperText, ...rest }: FieldProps) { - const error = - meta.submitFailed && (meta.error || (!meta.dirtySinceLastSubmit && meta.submitError)) - const props = { - error: !!error, - helperText: error ? errors[error] || error : helperText, - disabled: meta.submitting || meta.submitSucceeded, - ...input, - ...rest, - } - return -} - -const useFormErrorStyles = M.makeStyles((t) => ({ - root: { - marginTop: t.spacing(3), - - '& a': { - textDecoration: 'underline', - }, - }, -})) - -type FormErrorProps = M.TypographyProps & { - error?: string - errors: Record -} - -export function FormError({ error, errors, ...rest }: FormErrorProps) { - const classes = useFormErrorStyles() - return !error ? null : ( - - {errors[error] || error} - - ) -} diff --git a/catalog/app/containers/Admin/RolesAndPolicies/index.ts b/catalog/app/containers/Admin/RolesAndPolicies/index.ts deleted file mode 100644 index b9c6038f761..00000000000 --- a/catalog/app/containers/Admin/RolesAndPolicies/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as Roles } from './Roles' -export { default as Policies } from './Policies' diff --git a/catalog/app/containers/Admin/Table/untyped.js b/catalog/app/containers/Admin/Table/untyped.js index c295ad4403f..de7d9b9d18a 100644 --- a/catalog/app/containers/Admin/Table/untyped.js +++ b/catalog/app/containers/Admin/Table/untyped.js @@ -43,7 +43,7 @@ export function Head({ )} ))} - {withInlineActions && Actions} + {withInlineActions &&  } ) 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 - -
- - Enter a valid username{' '} - - info - - - ), - }} - autoComplete="off" - /> - - - {roles.map((r) => ( - - {r.name} - - ))} - - {(!!error || !!submitError) && ( - - )} - - -
- - 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}" - -
- - {submitFailed && ( - - )} - - -
- - 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/index.ts b/catalog/app/containers/Admin/Users/index.ts deleted file mode 100644 index ebbbf9cf16e..00000000000 --- a/catalog/app/containers/Admin/Users/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Users' diff --git a/catalog/app/containers/Admin/UsersAndRoles.js b/catalog/app/containers/Admin/UsersAndRoles.js deleted file mode 100644 index f7141628385..00000000000 --- a/catalog/app/containers/Admin/UsersAndRoles.js +++ /dev/null @@ -1,30 +0,0 @@ -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' - -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/RolesAndPolicies/AssociatedRoles.tsx b/catalog/app/containers/Admin/UsersAndRoles/AssociatedRoles.tsx similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/AssociatedRoles.tsx rename to catalog/app/containers/Admin/UsersAndRoles/AssociatedRoles.tsx diff --git a/catalog/app/containers/Admin/RolesAndPolicies/AttachedPolicies.tsx b/catalog/app/containers/Admin/UsersAndRoles/AttachedPolicies.tsx similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/AttachedPolicies.tsx rename to catalog/app/containers/Admin/UsersAndRoles/AttachedPolicies.tsx diff --git a/catalog/app/containers/Admin/RolesAndPolicies/BucketsPermissions.tsx b/catalog/app/containers/Admin/UsersAndRoles/BucketsPermissions.tsx similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/BucketsPermissions.tsx rename to catalog/app/containers/Admin/UsersAndRoles/BucketsPermissions.tsx diff --git a/catalog/app/containers/Admin/RolesAndPolicies/Filter.tsx b/catalog/app/containers/Admin/UsersAndRoles/Filter.tsx similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/Filter.tsx rename to catalog/app/containers/Admin/UsersAndRoles/Filter.tsx diff --git a/catalog/app/containers/Admin/RolesAndPolicies/Policies.tsx b/catalog/app/containers/Admin/UsersAndRoles/Policies.tsx similarity index 91% rename from catalog/app/containers/Admin/RolesAndPolicies/Policies.tsx rename to catalog/app/containers/Admin/UsersAndRoles/Policies.tsx index aa3b56a336f..33ccf850c62 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/Policies.tsx +++ b/catalog/app/containers/Admin/UsersAndRoles/Policies.tsx @@ -15,7 +15,7 @@ import { mkFormError, mapInputErrors } from 'utils/formTools' import * as Types from 'utils/types' import validate, * as validators from 'utils/validators' -import * as Form from '../RFForm' +import * as Form from '../Form' import * as Table from '../Table' import AssociatedRoles from './AssociatedRoles' @@ -601,7 +601,7 @@ function Edit({ policy, close }: EditProps) { interface SettingsMenuProps { policy: Policy - openDialog: (render: (props: DialogsOpenProps) => JSX.Element, props?: $TSFixMe) => void + openDialog: Dialogs.Open } function SettingsMenu({ policy, openDialog }: SettingsMenuProps) { @@ -641,11 +641,6 @@ function SettingsMenu({ policy, openDialog }: SettingsMenuProps) { ) } -// XXX: move to dialogs module -interface DialogsOpenProps { - close: (reason?: string) => void -} - export default function Policies() { const { policies: rows } = GQL.useQueryS(POLICIES_QUERY) @@ -664,7 +659,7 @@ export default function Policies() { title: 'Create', icon: add, fn: React.useCallback(() => { - dialogs.open(({ close }: DialogsOpenProps) => ) + dialogs.open(({ close }) => ) }, [dialogs.open]), // eslint-disable-line react-hooks/exhaustive-deps }, ] @@ -681,54 +676,38 @@ export default function Policies() { title: 'Edit', icon: edit, fn: () => { - dialogs.open(({ close }: DialogsOpenProps) => ( - - )) + dialogs.open(({ close }) => ) }, }, ] return ( - - - - - } - > - - {dialogs.render({ fullWidth: true, maxWidth: 'sm' })} - - - - - - - - {ordering.ordered.map((i: Policy) => ( - - {columns.map((col) => ( - - {(col.getDisplay || R.identity)(col.getValue(i), i)} - - ))} - - - - + <> + {dialogs.render({ fullWidth: true, maxWidth: 'sm' })} + + + + + + + + {ordering.ordered.map((i: Policy) => ( + + {columns.map((col) => ( + + {(col.getDisplay || R.identity)(col.getValue(i), i)} - - ))} - - - - - + ))} + + + + + + + ))} + + + + ) } diff --git a/catalog/app/containers/Admin/UsersAndRoles/RoleSelect.tsx b/catalog/app/containers/Admin/UsersAndRoles/RoleSelect.tsx new file mode 100644 index 00000000000..49ced465f89 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/RoleSelect.tsx @@ -0,0 +1,237 @@ +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' + +export interface Role { + id: string + name: string +} + +export interface Value { + selected: readonly Role[] + active: Role | null +} + +export const EMPTY_VALUE: Value = { selected: [], active: null } + +export const validate: FF.FieldValidator = (v) => { + if (!v.selected.length) return 'required' + if (!v.active) return 'active' +} + +export const ROLE_NAME_ASC = R.ascend((r: Role) => r.name) + +const ITEM_HEIGHT = 46 + +const useRoleSelectStyles = M.makeStyles((t) => ({ + grid: { + alignItems: 'center', + display: 'grid', + gap: t.spacing(1), + grid: 'auto-flow / 1fr auto 1fr', + marginTop: t.spacing(2), + }, + list: ({ roles }: { roles: number }) => ({ + // show no less than 3 and no more than 4 and a half items + height: `${ITEM_HEIGHT * R.clamp(3, 4.5, roles)}px`, + overflowY: 'auto', + }), + listEmpty: { + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + paddingTop: t.spacing(3), + }, + availableRole: { + paddingBottom: '5px', + paddingTop: '5px', + }, + defaultRole: { + fontWeight: t.typography.fontWeightMedium, + '&::after': { + content: '"*"', + }, + }, +})) + +interface RoleSelectProps extends RF.FieldRenderProps { + roles: readonly Role[] + defaultRole: Role | null +} + +export function RoleSelect({ + roles, + defaultRole, + input: { value, onChange }, + meta, +}: RoleSelectProps) { + const classes = useRoleSelectStyles({ roles: roles.length }) + + const error = meta.submitFailed && meta.error + const disabled = meta.submitting || meta.submitSucceeded + + const { active, selected } = value ?? EMPTY_VALUE + + const available = React.useMemo( + () => roles.filter((r) => !selected.find((r2) => r2.id === r.id)).sort(ROLE_NAME_ASC), + [roles, selected], + ) + + const add = (r: Role) => + onChange({ + selected: selected.concat(r).sort(ROLE_NAME_ASC), + active: active ?? r, + }) + + const remove = (r: Role) => { + const newSelected = selected.filter((r2) => r2.id !== r.id) + let newActive: Role | null + if (newSelected.length === 1) { + // select the only available role + newActive = newSelected[0] + } else if (newSelected.find((r2) => r2.id === active?.id)) { + // keep the active role if it's still available + newActive = active + } else { + newActive = null + } + onChange({ selected: newSelected, active: newActive }) + } + + const activate = (r: Role) => onChange({ selected, active: r }) + + const clear = () => onChange({ selected: [], active: null }) + + function roleNameDisplay(r: Role) { + if (r.id !== defaultRole?.id) return r.name + return ( + + {r.name} + + ) + } + + return ( + + {error ? ( + + {error === 'required' ? 'Assign at least one role' : 'Select an active role'} + + ) : ( + + User can assume any of the assigned roles + + )} + +
+ + + + + + + clear_all + + + + + {selected.length ? ( + + {selected.map((r) => ( + activate(r)} + > + + + + + {roleNameDisplay(r)} + + + + remove(r)}> + close + + + + + ))} + + ) : ( +
+ + No roles assigned + + {!!defaultRole && ( + <> + + add(defaultRole)} + disabled={disabled} + > + Assign default role + + + )} +
+ )} +
+ + sync_alt + + + + + + {available.length ? ( + + {available.map((r) => ( + add(r)}> + + {roleNameDisplay(r)} + + + ))} + + ) : ( +
+ All roles assigned +
+ )} +
+
+
+ ) +} diff --git a/catalog/app/containers/Admin/RolesAndPolicies/Roles.tsx b/catalog/app/containers/Admin/UsersAndRoles/Roles.tsx similarity index 94% rename from catalog/app/containers/Admin/RolesAndPolicies/Roles.tsx rename to catalog/app/containers/Admin/UsersAndRoles/Roles.tsx index cba79130d01..d8e2c8b5517 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/Roles.tsx +++ b/catalog/app/containers/Admin/UsersAndRoles/Roles.tsx @@ -14,7 +14,7 @@ import assertNever from 'utils/assertNever' import * as Types from 'utils/types' import * as validators from 'utils/validators' -import * as Form from '../RFForm' +import * as Form from '../Form' import * as Table from '../Table' import AttachedPolicies from './AttachedPolicies' @@ -734,43 +734,34 @@ export default function Roles() { ] return ( - - - - - } - > - - {dialogs.render({ fullWidth: true, maxWidth: 'sm' })} - - - - - - - - {ordering.ordered.map((i: Role) => ( - - {columns.map((col) => ( - - {(col.getDisplay || R.identity)(col.getValue(i), i, { - defaultRoleId, - })} - - ))} - - - - + <> + {dialogs.render({ fullWidth: true, maxWidth: 'sm' })} + + + + + + + + {ordering.ordered.map((i: Role) => ( + + {columns.map((col) => ( + + {(col.getDisplay || R.identity)(col.getValue(i), i, { + defaultRoleId, + })} - - ))} - - - - - + ))} + + + + + + + ))} + + + + ) } diff --git a/catalog/app/containers/Admin/UsersAndRoles/SuspenseWrapper.tsx b/catalog/app/containers/Admin/UsersAndRoles/SuspenseWrapper.tsx new file mode 100644 index 00000000000..714e81e4215 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/SuspenseWrapper.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' +import * as M from '@material-ui/core' + +import * as Table from '../Table' + +interface SuspenseWrapperProps { + children: React.ReactNode + heading: React.ReactNode +} + +export default function SuspenseWrapper({ children, heading }: SuspenseWrapperProps) { + return ( + + + + + + } + > + {children} + + + ) +} diff --git a/catalog/app/containers/Admin/UsersAndRoles/Users.tsx b/catalog/app/containers/Admin/UsersAndRoles/Users.tsx new file mode 100644 index 00000000000..4b884cc79f2 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/Users.tsx @@ -0,0 +1,1070 @@ +import cx from 'classnames' +import * as FF from 'final-form' +import invariant from 'invariant' +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 Sentry from '@sentry/react' + +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 * as RoleSelect from './RoleSelect' + +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 useDialogFormStyles = M.makeStyles((t) => ({ + root: { + marginTop: t.spacing(-2), + }, +})) + +function DialogForm({ className, ...props }: React.FormHTMLAttributes) { + const classes = useDialogFormStyles() + return
+} + +const useInviteStyles = M.makeStyles({ + infoIcon: { + fontSize: '1.25em', + verticalAlign: '-3px', + }, +}) + +interface InviteProps { + close: () => void + roles: readonly Role[] + defaultRole: Role | null +} + +function Invite({ close, roles, defaultRole }: InviteProps) { + const classes = useInviteStyles() + const create = GQL.useMutation(USER_CREATE_MUTATION) + const { push } = Notifications.use() + + interface FormValues { + username: string + email: string + roles: RoleSelect.Value + } + + const onSubmit = React.useCallback( + async (values: FormValues) => { + // XXX: use formspec to convert/validate form values into gql input? + invariant(values.roles.active, 'No active role') + const role = values.roles.active.name + const extraRoles = values.roles.selected + .map((r) => r.name) + .filter((r) => r !== role) + const input = { + name: values.username, + email: values.email, + role, + extraRoles, + } + try { + 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' } + } + 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': + errors.username = e.name === 'Conflict' ? 'taken' : 'invalid' + break + case 'input.email': + errors.email = e.name === 'Conflict' ? 'taken' : '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', input) + // eslint-disable-next-line no-console + console.dir(e) + Sentry.captureException(e) + return { [FF.FORM_ERROR]: 'unexpected' } + } + }, + [create, push, close], + ) + + return ( + + onSubmit={onSubmit} + initialValues={{ + roles: { + active: defaultRole, + selected: defaultRole ? [defaultRole] : [], + }, + }} + initialValuesEqual={R.equals} + > + {({ + handleSubmit, + submitting, + submitFailed, + hasSubmitErrors, + hasValidationErrors, + modifiedSinceLastSubmit, + }) => ( + <> + Invite a user + + + } + label="Username" + placeholder="Enter a username" + fullWidth + margin="normal" + errors={{ + required: 'Enter a username', + taken: 'Username already taken', + invalid: ( + <> + Enter a valid username{' '} + + info + + + ), + }} + autoComplete="off" + /> + } + label="Email" + placeholder="Enter an email" + fullWidth + margin="normal" + errors={{ + required: 'Enter an email', + taken: 'Email already taken', + invalid: 'Enter a valid email', + }} + autoComplete="off" + /> + + Assign roles + name="roles" validate={RoleSelect.validate}> + {(props) => ( + + )} + + + {{ + unexpected: 'Something went wrong', + smtp: 'SMTP error: contact your administrator', + subscriptionInvalid: 'Invalid subscription', + }} + + + + + + + Cancel + + + Invite + + + + )} + + ) +} + +interface EditEmailProps { + close: () => void + user: User +} + +function EditEmail({ close, user: { email: oldEmail, name } }: EditEmailProps) { + 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 + switch (r?.__typename) { + case 'User': + close() + push('Changes saved') + return + case undefined: + throw new Error('User not found') // should not happend + case 'OperationError': + if (r.name === 'EmailAlreadyInUse') return { email: 'taken' } + throw new Error(`Unexpected operation error: [${r.name}] ${r.message}`) + case 'InvalidInput': + const [e] = r.errors + if (e.path === 'email' && e.name === 'InvalidEmail') { + return { email: 'invalid' } + } + throw new Error( + `Unexpected input error at '${e.path}': [${e.name}] ${e.message}`, + ) + default: + assertNever(r) + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error changing email', { name, email }) + // eslint-disable-next-line no-console + console.dir(e) + Sentry.captureException(e) + return { [FF.FORM_ERROR]: 'unexpected' } + } + }, + [close, name, oldEmail, setEmail, push], + ) + + return ( + + {({ + handleSubmit, + submitting, + submitFailed, + hasSubmitErrors, + hasValidationErrors, + modifiedSinceLastSubmit, + }) => ( + <> + Edit email for user "{name}" + + + } + label="Email" + placeholder="Enter an email" + fullWidth + margin="normal" + errors={{ + required: 'Enter an email', + taken: 'Email already taken', + invalid: 'Enter a valid email', + }} + autoComplete="off" + /> + + {{ unexpected: 'Something went wrong' }} + + + + + + + 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) + Sentry.captureException(e) + return { [FF.FORM_ERROR]: 'unexpected' } + } + }, [del, name, close, push]) + + return ( + + {({ handleSubmit, submitting }) => ( + <> + Delete a user + + You are about to delete user "{name}". +
+ This operation is irreversible. +
+ + {{ + unexpected: 'Something went wrong', + notFound: 'User not found', // should not happen + deleteSelf: 'You cannot delete yourself', // should not happen + }} + +
+ + {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 user "${name}": ${e}`) + // 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'}{' '} + user "{name}". + + + close(false)} color="primary"> + Cancel + + + {admin ? 'Grant' : 'Revoke'} + + + + ) +} + +const useHintStyles = M.makeStyles((t) => ({ + hint: { + color: t.palette.text.hint, + fontWeight: t.typography.fontWeightLight, + }, +})) + +function Hint({ className, ...props }: React.HTMLAttributes) { + const classes = useHintStyles() + return +} + +const useClickableStyles = M.makeStyles((t) => ({ + clickable: { + borderBottom: `1px dashed ${t.palette.text.hint}`, + cursor: 'pointer', + }, +})) + +const Clickable = React.forwardRef< + HTMLSpanElement, + React.HTMLAttributes +>(function Clickable({ className, ...props }, ref) { + const classes = useClickableStyles() + return +}) + +const useUsernameStyles = M.makeStyles((t) => ({ + root: { + alignItems: 'center', + display: 'flex', + }, + name: { + maxWidth: '14rem', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + admin: { + fontWeight: t.typography.fontWeightMedium, + }, + self: { + '&$name': { + maxWidth: '12rem', + }, + }, + icon: { + fontSize: '1em', + marginLeft: `calc(-1em - ${t.spacing(0.5)}px)`, + marginRight: t.spacing(0.5), + }, +})) + +interface UsernameDisplayProps { + user: User + self: boolean +} + +function UsernameDisplay({ user, self }: UsernameDisplayProps) { + const classes = useUsernameStyles() + return ( + + {user.isAdmin && security} + + + {user.name} + + + {self &&  (you)} + + ) +} + +interface EditRolesProps { + close: Dialogs.Close + roles: readonly Role[] + defaultRole: Role | null + user: User +} + +function EditRoles({ close, roles, defaultRole, user }: EditRolesProps) { + const { push } = Notifications.use() + const setRole = GQL.useMutation(USER_SET_ROLE_MUTATION) + + interface FormValues { + roles: RoleSelect.Value + } + + const onSubmit = React.useCallback( + async (values: FormValues) => { + // XXX: use formspec to convert/validate form values into gql input? + invariant(values.roles.active, 'No active role') + const role = values.roles.active.name + const extraRoles = values.roles.selected + .map((r) => r.name) + .filter((r) => r !== role) + const vars = { + name: user.name, + role, + extraRoles, + } + try { + const data = await setRole(vars) + const r = data.admin.user.mutate?.setRole + switch (r?.__typename) { + case undefined: + throw new Error('User not found') // should not happend + case 'User': + close() + push('Changes saved') + return + case 'OperationError': + // should not happend + throw new Error(`Unexpected operation error: [${r.name}] ${r.message}`) + case 'InvalidInput': + // should not happend + const [e] = r.errors + throw new Error( + `Unexpected input error at '${e.path}': [${e.name}] ${e.message}`, + ) + default: + return assertNever(r) + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error setting roles for user', vars) + // eslint-disable-next-line no-console + console.dir(e) + Sentry.captureException(e) + return { [FF.FORM_ERROR]: 'unexpected' } + } + }, + [push, close, setRole, user.name], + ) + + const selected = React.useMemo( + () => user.extraRoles.concat(user.role ?? []).sort(RoleSelect.ROLE_NAME_ASC), + [user.extraRoles, user.role], + ) + + return ( + + onSubmit={onSubmit} + initialValues={{ roles: { active: user.role, selected } }} + initialValuesEqual={R.equals} + > + {({ + form, + handleSubmit, + hasSubmitErrors, + hasValidationErrors, + modifiedSinceLastSubmit, + pristine, + submitFailed, + submitting, + }) => ( + <> + Assign roles to "{user.name}" + + + name="roles" validate={RoleSelect.validate}> + {(props) => ( + + )} + + + {{ unexpected: 'Something went wrong' }} + + + + + form.reset()} + color="primary" + disabled={pristine || submitting} + > + Reset + + + Cancel + + + Save + + + + )} + + ) +} + +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 }) +} + +const useEditableStyles = M.makeStyles((t) => ({ + root: { + marginLeft: t.spacing(0.5), + }, +})) + +interface EditableSwitchProps { + disabled?: boolean + checked: boolean + onChange: (v: boolean) => void + hint: NonNullable +} + +function EditableSwitch({ + disabled = false, + checked, + onChange, + hint, +}: EditableSwitchProps) { + const classes = useEditableStyles() + return disabled ? ( + + ) : ( + + {({ change, busy, value }) => ( + + change(e.target.checked)} + disabled={busy} + color="default" + /> + + )} + + ) +} + +interface EmailDisplayProps { + user: User + openDialog: Dialogs.Open +} + +function EmailDisplay({ user, openDialog }: EmailDisplayProps) { + const edit = () => + openDialog(({ close }) => , DIALOG_PROPS) + + return ( + + {user.email} + + ) +} + +// not a valid role name +const emptyRole = '' + +interface RoleDisplayProps { + user: User + roles: readonly Role[] + defaultRole: Role | null + openDialog: Dialogs.Open +} + +function RoleDisplay({ user, roles, defaultRole, openDialog }: RoleDisplayProps) { + const edit = () => + openDialog(({ close }) => , { + maxWidth: 'sm', + fullWidth: true, + }) + + return ( + + + {user.role?.name ?? emptyRole} + {user.extraRoles.length > 0 && +{user.extraRoles.length}} + + + ) +} + +function DateDisplay({ value }: { value: Date }) { + return ( + + + + + + ) +} + +interface ColumnDisplayProps { + roles: readonly Role[] + defaultRole: Role | null + setActive: (name: string, active: boolean) => Promise + openDialog: Dialogs.Open + isSelf: boolean +} + +const columns: Table.Column[] = [ + { + id: 'isActive', + label: 'Enabled', + getValue: (u) => u.isActive, + getDisplay: (_v, u, { setActive, isSelf }: ColumnDisplayProps) => ( + setActive(u.name, active)} + /> + ), + props: { padding: 'none' }, + }, + { + id: 'username', + label: 'Username', + getValue: (u) => u.name, + getDisplay: (_v, u, { isSelf }: ColumnDisplayProps) => ( + + ), + props: { component: 'th', scope: 'row' }, + }, + { + id: 'email', + label: 'Email', + getValue: (u) => u.email, + getDisplay: (_v, u, { openDialog }: ColumnDisplayProps) => ( + + ), + }, + { + id: 'role', + label: 'Role', + getValue: (u) => u.role?.name, + getDisplay: (_v, u, { roles, defaultRole, openDialog }: ColumnDisplayProps) => ( + + ), + }, + { + id: 'dateJoined', + label: 'Date joined', + getValue: (u) => u.dateJoined, + getDisplay: (_v, u) => , + }, + { + id: 'lastLogin', + label: 'Last login', + getValue: (u) => u.lastLogin, + getDisplay: (_v, u) => , + }, + { + id: 'isAdmin', + label: 'Admin', + getValue: (u) => u.isAdmin, + getDisplay: (_v, u, { openDialog, isSelf }: ColumnDisplayProps) => ( + + openDialog( + ({ close }) => , + DIALOG_PROPS, + ).then((res) => { + if (!res) throw new Error('cancel') + }) + } + /> + ), + props: { padding: 'none' }, + }, +] + +function useSetActive() { + const { push } = Notifications.use() + const setActive = GQL.useMutation(USER_SET_ACTIVE_MUTATION) + + return React.useCallback( + async (name: string, active: boolean) => { + try { + const data = await setActive({ name, active }) + const r = data.admin.user.mutate?.setActive + switch (r?.__typename) { + case 'User': + return + case undefined: + throw new Error('User not found') // should not happend + case 'OperationError': + throw new Error(`Unexpected operation error: [${r.name}] ${r.message}`) + case 'InvalidInput': + const [e] = r.errors + throw new Error( + `Unexpected input error at '${e.path}': [${e.name}] ${e.message}`, + ) + default: + assertNever(r) + } + } catch (e) { + push(`Could not ${active ? 'enable' : 'disable'} user "${name}": ${e}`) + // eslint-disable-next-line no-console + console.error('Error (de)activating user', { name, active }) + // eslint-disable-next-line no-console + console.dir(e) + Sentry.captureException(e) + throw e + } + }, + [setActive, push], + ) +} + +const useStyles = M.makeStyles((t) => ({ + table: { + '& th, & td': { + whiteSpace: 'nowrap', + }, + '& tbody th, & tbody td': { + paddingRight: t.spacing(1), + }, + }, +})) + +export default function Users() { + const classes = useStyles() + + const data = GQL.useQueryS(USERS_QUERY) + const rows = data.admin.user.list + const { roles, defaultRole } = data + + const openDialog = Dialogs.use() + + const setActive = useSetActive() + + const filtering = Table.useFiltering({ + rows, + filterBy: ({ email, name }) => email + name, + }) + const ordering = Table.useOrdering({ + rows: filtering.filtered, + column: columns[1], + }) + 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, + maxWidth: 'sm', + }) + }, [roles, defaultRole, 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, + ), + }, + ] + + const getDisplayProps = (u: User): ColumnDisplayProps => ({ + setActive, + roles, + defaultRole, + 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/RolesAndPolicies/gql/BucketPermissionSelection.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/BucketPermissionSelection.generated.ts similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/BucketPermissionSelection.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/BucketPermissionSelection.generated.ts diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/BucketPermissionSelection.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/BucketPermissionSelection.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/BucketPermissionSelection.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/BucketPermissionSelection.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/Buckets.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/Buckets.generated.ts similarity index 70% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/Buckets.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/Buckets.generated.ts index 36b9e927b6c..51ec36847aa 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/Buckets.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/Buckets.generated.ts @@ -2,11 +2,11 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' import * as Types from '../../../../model/graphql/types.generated' -export type containers_Admin_RolesAndPolicies_gql_BucketsQueryVariables = Types.Exact<{ +export type containers_Admin_UsersAndRoles_gql_BucketsQueryVariables = Types.Exact<{ [key: string]: never }> -export type containers_Admin_RolesAndPolicies_gql_BucketsQuery = { +export type containers_Admin_UsersAndRoles_gql_BucketsQuery = { readonly __typename: 'Query' } & { readonly buckets: ReadonlyArray< @@ -17,13 +17,13 @@ export type containers_Admin_RolesAndPolicies_gql_BucketsQuery = { > } -export const containers_Admin_RolesAndPolicies_gql_BucketsDocument = { +export const containers_Admin_UsersAndRoles_gql_BucketsDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'query', - name: { kind: 'Name', value: 'containers_Admin_RolesAndPolicies_gql_Buckets' }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_gql_Buckets' }, selectionSet: { kind: 'SelectionSet', selections: [ @@ -45,8 +45,8 @@ export const containers_Admin_RolesAndPolicies_gql_BucketsDocument = { }, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_BucketsQuery, - containers_Admin_RolesAndPolicies_gql_BucketsQueryVariables + containers_Admin_UsersAndRoles_gql_BucketsQuery, + containers_Admin_UsersAndRoles_gql_BucketsQueryVariables > -export { containers_Admin_RolesAndPolicies_gql_BucketsDocument as default } +export { containers_Admin_UsersAndRoles_gql_BucketsDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/Buckets.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/Buckets.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/Buckets.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/Buckets.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/Policies.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/Policies.generated.ts similarity index 69% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/Policies.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/Policies.generated.ts index a7f5df550a5..f8e6709d56f 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/Policies.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/Policies.generated.ts @@ -7,11 +7,11 @@ import { PolicySelectionFragmentDoc, } from './PolicySelection.generated' -export type containers_Admin_RolesAndPolicies_gql_PoliciesQueryVariables = Types.Exact<{ +export type containers_Admin_UsersAndRoles_gql_PoliciesQueryVariables = Types.Exact<{ [key: string]: never }> -export type containers_Admin_RolesAndPolicies_gql_PoliciesQuery = { +export type containers_Admin_UsersAndRoles_gql_PoliciesQuery = { readonly __typename: 'Query' } & { readonly policies: ReadonlyArray< @@ -19,13 +19,13 @@ export type containers_Admin_RolesAndPolicies_gql_PoliciesQuery = { > } -export const containers_Admin_RolesAndPolicies_gql_PoliciesDocument = { +export const containers_Admin_UsersAndRoles_gql_PoliciesDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'query', - name: { kind: 'Name', value: 'containers_Admin_RolesAndPolicies_gql_Policies' }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_gql_Policies' }, selectionSet: { kind: 'SelectionSet', selections: [ @@ -48,8 +48,8 @@ export const containers_Admin_RolesAndPolicies_gql_PoliciesDocument = { ...PolicySelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_PoliciesQuery, - containers_Admin_RolesAndPolicies_gql_PoliciesQueryVariables + containers_Admin_UsersAndRoles_gql_PoliciesQuery, + containers_Admin_UsersAndRoles_gql_PoliciesQueryVariables > -export { containers_Admin_RolesAndPolicies_gql_PoliciesDocument as default } +export { containers_Admin_UsersAndRoles_gql_PoliciesDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/Policies.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/Policies.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/Policies.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/Policies.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateManaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateManaged.generated.ts similarity index 80% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateManaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateManaged.generated.ts index 11399e561f2..31ca1b9899f 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateManaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateManaged.generated.ts @@ -9,12 +9,12 @@ import { PolicyResultSelectionFragmentDoc, } from './PolicyResultSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_PolicyCreateManagedMutationVariables = Types.Exact<{ input: Types.ManagedPolicyInput }> -export type containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedMutation = { +export type containers_Admin_UsersAndRoles_gql_PolicyCreateManagedMutation = { readonly __typename: 'Mutation' } & { readonly policyCreate: @@ -27,7 +27,7 @@ export type containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedMutation = } & PolicyResultSelection_OperationError_Fragment) } -export const containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedDocument = { +export const containers_Admin_UsersAndRoles_gql_PolicyCreateManagedDocument = { kind: 'Document', definitions: [ { @@ -35,7 +35,7 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedDocument = operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_PolicyCreateManaged', + value: 'containers_Admin_UsersAndRoles_gql_PolicyCreateManaged', }, variableDefinitions: [ { @@ -80,8 +80,8 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedDocument = ...PolicyResultSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedMutation, - containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedMutationVariables + containers_Admin_UsersAndRoles_gql_PolicyCreateManagedMutation, + containers_Admin_UsersAndRoles_gql_PolicyCreateManagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_PolicyCreateManagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_PolicyCreateManagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateManaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateManaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateManaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateManaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateUnmanaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateUnmanaged.generated.ts similarity index 79% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateUnmanaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateUnmanaged.generated.ts index 8c9d9d1a4a5..fb112d39165 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateUnmanaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateUnmanaged.generated.ts @@ -9,12 +9,12 @@ import { PolicyResultSelectionFragmentDoc, } from './PolicyResultSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanagedMutationVariables = Types.Exact<{ input: Types.UnmanagedPolicyInput }> -export type containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedMutation = { +export type containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanagedMutation = { readonly __typename: 'Mutation' } & { readonly policyCreate: @@ -27,7 +27,7 @@ export type containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedMutation } & PolicyResultSelection_OperationError_Fragment) } -export const containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedDocument = { +export const containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanagedDocument = { kind: 'Document', definitions: [ { @@ -35,7 +35,7 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedDocument operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanaged', + value: 'containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanaged', }, variableDefinitions: [ { @@ -80,8 +80,8 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedDocument ...PolicyResultSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedMutation, - containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedMutationVariables + containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanagedMutation, + containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_PolicyCreateUnmanagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_PolicyCreateUnmanagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateUnmanaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateUnmanaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyCreateUnmanaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyCreateUnmanaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyDelete.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyDelete.generated.ts similarity index 85% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyDelete.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyDelete.generated.ts index 5cef95b64d5..90920054ad6 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyDelete.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyDelete.generated.ts @@ -2,12 +2,12 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' import * as Types from '../../../../model/graphql/types.generated' -export type containers_Admin_RolesAndPolicies_gql_PolicyDeleteMutationVariables = +export type containers_Admin_UsersAndRoles_gql_PolicyDeleteMutationVariables = Types.Exact<{ id: Types.Scalars['ID'] }> -export type containers_Admin_RolesAndPolicies_gql_PolicyDeleteMutation = { +export type containers_Admin_UsersAndRoles_gql_PolicyDeleteMutation = { readonly __typename: 'Mutation' } & { readonly policyDelete: @@ -23,13 +23,13 @@ export type containers_Admin_RolesAndPolicies_gql_PolicyDeleteMutation = { | ({ readonly __typename: 'OperationError' } & Pick) } -export const containers_Admin_RolesAndPolicies_gql_PolicyDeleteDocument = { +export const containers_Admin_UsersAndRoles_gql_PolicyDeleteDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'mutation', - name: { kind: 'Name', value: 'containers_Admin_RolesAndPolicies_gql_PolicyDelete' }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_gql_PolicyDelete' }, variableDefinitions: [ { kind: 'VariableDefinition', @@ -101,8 +101,8 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyDeleteDocument = { }, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_PolicyDeleteMutation, - containers_Admin_RolesAndPolicies_gql_PolicyDeleteMutationVariables + containers_Admin_UsersAndRoles_gql_PolicyDeleteMutation, + containers_Admin_UsersAndRoles_gql_PolicyDeleteMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_PolicyDeleteDocument as default } +export { containers_Admin_UsersAndRoles_gql_PolicyDeleteDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyDelete.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyDelete.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyDelete.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyDelete.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyResultSelection.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyResultSelection.generated.ts similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyResultSelection.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyResultSelection.generated.ts diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyResultSelection.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyResultSelection.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyResultSelection.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyResultSelection.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicySelection.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicySelection.generated.ts similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicySelection.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicySelection.generated.ts diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicySelection.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicySelection.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicySelection.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicySelection.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateManaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateManaged.generated.ts similarity index 83% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateManaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateManaged.generated.ts index 22d2a478d31..7f6146556be 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateManaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateManaged.generated.ts @@ -9,13 +9,13 @@ import { PolicyResultSelectionFragmentDoc, } from './PolicyResultSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_PolicyUpdateManagedMutationVariables = Types.Exact<{ id: Types.Scalars['ID'] input: Types.ManagedPolicyInput }> -export type containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedMutation = { +export type containers_Admin_UsersAndRoles_gql_PolicyUpdateManagedMutation = { readonly __typename: 'Mutation' } & { readonly policyUpdate: @@ -28,7 +28,7 @@ export type containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedMutation = } & PolicyResultSelection_OperationError_Fragment) } -export const containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedDocument = { +export const containers_Admin_UsersAndRoles_gql_PolicyUpdateManagedDocument = { kind: 'Document', definitions: [ { @@ -36,7 +36,7 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedDocument = operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_PolicyUpdateManaged', + value: 'containers_Admin_UsersAndRoles_gql_PolicyUpdateManaged', }, variableDefinitions: [ { @@ -94,8 +94,8 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedDocument = ...PolicyResultSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedMutation, - containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedMutationVariables + containers_Admin_UsersAndRoles_gql_PolicyUpdateManagedMutation, + containers_Admin_UsersAndRoles_gql_PolicyUpdateManagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_PolicyUpdateManagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_PolicyUpdateManagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateManaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateManaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateManaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateManaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateUnmanaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateUnmanaged.generated.ts similarity index 82% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateUnmanaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateUnmanaged.generated.ts index 1b27f3c11ed..dbfed5a02bd 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateUnmanaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateUnmanaged.generated.ts @@ -9,13 +9,13 @@ import { PolicyResultSelectionFragmentDoc, } from './PolicyResultSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanagedMutationVariables = Types.Exact<{ id: Types.Scalars['ID'] input: Types.UnmanagedPolicyInput }> -export type containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedMutation = { +export type containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanagedMutation = { readonly __typename: 'Mutation' } & { readonly policyUpdate: @@ -28,7 +28,7 @@ export type containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedMutation } & PolicyResultSelection_OperationError_Fragment) } -export const containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedDocument = { +export const containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanagedDocument = { kind: 'Document', definitions: [ { @@ -36,7 +36,7 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedDocument operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanaged', + value: 'containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanaged', }, variableDefinitions: [ { @@ -94,8 +94,8 @@ export const containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedDocument ...PolicyResultSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedMutation, - containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedMutationVariables + containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanagedMutation, + containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_PolicyUpdateUnmanagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_PolicyUpdateUnmanagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateUnmanaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateUnmanaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/PolicyUpdateUnmanaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/PolicyUpdateUnmanaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateManaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateManaged.generated.ts similarity index 85% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateManaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateManaged.generated.ts index cc7deac7e2f..118e737d626 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateManaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateManaged.generated.ts @@ -8,12 +8,12 @@ import { RoleSelectionFragmentDoc, } from './RoleSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_RoleCreateManagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_RoleCreateManagedMutationVariables = Types.Exact<{ input: Types.ManagedRoleInput }> -export type containers_Admin_RolesAndPolicies_gql_RoleCreateManagedMutation = { +export type containers_Admin_UsersAndRoles_gql_RoleCreateManagedMutation = { readonly __typename: 'Mutation' } & { readonly roleCreate: @@ -30,7 +30,7 @@ export type containers_Admin_RolesAndPolicies_gql_RoleCreateManagedMutation = { | { readonly __typename: 'RoleHasTooManyPoliciesToAttach' } } -export const containers_Admin_RolesAndPolicies_gql_RoleCreateManagedDocument = { +export const containers_Admin_UsersAndRoles_gql_RoleCreateManagedDocument = { kind: 'Document', definitions: [ { @@ -38,7 +38,7 @@ export const containers_Admin_RolesAndPolicies_gql_RoleCreateManagedDocument = { operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_RoleCreateManaged', + value: 'containers_Admin_UsersAndRoles_gql_RoleCreateManaged', }, variableDefinitions: [ { @@ -104,8 +104,8 @@ export const containers_Admin_RolesAndPolicies_gql_RoleCreateManagedDocument = { ...RoleSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RoleCreateManagedMutation, - containers_Admin_RolesAndPolicies_gql_RoleCreateManagedMutationVariables + containers_Admin_UsersAndRoles_gql_RoleCreateManagedMutation, + containers_Admin_UsersAndRoles_gql_RoleCreateManagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_RoleCreateManagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_RoleCreateManagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateManaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateManaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateManaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateManaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateUnmanaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateUnmanaged.generated.ts similarity index 84% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateUnmanaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateUnmanaged.generated.ts index 9404f1c5126..e25b6ab444e 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateUnmanaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateUnmanaged.generated.ts @@ -8,12 +8,12 @@ import { RoleSelectionFragmentDoc, } from './RoleSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_RoleCreateUnmanagedMutationVariables = Types.Exact<{ input: Types.UnmanagedRoleInput }> -export type containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedMutation = { +export type containers_Admin_UsersAndRoles_gql_RoleCreateUnmanagedMutation = { readonly __typename: 'Mutation' } & { readonly roleCreate: @@ -30,7 +30,7 @@ export type containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedMutation = | { readonly __typename: 'RoleHasTooManyPoliciesToAttach' } } -export const containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedDocument = { +export const containers_Admin_UsersAndRoles_gql_RoleCreateUnmanagedDocument = { kind: 'Document', definitions: [ { @@ -38,7 +38,7 @@ export const containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedDocument = operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanaged', + value: 'containers_Admin_UsersAndRoles_gql_RoleCreateUnmanaged', }, variableDefinitions: [ { @@ -104,8 +104,8 @@ export const containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedDocument = ...RoleSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedMutation, - containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedMutationVariables + containers_Admin_UsersAndRoles_gql_RoleCreateUnmanagedMutation, + containers_Admin_UsersAndRoles_gql_RoleCreateUnmanagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_RoleCreateUnmanagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_RoleCreateUnmanagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateUnmanaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateUnmanaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleCreateUnmanaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleCreateUnmanaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleDelete.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleDelete.generated.ts similarity index 73% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleDelete.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleDelete.generated.ts index 01abd6b0f22..22fc3c0e923 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleDelete.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleDelete.generated.ts @@ -2,12 +2,11 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' import * as Types from '../../../../model/graphql/types.generated' -export type containers_Admin_RolesAndPolicies_gql_RoleDeleteMutationVariables = - Types.Exact<{ - id: Types.Scalars['ID'] - }> +export type containers_Admin_UsersAndRoles_gql_RoleDeleteMutationVariables = Types.Exact<{ + id: Types.Scalars['ID'] +}> -export type containers_Admin_RolesAndPolicies_gql_RoleDeleteMutation = { +export type containers_Admin_UsersAndRoles_gql_RoleDeleteMutation = { readonly __typename: 'Mutation' } & { readonly roleDelete: @@ -17,13 +16,13 @@ export type containers_Admin_RolesAndPolicies_gql_RoleDeleteMutation = { | { readonly __typename: 'RoleAssigned' } } -export const containers_Admin_RolesAndPolicies_gql_RoleDeleteDocument = { +export const containers_Admin_UsersAndRoles_gql_RoleDeleteDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'mutation', - name: { kind: 'Name', value: 'containers_Admin_RolesAndPolicies_gql_RoleDelete' }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_gql_RoleDelete' }, variableDefinitions: [ { kind: 'VariableDefinition', @@ -59,8 +58,8 @@ export const containers_Admin_RolesAndPolicies_gql_RoleDeleteDocument = { }, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RoleDeleteMutation, - containers_Admin_RolesAndPolicies_gql_RoleDeleteMutationVariables + containers_Admin_UsersAndRoles_gql_RoleDeleteMutation, + containers_Admin_UsersAndRoles_gql_RoleDeleteMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_RoleDeleteDocument as default } +export { containers_Admin_UsersAndRoles_gql_RoleDeleteDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleDelete.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleDelete.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleDelete.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleDelete.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSelection.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleSelection.generated.ts similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSelection.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleSelection.generated.ts diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSelection.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleSelection.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSelection.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleSelection.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSetDefault.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleSetDefault.generated.ts similarity index 87% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSetDefault.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleSetDefault.generated.ts index 78ad026f4d9..ff194d1497a 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSetDefault.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleSetDefault.generated.ts @@ -2,12 +2,12 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' import * as Types from '../../../../model/graphql/types.generated' -export type containers_Admin_RolesAndPolicies_gql_RoleSetDefaultMutationVariables = +export type containers_Admin_UsersAndRoles_gql_RoleSetDefaultMutationVariables = Types.Exact<{ id: Types.Scalars['ID'] }> -export type containers_Admin_RolesAndPolicies_gql_RoleSetDefaultMutation = { +export type containers_Admin_UsersAndRoles_gql_RoleSetDefaultMutation = { readonly __typename: 'Mutation' } & { readonly roleSetDefault: @@ -19,16 +19,13 @@ export type containers_Admin_RolesAndPolicies_gql_RoleSetDefaultMutation = { | { readonly __typename: 'RoleDoesNotExist' } } -export const containers_Admin_RolesAndPolicies_gql_RoleSetDefaultDocument = { +export const containers_Admin_UsersAndRoles_gql_RoleSetDefaultDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'mutation', - name: { - kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_RoleSetDefault', - }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_gql_RoleSetDefault' }, variableDefinitions: [ { kind: 'VariableDefinition', @@ -115,8 +112,8 @@ export const containers_Admin_RolesAndPolicies_gql_RoleSetDefaultDocument = { }, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RoleSetDefaultMutation, - containers_Admin_RolesAndPolicies_gql_RoleSetDefaultMutationVariables + containers_Admin_UsersAndRoles_gql_RoleSetDefaultMutation, + containers_Admin_UsersAndRoles_gql_RoleSetDefaultMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_RoleSetDefaultDocument as default } +export { containers_Admin_UsersAndRoles_gql_RoleSetDefaultDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSetDefault.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleSetDefault.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleSetDefault.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleSetDefault.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateManaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateManaged.generated.ts similarity index 87% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateManaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateManaged.generated.ts index e7b9e3d9d54..dbedafb06c3 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateManaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateManaged.generated.ts @@ -8,13 +8,13 @@ import { RoleSelectionFragmentDoc, } from './RoleSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_RoleUpdateManagedMutationVariables = Types.Exact<{ id: Types.Scalars['ID'] input: Types.ManagedRoleInput }> -export type containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedMutation = { +export type containers_Admin_UsersAndRoles_gql_RoleUpdateManagedMutation = { readonly __typename: 'Mutation' } & { readonly roleUpdate: @@ -33,7 +33,7 @@ export type containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedMutation = { | { readonly __typename: 'RoleHasTooManyPoliciesToAttach' } } -export const containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedDocument = { +export const containers_Admin_UsersAndRoles_gql_RoleUpdateManagedDocument = { kind: 'Document', definitions: [ { @@ -41,7 +41,7 @@ export const containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedDocument = { operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_RoleUpdateManaged', + value: 'containers_Admin_UsersAndRoles_gql_RoleUpdateManaged', }, variableDefinitions: [ { @@ -120,8 +120,8 @@ export const containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedDocument = { ...RoleSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedMutation, - containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedMutationVariables + containers_Admin_UsersAndRoles_gql_RoleUpdateManagedMutation, + containers_Admin_UsersAndRoles_gql_RoleUpdateManagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_RoleUpdateManagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_RoleUpdateManagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateManaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateManaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateManaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateManaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateUnmanaged.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateUnmanaged.generated.ts similarity index 87% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateUnmanaged.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateUnmanaged.generated.ts index b050a7eaba3..7a8c7a65311 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateUnmanaged.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateUnmanaged.generated.ts @@ -8,13 +8,13 @@ import { RoleSelectionFragmentDoc, } from './RoleSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedMutationVariables = +export type containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanagedMutationVariables = Types.Exact<{ id: Types.Scalars['ID'] input: Types.UnmanagedRoleInput }> -export type containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedMutation = { +export type containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanagedMutation = { readonly __typename: 'Mutation' } & { readonly roleUpdate: @@ -33,7 +33,7 @@ export type containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedMutation = | { readonly __typename: 'RoleHasTooManyPoliciesToAttach' } } -export const containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedDocument = { +export const containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanagedDocument = { kind: 'Document', definitions: [ { @@ -41,7 +41,7 @@ export const containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedDocument = operation: 'mutation', name: { kind: 'Name', - value: 'containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanaged', + value: 'containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanaged', }, variableDefinitions: [ { @@ -120,8 +120,8 @@ export const containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedDocument = ...RoleSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedMutation, - containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedMutationVariables + containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanagedMutation, + containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanagedMutationVariables > -export { containers_Admin_RolesAndPolicies_gql_RoleUpdateUnmanagedDocument as default } +export { containers_Admin_UsersAndRoles_gql_RoleUpdateUnmanagedDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateUnmanaged.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateUnmanaged.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/RoleUpdateUnmanaged.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/RoleUpdateUnmanaged.graphql diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/Roles.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/Roles.generated.ts similarity index 84% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/Roles.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/Roles.generated.ts index 33b7bf5b63e..c08e73c7020 100644 --- a/catalog/app/containers/Admin/RolesAndPolicies/gql/Roles.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/Roles.generated.ts @@ -8,11 +8,11 @@ import { RoleSelectionFragmentDoc, } from './RoleSelection.generated' -export type containers_Admin_RolesAndPolicies_gql_RolesQueryVariables = Types.Exact<{ +export type containers_Admin_UsersAndRoles_gql_RolesQueryVariables = Types.Exact<{ [key: string]: never }> -export type containers_Admin_RolesAndPolicies_gql_RolesQuery = { +export type containers_Admin_UsersAndRoles_gql_RolesQuery = { readonly __typename: 'Query' } & { readonly roles: ReadonlyArray< @@ -25,13 +25,13 @@ export type containers_Admin_RolesAndPolicies_gql_RolesQuery = { > } -export const containers_Admin_RolesAndPolicies_gql_RolesDocument = { +export const containers_Admin_UsersAndRoles_gql_RolesDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'query', - name: { kind: 'Name', value: 'containers_Admin_RolesAndPolicies_gql_Roles' }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_gql_Roles' }, selectionSet: { kind: 'SelectionSet', selections: [ @@ -86,8 +86,8 @@ export const containers_Admin_RolesAndPolicies_gql_RolesDocument = { ...RoleSelectionFragmentDoc.definitions, ], } as unknown as DocumentNode< - containers_Admin_RolesAndPolicies_gql_RolesQuery, - containers_Admin_RolesAndPolicies_gql_RolesQueryVariables + containers_Admin_UsersAndRoles_gql_RolesQuery, + containers_Admin_UsersAndRoles_gql_RolesQueryVariables > -export { containers_Admin_RolesAndPolicies_gql_RolesDocument as default } +export { containers_Admin_UsersAndRoles_gql_RolesDocument as default } diff --git a/catalog/app/containers/Admin/RolesAndPolicies/gql/Roles.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/Roles.graphql similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/gql/Roles.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/Roles.graphql diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserCreate.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserCreate.generated.ts new file mode 100644 index 00000000000..e14cbbd7c6f --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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_UsersAndRoles_gql_UserCreateMutationVariables = Types.Exact<{ + input: Types.UserInput +}> + +export type containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserCreateDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserCreateMutation, + containers_Admin_UsersAndRoles_gql_UserCreateMutationVariables +> + +export { containers_Admin_UsersAndRoles_gql_UserCreateDocument as default } diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserCreate.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserCreate.graphql new file mode 100644 index 00000000000..287d9b2f0f6 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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/UsersAndRoles/gql/UserDelete.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserDelete.generated.ts new file mode 100644 index 00000000000..cf196c146ad --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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_UsersAndRoles_gql_UserDeleteMutationVariables = Types.Exact<{ + name: Types.Scalars['String'] +}> + +export type containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserDeleteDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserDeleteMutation, + containers_Admin_UsersAndRoles_gql_UserDeleteMutationVariables +> + +export { containers_Admin_UsersAndRoles_gql_UserDeleteDocument as default } diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserDelete.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserDelete.graphql new file mode 100644 index 00000000000..78d453b9b9f --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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/UsersAndRoles/gql/UserResultSelection.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserResultSelection.generated.ts new file mode 100644 index 00000000000..1833ee2e578 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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/UsersAndRoles/gql/UserResultSelection.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserResultSelection.graphql new file mode 100644 index 00000000000..d1e1da5b755 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserResultSelection.graphql @@ -0,0 +1,21 @@ +# 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/UsersAndRoles/gql/UserSelection.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSelection.generated.ts new file mode 100644 index 00000000000..2c045575818 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSelection.generated.ts @@ -0,0 +1,131 @@ +/* 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) + > + readonly extraRoles: ReadonlyArray< + | ({ 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' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'extraRoles' }, + 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/UsersAndRoles/gql/UserSelection.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSelection.graphql new file mode 100644 index 00000000000..d9151dc96d5 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSelection.graphql @@ -0,0 +1,33 @@ +fragment UserSelection on User { + __typename + name + email + dateJoined + lastLogin + isActive + isAdmin + isSsoOnly + isService + role { + __typename + ... on ManagedRole { + id + name + } + ... on UnmanagedRole { + id + name + } + } + extraRoles { + __typename + ... on ManagedRole { + id + name + } + ... on UnmanagedRole { + id + name + } + } +} diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetActive.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetActive.generated.ts new file mode 100644 index 00000000000..16f0c65a372 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetActive.generated.ts @@ -0,0 +1,137 @@ +/* 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_UsersAndRoles_gql_UserSetActiveMutationVariables = + Types.Exact<{ + name: Types.Scalars['String'] + active: Types.Scalars['Boolean'] + }> + +export type containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetActiveDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetActiveMutation, + containers_Admin_UsersAndRoles_gql_UserSetActiveMutationVariables +> + +export { containers_Admin_UsersAndRoles_gql_UserSetActiveDocument as default } diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetActive.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetActive.graphql new file mode 100644 index 00000000000..0c1089ea8a6 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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/UsersAndRoles/gql/UserSetAdmin.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetAdmin.generated.ts new file mode 100644 index 00000000000..71d6e44ce30 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetAdmin.generated.ts @@ -0,0 +1,137 @@ +/* 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_UsersAndRoles_gql_UserSetAdminMutationVariables = + Types.Exact<{ + name: Types.Scalars['String'] + admin: Types.Scalars['Boolean'] + }> + +export type containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetAdminDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetAdminMutation, + containers_Admin_UsersAndRoles_gql_UserSetAdminMutationVariables +> + +export { containers_Admin_UsersAndRoles_gql_UserSetAdminDocument as default } diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetAdmin.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetAdmin.graphql new file mode 100644 index 00000000000..5f400011f04 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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/UsersAndRoles/gql/UserSetEmail.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetEmail.generated.ts new file mode 100644 index 00000000000..8d46325fe0c --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetEmail.generated.ts @@ -0,0 +1,137 @@ +/* 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_UsersAndRoles_gql_UserSetEmailMutationVariables = + Types.Exact<{ + name: Types.Scalars['String'] + email: Types.Scalars['String'] + }> + +export type containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetEmailDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetEmailMutation, + containers_Admin_UsersAndRoles_gql_UserSetEmailMutationVariables +> + +export { containers_Admin_UsersAndRoles_gql_UserSetEmailDocument as default } diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetEmail.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetEmail.graphql new file mode 100644 index 00000000000..f64a6a3ffdd --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/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/UsersAndRoles/gql/UserSetRole.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetRole.generated.ts new file mode 100644 index 00000000000..2a262dd7f42 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetRole.generated.ts @@ -0,0 +1,160 @@ +/* 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_UsersAndRoles_gql_UserSetRoleMutationVariables = + Types.Exact<{ + name: Types.Scalars['String'] + role: Types.Scalars['String'] + extraRoles: ReadonlyArray + }> + +export type containers_Admin_UsersAndRoles_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_UsersAndRoles_gql_UserSetRoleDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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: 'role' } }, + type: { + kind: 'NonNullType', + type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } }, + }, + }, + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'extraRoles' } }, + type: { + kind: 'NonNullType', + type: { + kind: 'ListType', + 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: 'role' }, + value: { + kind: 'Variable', + name: { kind: 'Name', value: 'role' }, + }, + }, + { + kind: 'Argument', + name: { kind: 'Name', value: 'extraRoles' }, + value: { + kind: 'Variable', + name: { kind: 'Name', value: 'extraRoles' }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'FragmentSpread', + name: { kind: 'Name', value: 'UserResultSelection' }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ...UserResultSelectionFragmentDoc.definitions, + ], +} as unknown as DocumentNode< + containers_Admin_UsersAndRoles_gql_UserSetRoleMutation, + containers_Admin_UsersAndRoles_gql_UserSetRoleMutationVariables +> + +export { containers_Admin_UsersAndRoles_gql_UserSetRoleDocument as default } diff --git a/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetRole.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetRole.graphql new file mode 100644 index 00000000000..3bb2eb35f60 --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/UserSetRole.graphql @@ -0,0 +1,13 @@ +# import UserResultSelection from "./UserResultSelection.graphql" + +mutation ($name: String!, $role: String!, $extraRoles: [String!]!) { + admin { + user { + mutate(name: $name) { + setRole(role: $role, extraRoles: $extraRoles) { + ...UserResultSelection + } + } + } + } +} diff --git a/catalog/app/containers/Admin/Users/gql/Roles.generated.ts b/catalog/app/containers/Admin/UsersAndRoles/gql/Users.generated.ts similarity index 55% rename from catalog/app/containers/Admin/Users/gql/Roles.generated.ts rename to catalog/app/containers/Admin/UsersAndRoles/gql/Users.generated.ts index 69e8efd8687..c8f6dc5bfd8 100644 --- a/catalog/app/containers/Admin/Users/gql/Roles.generated.ts +++ b/catalog/app/containers/Admin/UsersAndRoles/gql/Users.generated.ts @@ -2,11 +2,25 @@ 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_UsersAndRoles_gql_UsersQueryVariables = Types.Exact<{ [key: string]: never }> -export type containers_Admin_Users_gql_RolesQuery = { readonly __typename: 'Query' } & { +export type containers_Admin_UsersAndRoles_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 +29,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_UsersAndRoles_gql_UsersDocument = { kind: 'Document', definitions: [ { kind: 'OperationDefinition', operation: 'query', - name: { kind: 'Name', value: 'containers_Admin_Users_gql_Roles' }, + name: { kind: 'Name', value: 'containers_Admin_UsersAndRoles_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 +129,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 +143,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 +155,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_UsersAndRoles_gql_UsersQuery, + containers_Admin_UsersAndRoles_gql_UsersQueryVariables > -export { containers_Admin_Users_gql_RolesDocument as default } +export { containers_Admin_UsersAndRoles_gql_UsersDocument as default } diff --git a/catalog/app/containers/Admin/Users/gql/Roles.graphql b/catalog/app/containers/Admin/UsersAndRoles/gql/Users.graphql similarity index 59% rename from catalog/app/containers/Admin/Users/gql/Roles.graphql rename to catalog/app/containers/Admin/UsersAndRoles/gql/Users.graphql index 72109c76ff5..1d87f58c480 100644 --- a/catalog/app/containers/Admin/Users/gql/Roles.graphql +++ b/catalog/app/containers/Admin/UsersAndRoles/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/index.tsx b/catalog/app/containers/Admin/UsersAndRoles/index.tsx new file mode 100644 index 00000000000..a91d5ea6dbe --- /dev/null +++ b/catalog/app/containers/Admin/UsersAndRoles/index.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' +import * as M from '@material-ui/core' + +import MetaTitle from 'utils/MetaTitle' + +import Policies from './Policies' +import Roles from './Roles' +import Users from './Users' +import SuspenseWrapper from './SuspenseWrapper' + +export default function UsersAndRoles() { + return ( + <> + {['Users, Roles and Policies', 'Admin']} + + + + + + + + + + + + + + + + + + ) +} diff --git a/catalog/app/containers/Admin/RolesAndPolicies/shared.ts b/catalog/app/containers/Admin/UsersAndRoles/shared.ts similarity index 100% rename from catalog/app/containers/Admin/RolesAndPolicies/shared.ts rename to catalog/app/containers/Admin/UsersAndRoles/shared.ts 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 diff --git a/catalog/app/containers/NavBar/NavBar.tsx b/catalog/app/containers/NavBar/NavBar.tsx index cd36a641d77..6f8d640b1b9 100644 --- a/catalog/app/containers/NavBar/NavBar.tsx +++ b/catalog/app/containers/NavBar/NavBar.tsx @@ -1,25 +1,20 @@ -import * as R from 'ramda' import * as React from 'react' import * as redux from 'react-redux' -import { Link, useRouteMatch } from 'react-router-dom' -import { createStructuredSelector } from 'reselect' -import { sanitizeUrl } from '@braintree/sanitize-url' +import { Link } from 'react-router-dom' import * as M from '@material-ui/core' -import * as Intercom from 'components/Intercom' import Logo from 'components/Logo' -import * as Bookmarks from 'containers/Bookmarks' import cfg from 'constants/config' import * as style from 'constants/style' import * as URLS from 'constants/urls' import * as authSelectors from 'containers/Auth/selectors' import * as CatalogSettings from 'utils/CatalogSettings' -import HashLink from 'utils/HashLink' import * as NamedRoutes from 'utils/NamedRoutes' import bg from './bg.png' import Controls from './Controls' +import * as NavMenu from './NavMenu' import * as Subscription from './Subscription' const useLogoLinkStyles = M.makeStyles((t) => ({ @@ -81,310 +76,6 @@ function QuiltLink({ className }: QuiltLinkProps) { ) } -const useItemStyles = M.makeStyles({ - root: { - display: 'inline-flex', - maxWidth: '400px', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, -}) - -// type ItemProps = (LinkProps | { href: string }) & M.MenuItemProps -interface ItemProps extends M.MenuItemProps { - to?: string - href?: string -} - -// FIXME: doesn't compile with Ref -// const Item = React.forwardRef((props: ItemProps, ref: React.Ref) => ( -const Item = React.forwardRef( - ({ children, ...props }: ItemProps, ref: React.Ref) => { - const classes = useItemStyles() - return ( - - {children} - - ) - }, -) - -const selectUser = createStructuredSelector({ - name: authSelectors.username, - isAdmin: authSelectors.isAdmin, -}) - -const userDisplay = (user: $TSFixMe) => ( - <> - {user.isAdmin && ( - <> - security -   - - )} - {user.name} - -) - -const useBadgeStyles = M.makeStyles({ - root: { - alignItems: 'inherit', - }, - badge: { - top: '4px', - }, -}) - -interface BadgeProps extends M.BadgeProps {} - -function Badge({ children, color, invisible, ...props }: BadgeProps) { - const classes = useBadgeStyles() - return ( - - {children} - - ) -} - -function UserDropdown() { - const user = redux.useSelector(selectUser) - const { urls, paths } = NamedRoutes.use() - const bookmarks = Bookmarks.use() - const isProfile = !!useRouteMatch({ path: paths.profile, exact: true }) - const isAdmin = !!useRouteMatch(paths.admin) - const [anchor, setAnchor] = React.useState(null) - const [visible, setVisible] = React.useState(true) - - const open = React.useCallback( - (evt) => { - setAnchor(evt.target) - }, - [setAnchor], - ) - - const close = React.useCallback(() => { - setVisible(false) - setAnchor(null) - }, [setAnchor]) - - const showBookmarks = React.useCallback(() => { - if (!bookmarks) return - bookmarks.show() - close() - }, [bookmarks, close]) - - React.useEffect(() => { - const hasUpdates = bookmarks?.hasUpdates || false - if (hasUpdates !== visible) setVisible(!!hasUpdates) - }, [bookmarks, visible]) - - return ( - <> - - - {userDisplay(user)} - {' '} - expand_more - - - - - {bookmarks && ( - - - bookmarks_outlined - -  Bookmarks - - )} - {user.isAdmin && ( - - security Admin settings - - )} - {cfg.mode === 'OPEN' && ( - - Profile - - )} - - Sign Out - - - - - ) -} - -function useHam() { - const [anchor, setAnchor] = React.useState(null) - - const open = React.useCallback( - (evt) => { - setAnchor(evt.target) - }, - [setAnchor], - ) - - const close = React.useCallback(() => { - setAnchor(null) - }, [setAnchor]) - - const render = (children: React.ReactNode) => ( - <> - - menu - - - - {children} - - - - ) - - return { open, close, render } -} - -interface AuthHamburgerProps { - authenticated: boolean - waiting: boolean - error: boolean -} - -function AuthHamburger({ authenticated, waiting, error }: AuthHamburgerProps) { - const user = redux.useSelector(selectUser) - const { urls, paths } = NamedRoutes.use() - const isProfile = !!useRouteMatch({ path: paths.profile, exact: true }) - const isAdmin = !!useRouteMatch(paths.admin) - const ham = useHam() - const links = useLinks() - return ham.render([ - ...// eslint-disable-next-line no-nested-ternary - (authenticated - ? [ - - {userDisplay(user)} - , - user.isAdmin && ( - - - security -  Admin settings - - ), - cfg.mode === 'OPEN' && ( - - - Profile - - ), - - - Sign Out - , - ] - : waiting - ? [ - - - , - ] - : [ - - {error && ( - <> - error_outline{' '} - - )} - Sign In - , - ]), - , - ...links.map(({ label, ...rest }) => ( - - {label} - - )), - ]) -} - -function LinksHamburger() { - const ham = useHam() - return ham.render( - useLinks().map(({ label, ...rest }) => ( - - {label} - - )), - ) -} - -const useSignInStyles = M.makeStyles((t) => ({ - icon: { - marginRight: t.spacing(1), - }, -})) - -interface AuthError { - message: string -} - -interface SignInProps { - error?: AuthError - waiting: boolean -} - -function SignIn({ error, waiting }: SignInProps) { - const classes = useSignInStyles() - const { urls } = NamedRoutes.use() - if (waiting) { - return - } - return ( - <> - {error && ( - - error_outline - - )} - - Sign In - - - ) -} - const useAppBarStyles = M.makeStyles((t) => ({ root: { zIndex: t.zIndex.appBar + 1, @@ -486,176 +177,64 @@ export function Container({ children }: ContainerProps) { ) } -interface NavLinkOwnProps { - to?: string - path?: string -} - -type NavLinkProps = NavLinkOwnProps & M.BoxProps +const useLicenseErrorStyles = M.makeStyles((t) => ({ + licenseError: { + color: t.palette.error.light, + marginRight: t.spacing(0.5), -const NavLink = React.forwardRef((props: NavLinkProps, ref: React.Ref) => { - const isActive = !!useRouteMatch({ path: props.path, exact: true }) - return ( - 10 - ? props.children - : undefined - } - {...props} - ref={ref} - /> - ) -}) + [t.breakpoints.down('sm')]: { + marginLeft: t.spacing(1.5), + marginRight: 0, + }, + }, +})) -interface LinkDescriptor { - label: string - to?: string - href?: string - target?: '_blank' +interface LicenseErrorProps { + restore: () => void } -function useLinks(): LinkDescriptor[] { - const { paths, urls } = NamedRoutes.use() - const settings = CatalogSettings.use() - const customNavLink: LinkDescriptor | null = React.useMemo(() => { - if (!settings?.customNavLink) return null - const href = sanitizeUrl(settings.customNavLink.url) - if (href === 'about:blank') return null - return { - href, - label: settings.customNavLink.label, - target: '_blank', - } - }, [settings?.customNavLink]) - - return [ - process.env.NODE_ENV === 'development' && { - to: urls.example(), - label: 'Example', - }, - customNavLink, - cfg.mode !== 'MARKETING' && { - to: urls.uriResolver(), - label: 'URI', - path: paths.uriResolver, - }, - { href: URLS.docs, label: 'Docs' }, - cfg.mode === 'MARKETING' && { to: `${urls.home()}#pricing`, label: 'Pricing' }, - (cfg.mode === 'MARKETING' || cfg.mode === 'OPEN') && { - href: URLS.jobs, - label: 'Jobs', - }, - cfg.mode !== 'PRODUCT' && { href: URLS.blog, label: 'Blog' }, - cfg.mode === 'MARKETING' && { to: urls.about(), label: 'About' }, - ].filter(Boolean) as LinkDescriptor[] +function LicenseError({ restore }: LicenseErrorProps) { + const classes = useLicenseErrorStyles() + return ( + + + error_outline + + + ) } -const selector = createStructuredSelector( - R.pick(['error', 'waiting', 'authenticated'], authSelectors), -) - -const useNavBarStyles = M.makeStyles((t) => ({ - nav: { - alignItems: 'center', - display: 'flex', - marginLeft: t.spacing(3), - marginRight: t.spacing(2), - }, - navItem: { - '& + &': { - marginLeft: t.spacing(2), - }, - }, +const useNavBarStyles = M.makeStyles({ quiltLogo: { margin: '0 0 3px 8px', }, spacer: { flexGrow: 1, }, - licenseError: { - color: t.palette.error.light, - marginRight: t.spacing(0.5), - - [t.breakpoints.down('sm')]: { - marginLeft: t.spacing(1.5), - marginRight: 0, - }, - }, -})) +}) export function NavBar() { - const settings = CatalogSettings.use() - const { paths } = NamedRoutes.use() - const isSignIn = !!useRouteMatch({ path: paths.signIn, exact: true }) - const { error, waiting, authenticated } = redux.useSelector(selector) - const t = M.useTheme() - const useHamburger = M.useMediaQuery(t.breakpoints.down('sm')) - const links = useLinks() - const intercom = Intercom.use() const classes = useNavBarStyles() + const t = M.useTheme() + const collapse = M.useMediaQuery(t.breakpoints.down('sm')) + + const settings = CatalogSettings.use() const sub = Subscription.useState() + const authenticated = redux.useSelector(authSelectors.authenticated) + + const hideControls = cfg.disableNavigator || (cfg.alwaysRequiresAuth && !authenticated) + return ( - - - {cfg.disableNavigator || (cfg.alwaysRequiresAuth && isSignIn) ? ( -
- ) : ( - - )} + {hideControls ?
: } - {!useHamburger && ( - - )} + - {sub.invalid && ( - - - error_outline - - - )} + {!collapse && } - {!cfg.disableNavigator && - cfg.mode !== 'LOCAL' && - !useHamburger && - (authenticated ? ( - - ) : ( - !isSignIn && - ))} + {sub.invalid && } - {useHamburger && - (cfg.disableNavigator || cfg.mode === 'LOCAL' ? ( - - ) : ( - - ))} + {settings?.logo?.url && } diff --git a/catalog/app/containers/NavBar/NavMenu.tsx b/catalog/app/containers/NavBar/NavMenu.tsx new file mode 100644 index 00000000000..0609e93cb6c --- /dev/null +++ b/catalog/app/containers/NavBar/NavMenu.tsx @@ -0,0 +1,562 @@ +import * as R from 'ramda' +import * as React from 'react' +import * as redux from 'react-redux' +import { Link, useRouteMatch } from 'react-router-dom' +import * as RR from 'react-router-dom' +import { createStructuredSelector } from 'reselect' +import { sanitizeUrl } from '@braintree/sanitize-url' +import * as M from '@material-ui/core' + +import * as Intercom from 'components/Intercom' +import cfg from 'constants/config' +import * as style from 'constants/style' +import * as URLS from 'constants/urls' +import * as Bookmarks from 'containers/Bookmarks' +import * as authSelectors from 'containers/Auth/selectors' +import * as CatalogSettings from 'utils/CatalogSettings' +import * as GQL from 'utils/GraphQL' +import * as NamedRoutes from 'utils/NamedRoutes' +import assertNever from 'utils/assertNever' +import * as tagged from 'utils/taggedV2' + +import useRoleSwitcher from './RoleSwitcher' + +import ME_QUERY from './gql/Me.generated' + +type MaybeMe = GQL.DataForDoc['me'] +type Me = NonNullable + +const AuthState = tagged.create('app/containers/NavBar/NavMenu:AuthState' as const, { + Loading: () => {}, + Error: (error: Error) => ({ error }), + Ready: (user: MaybeMe) => ({ user }), +}) + +// eslint-disable-next-line @typescript-eslint/no-redeclare +type AuthState = tagged.InstanceOf + +const authSelector = createStructuredSelector( + R.pick(['error', 'waiting', 'authenticated'], authSelectors), +) + +function useAuthState(): AuthState { + const { error, waiting, authenticated } = redux.useSelector(authSelector) + const meQuery = GQL.useQuery(ME_QUERY, {}, { pause: waiting || !authenticated }) + if (error) return AuthState.Error(error) + if (waiting) return AuthState.Loading() + if (!authenticated) return AuthState.Ready(null) + return GQL.fold(meQuery, { + data: (d) => + d.me + ? AuthState.Ready(d.me) + : AuthState.Error(new Error("Couldn't load user data")), + fetching: () => AuthState.Loading(), + error: (err) => AuthState.Error(err), + }) +} + +const ItemDescriptor = tagged.create( + 'app/containers/NavBar/NavMenu:ItemDescriptor' as const, + { + To: (to: string, children: React.ReactNode) => ({ to, children }), + Href: (href: string, children: React.ReactNode) => ({ href, children }), + Click: (onClick: () => void, children: React.ReactNode) => ({ onClick, children }), + Text: (children: React.ReactNode) => ({ children }), + Divider: () => {}, + }, +) + +// eslint-disable-next-line @typescript-eslint/no-redeclare +type ItemDescriptor = tagged.InstanceOf + +const useDropdownMenuStyles = M.makeStyles((t) => ({ + divider: { + marginBottom: t.spacing(1), + marginTop: t.spacing(1), + }, +})) + +interface DropdownMenuProps + extends Omit< + M.MenuProps, + 'anchorEl' | 'open' | 'onClose' | 'children' | 'MenuListProps' + > { + trigger: ( + open: React.EventHandler>, + ) => React.ReactNode + onClose?: () => void + items: (ItemDescriptor | false)[] +} + +function DropdownMenu({ trigger, items, onClose, ...rest }: DropdownMenuProps) { + const classes = useDropdownMenuStyles() + + const [anchor, setAnchor] = React.useState(null) + + const open = React.useCallback( + (evt: React.SyntheticEvent) => { + setAnchor(evt.currentTarget) + }, + [setAnchor], + ) + + const close = React.useCallback(() => { + setAnchor(null) + if (onClose) onClose() + }, [setAnchor, onClose]) + + const filtered = items.filter(Boolean) as ItemDescriptor[] + const children = filtered.map( + ItemDescriptor.match({ + To: (props, i) => ( + + {({ match }) => ( + + )} + + ), + Href: (props, i) => ( + + ), + Click: ({ onClick, ...props }, i) => ( + { + onClick() + close() + }} + {...props} + /> + ), + Text: (props, i) => ( + + ), + Divider: (_, i) => , + }), + ) + + return ( + <> + {trigger(open)} + + + {children} + + + + ) +} + +const mkNavItem = ( + kind: 'to' | 'href', + target: string, + label: string, + icon: string = 'open_in_new', +) => ({ kind, target, label, icon }) + +type NavItem = ReturnType + +function useNavItems(): NavItem[] { + const { urls } = NamedRoutes.use() + const settings = CatalogSettings.use() + + const customNavLink: NavItem | false = React.useMemo(() => { + if (!settings?.customNavLink) return false + const href = sanitizeUrl(settings.customNavLink.url) + if (href === 'about:blank') return false + return mkNavItem('href', href, settings.customNavLink.label) + }, [settings?.customNavLink]) + + return [ + process.env.NODE_ENV === 'development' && + mkNavItem('to', urls.example(), 'Example', 'account_tree'), + customNavLink, + cfg.mode !== 'MARKETING' && mkNavItem('to', urls.uriResolver(), 'URI', 'public'), + mkNavItem('href', URLS.docs, 'Docs', 'menu_book'), + (cfg.mode === 'MARKETING' || cfg.mode === 'OPEN') && + mkNavItem('href', URLS.jobs, 'Jobs', 'work'), + cfg.mode !== 'PRODUCT' && mkNavItem('href', URLS.blog, 'Blog', 'chat'), + cfg.mode === 'MARKETING' && mkNavItem('to', urls.about(), 'About', 'help'), + ].filter(Boolean) as NavItem[] +} + +const useItemStyles = M.makeStyles((t) => ({ + icon: { + minWidth: '36px', + }, + text: { + paddingRight: '8px', + }, + secondary: { + fontSize: t.typography.body2.fontSize, + fontWeight: 'lighter', + opacity: 0.6, + + '&::before': { + content: '" – "', + }, + }, +})) + +interface ItemContentsProps { + icon?: React.ReactNode + primary: React.ReactNode + secondary?: React.ReactNode +} + +function ItemContents({ icon, primary, secondary }: ItemContentsProps) { + const classes = useItemStyles() + const iconEl = typeof icon === 'string' ? {icon} : icon + return ( + <> + {!!iconEl && {iconEl}} + + {primary} + {!!secondary && {secondary}} + + } + className={classes.text} + /> + + ) +} + +function useGetAuthItems() { + const { urls } = NamedRoutes.use() + const switchRole = useRoleSwitcher() + const bookmarks = Bookmarks.use() + + return function getAuthLinks(user: Me) { + const items: ItemDescriptor[] = [] + + const userItem = ( + + ) + items.push( + cfg.mode === 'OPEN' // currently only OPEN has profile page + ? ItemDescriptor.To(urls.profile(), userItem) + : ItemDescriptor.Text(userItem), + ) + + if (user.roles.length > 1) { + items.push( + ItemDescriptor.Click( + () => switchRole(user), + {user.roles.length} available} + />, + ), + ) + } + + items.push(ItemDescriptor.Divider()) + + if (bookmarks) { + items.push( + ItemDescriptor.Click( + bookmarks.show, + + bookmarks_outline + + } + primary="Bookmarks" + />, + ), + ) + } + + if (user.isAdmin) { + items.push( + ItemDescriptor.To( + urls.admin(), + , + ), + ) + } + + items.push( + ItemDescriptor.To( + urls.signOut(), + , + ), + ) + + return items + } +} + +interface DesktopUserDropdownProps { + user: Me +} + +function DesktopUserDropdown({ user }: DesktopUserDropdownProps) { + const bookmarks = Bookmarks.use() + const getAuthItems = useGetAuthItems() + + return ( + ( + + + account_circle + +    + {user.name} +   + expand_more + + )} + items={getAuthItems(user)} + /> + ) +} + +const useDesktopSignInStyles = M.makeStyles((t) => ({ + icon: { + marginRight: t.spacing(1), + }, +})) + +interface DesktopSignInProps { + error?: Error +} + +function DesktopSignIn({ error }: DesktopSignInProps) { + const classes = useDesktopSignInStyles() + const { urls } = NamedRoutes.use() + return ( + <> + {!!error && ( + + error_outline + + )} + + Sign In + + + ) +} + +const useDesktopProgressStyles = M.makeStyles((t) => ({ + progress: { + marginLeft: t.spacing(1), + }, +})) + +function DesktopProgress() { + const classes = useDesktopProgressStyles() + return +} + +interface DesktopMenuProps { + auth: AuthState +} + +function DesktopMenu({ auth }: DesktopMenuProps) { + const { paths } = NamedRoutes.use() + const isSignIn = !!useRouteMatch({ path: paths.signIn, exact: true }) + if (isSignIn || cfg.disableNavigator || cfg.mode === 'LOCAL') return null + return AuthState.match( + { + Ready: ({ user }) => + user ? : , + Error: ({ error }) => , + Loading: () => , + }, + auth, + ) +} + +interface MobileMenuProps { + auth: AuthState +} + +function MobileMenu({ auth }: MobileMenuProps) { + const { urls } = NamedRoutes.use() + const navItems = useNavItems() + const getAuthItems = useGetAuthItems() + const bookmarks = Bookmarks.use() + + const authItems = React.useMemo(() => { + if (cfg.disableNavigator || cfg.mode === 'LOCAL') return [] + return AuthState.match<(ItemDescriptor | false)[]>( + { + Loading: () => [ + ItemDescriptor.Text( + } primary="Loading..." />, + ), + ], + Error: () => [ + ItemDescriptor.To( + urls.signIn(), + , + ), + ], + Ready: ({ user }) => + user + ? getAuthItems(user) + : [ + ItemDescriptor.To( + urls.signIn(), + , + ), + ], + }, + auth, + ).concat(ItemDescriptor.Divider()) + }, [auth, getAuthItems, urls]) + + const links = React.useMemo( + () => + navItems.map((n) => { + const children = + switch (n.kind) { + case 'to': + return ItemDescriptor.To(n.target, children) + case 'href': + return ItemDescriptor.Href(n.target, children) + default: + assertNever(n.kind) + } + }), + [navItems], + ) + + return ( + ( + + + menu + + + )} + items={[...authItems, ...links]} + /> + ) +} + +const useNavStyles = M.makeStyles((t) => ({ + nav: { + alignItems: 'center', + display: 'flex', + marginLeft: t.spacing(3), + marginRight: t.spacing(2), + }, + active: {}, + link: { + color: t.palette.text.secondary, + fontSize: t.typography.body2.fontSize, + maxWidth: '64px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + + '& + &': { + marginLeft: t.spacing(2), + }, + + '&$active': { + color: t.palette.text.disabled, + }, + }, + intercom: { + marginLeft: t.spacing(2), + }, +})) + +export function Links() { + const classes = useNavStyles() + const intercom = Intercom.use() + const navItems = useNavItems() + + const mkTitle = (label: string) => (label.length > 10 ? label : undefined) + + const links = navItems.map((n, i) => { + switch (n.kind) { + case 'to': + return ( + + {n.label} + + ) + case 'href': + return ( + + {n.label} + + ) + default: + assertNever(n.kind) + } + }) + + return ( + + ) +} + +interface MenuProps { + collapse: boolean +} + +export function Menu({ collapse }: MenuProps) { + const auth = useAuthState() + return collapse ? : +} diff --git a/catalog/app/containers/NavBar/RoleSwitcher.tsx b/catalog/app/containers/NavBar/RoleSwitcher.tsx new file mode 100644 index 00000000000..0689fd340f4 --- /dev/null +++ b/catalog/app/containers/NavBar/RoleSwitcher.tsx @@ -0,0 +1,183 @@ +import * as FF from 'final-form' +import * as React from 'react' +import * as RF from 'react-final-form' +import * as M from '@material-ui/core' +import * as Lab from '@material-ui/lab' +import * as Sentry from '@sentry/react' + +import * as Dialogs from 'utils/GlobalDialogs' +import * as GQL from 'utils/GraphQL' +import assertNever from 'utils/assertNever' + +import ME_QUERY from './gql/Me.generated' +import SWITCH_ROLE_MUTATION from './gql/SwitchRole.generated' + +type Me = NonNullable['me']> + +const useRoleSwitcherStyles = M.makeStyles((t) => ({ + content: { + position: 'relative', + }, + error: { + borderRadius: 0, + }, + errorIcon: { + fontSize: '24px', + marginLeft: '9px', + marginRight: '23px', + }, + progress: { + alignItems: 'center', + background: 'rgba(255, 255, 255, 0.5)', + bottom: 0, + display: 'flex', + justifyContent: 'center', + left: 0, + position: 'absolute', + right: 0, + top: 0, + }, + name: { + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + hint: { + color: t.palette.text.hint, + fontWeight: t.typography.fontWeightLight, + }, +})) + +interface RoleSwitcherProps { + user: Me + close: Dialogs.Close +} + +function RoleSwitcher({ user, close }: RoleSwitcherProps) { + const switchRole = GQL.useMutation(SWITCH_ROLE_MUTATION) + const classes = useRoleSwitcherStyles() + + interface FormValues { + role: string + } + + const onSubmit = React.useCallback( + async ({ role }: FormValues) => { + if (role === user.role.name) { + close() + return new Promise(() => {}) // don't unlock the form controls + } + + try { + const { switchRole: r } = await switchRole({ roleName: role }) + switch (r.__typename) { + case 'Me': + window.location.reload() + return await new Promise(() => {}) // don't unlock the form controls + case 'InvalidInput': + const [e] = r.errors + throw new Error(`InputError (${e.name}) at '${e.path}': ${e.message}`) + case 'OperationError': + throw new Error(`OperationError (${r.name}): ${r.message}`) + default: + assertNever(r) + } + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error switching role', err) + Sentry.captureException(err) + const message = err instanceof Error ? err.message : `${err}` + return { [FF.FORM_ERROR]: message } + } + }, + [close, switchRole, user.role.name], + ) + return ( + initialValues={{ role: user.role.name }} onSubmit={onSubmit}> + {({ handleSubmit, submitting, submitError }) => ( + <> + Switch role + +
+ + + Could not switch role + Try again or contact support. +
+ Error details: +
+ {submitError} +
+
+
+ +
+ name="role"> + {(props) => ( + + {user.roles.map((role) => ( + props.input.onChange(role.name)} + selected={role.name === props.input.value} + > + + + + + {role.name} + {role.name === user.role.name && ( +  (current) + )} + + + ))} + + )} + + {submitting && ( +
+ +
+ )} +
+ + + + Cancel + + + Switch + + + + )} + + ) +} + +const SWITCH_ROLES_DIALOG_PROPS = { + maxWidth: 'sm' as const, + fullWidth: true, +} + +export default function useRoleSwitcher() { + const openDialog = Dialogs.use() + return (user: Me) => + openDialog( + ({ close }) => , + SWITCH_ROLES_DIALOG_PROPS, + ) +} diff --git a/catalog/app/containers/NavBar/gql/Me.generated.ts b/catalog/app/containers/NavBar/gql/Me.generated.ts new file mode 100644 index 00000000000..33bce341948 --- /dev/null +++ b/catalog/app/containers/NavBar/gql/Me.generated.ts @@ -0,0 +1,69 @@ +/* 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_NavBar_gql_MeQueryVariables = Types.Exact<{ [key: string]: never }> + +export type containers_NavBar_gql_MeQuery = { readonly __typename: 'Query' } & { + readonly me: Types.Maybe< + { readonly __typename: 'Me' } & Pick & { + readonly role: { readonly __typename: 'MyRole' } & Pick + readonly roles: ReadonlyArray< + { readonly __typename: 'MyRole' } & Pick + > + } + > +} + +export const containers_NavBar_gql_MeDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: { kind: 'Name', value: 'containers_NavBar_gql_Me' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'me' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'email' } }, + { kind: 'Field', name: { kind: 'Name', value: 'isAdmin' } }, + { + kind: 'Field', + name: { kind: 'Name', value: 'role' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'roles' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode< + containers_NavBar_gql_MeQuery, + containers_NavBar_gql_MeQueryVariables +> + +export { containers_NavBar_gql_MeDocument as default } diff --git a/catalog/app/containers/NavBar/gql/Me.graphql b/catalog/app/containers/NavBar/gql/Me.graphql new file mode 100644 index 00000000000..a65a579108a --- /dev/null +++ b/catalog/app/containers/NavBar/gql/Me.graphql @@ -0,0 +1,13 @@ +query { + me { + name + email + isAdmin + role { + name + } + roles { + name + } + } +} diff --git a/catalog/app/containers/NavBar/gql/SwitchRole.generated.ts b/catalog/app/containers/NavBar/gql/SwitchRole.generated.ts new file mode 100644 index 00000000000..43e48592422 --- /dev/null +++ b/catalog/app/containers/NavBar/gql/SwitchRole.generated.ts @@ -0,0 +1,112 @@ +/* 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_NavBar_gql_SwitchRoleMutationVariables = Types.Exact<{ + roleName: Types.Scalars['String'] +}> + +export type containers_NavBar_gql_SwitchRoleMutation = { + readonly __typename: 'Mutation' +} & { + readonly switchRole: + | { readonly __typename: 'Me' } + | ({ readonly __typename: 'InvalidInput' } & { + readonly errors: ReadonlyArray< + { readonly __typename: 'InputError' } & Pick< + Types.InputError, + 'name' | 'path' | 'message' + > + > + }) + | ({ readonly __typename: 'OperationError' } & Pick< + Types.OperationError, + 'message' | 'name' + >) +} + +export const containers_NavBar_gql_SwitchRoleDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'containers_NavBar_gql_SwitchRole' }, + variableDefinitions: [ + { + 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: 'switchRole' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'roleName' }, + value: { kind: 'Variable', name: { kind: 'Name', value: 'roleName' } }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: '__typename' } }, + { + 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: '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: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'path' } }, + { kind: 'Field', name: { kind: 'Name', value: 'message' } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode< + containers_NavBar_gql_SwitchRoleMutation, + containers_NavBar_gql_SwitchRoleMutationVariables +> + +export { containers_NavBar_gql_SwitchRoleDocument as default } diff --git a/catalog/app/containers/NavBar/gql/SwitchRole.graphql b/catalog/app/containers/NavBar/gql/SwitchRole.graphql new file mode 100644 index 00000000000..eaf35baae02 --- /dev/null +++ b/catalog/app/containers/NavBar/gql/SwitchRole.graphql @@ -0,0 +1,16 @@ +mutation ($roleName: String!) { + switchRole(roleName: $roleName) { + __typename + ... on OperationError { + message + name + } + ... on InvalidInput { + errors { + name + path + message + } + } + } +} diff --git a/catalog/app/model/graphql/schema.generated.ts b/catalog/app/model/graphql/schema.generated.ts index daad77b1026..6030fd04aad 100644 --- a/catalog/app/model/graphql/schema.generated.ts +++ b/catalog/app/model/graphql/schema.generated.ts @@ -82,6 +82,44 @@ export default { ], interfaces: [], }, + { + kind: 'OBJECT', + name: 'AdminMutations', + fields: [ + { + name: 'user', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'OBJECT', + name: 'UserAdminMutations', + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, + { + kind: 'OBJECT', + name: 'AdminQueries', + fields: [ + { + name: 'user', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'OBJECT', + name: 'UserAdminQueries', + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, { kind: 'OBJECT', name: 'BooleanPackageUserMetaFacet', @@ -1313,87 +1351,130 @@ export default { }, { kind: 'OBJECT', - name: 'Mutation', + name: 'Me', fields: [ { - name: 'packageConstruct', + name: 'name', type: { kind: 'NON_NULL', ofType: { - kind: 'UNION', - name: 'PackageConstructResult', + kind: 'SCALAR', + name: 'String', ofType: null, }, }, - args: [ - { - name: 'params', - type: { - kind: 'NON_NULL', - ofType: { - kind: 'SCALAR', - name: 'Any', - }, - }, + args: [], + }, + { + name: 'email', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, }, - { - name: 'src', - type: { + }, + args: [], + }, + { + name: 'isAdmin', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + }, + args: [], + }, + { + name: 'role', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'OBJECT', + name: 'MyRole', + ofType: null, + }, + }, + args: [], + }, + { + name: 'roles', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'LIST', + ofType: { kind: 'NON_NULL', ofType: { - kind: 'SCALAR', - name: 'Any', + kind: 'OBJECT', + name: 'MyRole', + ofType: null, }, }, }, - ], + }, + args: [], }, + ], + interfaces: [], + }, + { + kind: 'OBJECT', + name: 'MutateUserAdminMutations', + fields: [ { - name: 'packagePromote', + name: 'delete', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PackagePromoteResult', + name: 'OperationResult', ofType: null, }, }, - args: [ - { - name: 'params', - type: { - kind: 'NON_NULL', - ofType: { - kind: 'SCALAR', - name: 'Any', - }, - }, + args: [], + }, + { + name: 'setEmail', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'UserResult', + ofType: null, }, + }, + args: [ { - name: 'src', + name: 'email', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'Any', + name: 'String', + ofType: null, }, }, }, ], }, { - name: 'packageRevisionDelete', + name: 'setRole', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PackageRevisionDeleteResult', + name: 'UserResult', ofType: null, }, }, args: [ { - name: 'bucket', + name: 'role', type: { kind: 'NON_NULL', ofType: { @@ -1404,23 +1485,26 @@ export default { }, }, { - name: 'name', + name: 'extraRoles', type: { - kind: 'NON_NULL', + kind: 'LIST', ofType: { - kind: 'SCALAR', - name: 'String', - ofType: null, + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, }, }, }, { - name: 'hash', + name: 'append', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'String', + name: 'Boolean', ofType: null, }, }, @@ -1428,80 +1512,91 @@ export default { ], }, { - name: 'bucketAdd', + name: 'addRoles', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'BucketAddResult', + name: 'UserResult', ofType: null, }, }, args: [ { - name: 'input', + name: 'roles', type: { kind: 'NON_NULL', ofType: { - kind: 'SCALAR', - name: 'Any', + kind: 'LIST', + ofType: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, }, }, }, ], }, { - name: 'bucketUpdate', + name: 'removeRoles', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'BucketUpdateResult', + name: 'UserResult', ofType: null, }, }, args: [ { - name: 'name', + name: 'roles', type: { kind: 'NON_NULL', ofType: { - kind: 'SCALAR', - name: 'String', - ofType: null, + kind: 'LIST', + ofType: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, }, }, }, { - name: 'input', + name: 'fallback', type: { - kind: 'NON_NULL', - ofType: { - kind: 'SCALAR', - name: 'Any', - }, + kind: 'SCALAR', + name: 'String', + ofType: null, }, }, ], }, { - name: 'bucketRemove', + name: 'setAdmin', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'BucketRemoveResult', + name: 'UserResult', ofType: null, }, }, args: [ { - name: 'name', + name: 'admin', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'String', + name: 'Boolean', ofType: null, }, }, @@ -1509,75 +1604,95 @@ export default { ], }, { - name: 'policyCreateManaged', + name: 'setActive', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PolicyResult', + name: 'UserResult', ofType: null, }, }, args: [ { - name: 'input', + name: 'active', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'Any', + name: 'Boolean', + ofType: null, }, }, }, ], }, { - name: 'policyCreateUnmanaged', + name: 'resetPassword', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PolicyResult', + name: 'OperationResult', + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, + { + kind: 'OBJECT', + name: 'Mutation', + fields: [ + { + name: 'switchRole', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'SwitchRoleResult', ofType: null, }, }, args: [ { - name: 'input', + name: 'roleName', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'Any', + name: 'String', + ofType: null, }, }, }, ], }, { - name: 'policyUpdateManaged', + name: 'packageConstruct', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PolicyResult', + name: 'PackageConstructResult', ofType: null, }, }, args: [ { - name: 'id', + name: 'params', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'ID', - ofType: null, + name: 'Any', }, }, }, { - name: 'input', + name: 'src', type: { kind: 'NON_NULL', ofType: { @@ -1589,29 +1704,28 @@ export default { ], }, { - name: 'policyUpdateUnmanaged', + name: 'packagePromote', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PolicyResult', + name: 'PackagePromoteResult', ofType: null, }, }, args: [ { - name: 'id', + name: 'params', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'ID', - ofType: null, + name: 'Any', }, }, }, { - name: 'input', + name: 'src', type: { kind: 'NON_NULL', ofType: { @@ -1623,52 +1737,305 @@ export default { ], }, { - name: 'policyDelete', + name: 'packageRevisionDelete', type: { kind: 'NON_NULL', ofType: { kind: 'UNION', - name: 'PolicyDeleteResult', + name: 'PackageRevisionDeleteResult', ofType: null, }, }, args: [ { - name: 'id', + name: 'bucket', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'ID', + name: 'String', ofType: null, }, }, }, - ], - }, - { - name: 'roleCreateManaged', - type: { - kind: 'NON_NULL', - ofType: { - kind: 'UNION', - name: 'RoleCreateResult', - ofType: null, - }, - }, - args: [ { - name: 'input', + name: 'name', type: { kind: 'NON_NULL', ofType: { kind: 'SCALAR', - name: 'Any', + name: 'String', + ofType: null, }, }, }, - ], - }, + { + name: 'hash', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + }, + ], + }, + { + name: 'admin', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'OBJECT', + name: 'AdminMutations', + ofType: null, + }, + }, + args: [], + }, + { + name: 'bucketAdd', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'BucketAddResult', + ofType: null, + }, + }, + args: [ + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'bucketUpdate', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'BucketUpdateResult', + ofType: null, + }, + }, + args: [ + { + name: 'name', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + }, + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'bucketRemove', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'BucketRemoveResult', + ofType: null, + }, + }, + args: [ + { + name: 'name', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + }, + ], + }, + { + name: 'policyCreateManaged', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'PolicyResult', + ofType: null, + }, + }, + args: [ + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'policyCreateUnmanaged', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'PolicyResult', + ofType: null, + }, + }, + args: [ + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'policyUpdateManaged', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'PolicyResult', + ofType: null, + }, + }, + args: [ + { + name: 'id', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'ID', + ofType: null, + }, + }, + }, + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'policyUpdateUnmanaged', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'PolicyResult', + ofType: null, + }, + }, + args: [ + { + name: 'id', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'ID', + ofType: null, + }, + }, + }, + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'policyDelete', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'PolicyDeleteResult', + ofType: null, + }, + }, + args: [ + { + name: 'id', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'ID', + ofType: null, + }, + }, + }, + ], + }, + { + name: 'roleCreateManaged', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'RoleCreateResult', + ofType: null, + }, + }, + args: [ + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, { name: 'roleCreateUnmanaged', type: { @@ -1905,6 +2272,25 @@ export default { ], interfaces: [], }, + { + kind: 'OBJECT', + name: 'MyRole', + fields: [ + { + name: 'name', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, { kind: 'OBJECT', name: 'NotificationConfigurationError', @@ -2232,6 +2618,24 @@ export default { ], interfaces: [], }, + { + kind: 'UNION', + name: 'OperationResult', + possibleTypes: [ + { + kind: 'OBJECT', + name: 'Ok', + }, + { + kind: 'OBJECT', + name: 'InvalidInput', + }, + { + kind: 'OBJECT', + name: 'OperationError', + }, + ], + }, { kind: 'OBJECT', name: 'Package', @@ -3308,6 +3712,15 @@ export default { kind: 'OBJECT', name: 'Query', fields: [ + { + name: 'me', + type: { + kind: 'OBJECT', + name: 'Me', + ofType: null, + }, + args: [], + }, { name: 'config', type: { @@ -3611,6 +4024,18 @@ export default { }, args: [], }, + { + name: 'admin', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'OBJECT', + name: 'AdminQueries', + ofType: null, + }, + }, + args: [], + }, { name: 'policies', type: { @@ -4564,6 +4989,24 @@ export default { ], interfaces: [], }, + { + kind: 'UNION', + name: 'SwitchRoleResult', + possibleTypes: [ + { + kind: 'OBJECT', + name: 'Me', + }, + { + kind: 'OBJECT', + name: 'InvalidInput', + }, + { + kind: 'OBJECT', + name: 'OperationError', + }, + ], + }, { kind: 'OBJECT', name: 'TestStats', @@ -4751,6 +5194,251 @@ export default { ], interfaces: [], }, + { + kind: 'OBJECT', + name: 'User', + fields: [ + { + name: 'name', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + args: [], + }, + { + name: 'email', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + args: [], + }, + { + name: 'dateJoined', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Datetime', + ofType: null, + }, + }, + args: [], + }, + { + name: 'lastLogin', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Datetime', + ofType: null, + }, + }, + args: [], + }, + { + name: 'isActive', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + }, + args: [], + }, + { + name: 'isAdmin', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + }, + args: [], + }, + { + name: 'isSsoOnly', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + }, + args: [], + }, + { + name: 'isService', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Boolean', + ofType: null, + }, + }, + args: [], + }, + { + name: 'role', + type: { + kind: 'UNION', + name: 'Role', + ofType: null, + }, + args: [], + }, + { + name: 'extraRoles', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'LIST', + ofType: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'Role', + ofType: null, + }, + }, + }, + }, + args: [], + }, + ], + interfaces: [], + }, + { + kind: 'OBJECT', + name: 'UserAdminMutations', + fields: [ + { + name: 'create', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'UNION', + name: 'UserResult', + ofType: null, + }, + }, + args: [ + { + name: 'input', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'Any', + }, + }, + }, + ], + }, + { + name: 'mutate', + type: { + kind: 'OBJECT', + name: 'MutateUserAdminMutations', + ofType: null, + }, + args: [ + { + name: 'name', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + }, + ], + }, + ], + interfaces: [], + }, + { + kind: 'OBJECT', + name: 'UserAdminQueries', + fields: [ + { + name: 'list', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'LIST', + ofType: { + kind: 'NON_NULL', + ofType: { + kind: 'OBJECT', + name: 'User', + ofType: null, + }, + }, + }, + }, + args: [], + }, + { + name: 'get', + type: { + kind: 'OBJECT', + name: 'User', + ofType: null, + }, + args: [ + { + name: 'name', + type: { + kind: 'NON_NULL', + ofType: { + kind: 'SCALAR', + name: 'String', + ofType: null, + }, + }, + }, + ], + }, + ], + interfaces: [], + }, + { + kind: 'UNION', + name: 'UserResult', + possibleTypes: [ + { + kind: 'OBJECT', + name: 'User', + }, + { + kind: 'OBJECT', + name: 'InvalidInput', + }, + { + kind: 'OBJECT', + name: 'OperationError', + }, + ], + }, { kind: 'SCALAR', name: 'Any', diff --git a/catalog/app/model/graphql/types.generated.ts b/catalog/app/model/graphql/types.generated.ts index ae98dee3755..1212bf54522 100644 --- a/catalog/app/model/graphql/types.generated.ts +++ b/catalog/app/model/graphql/types.generated.ts @@ -36,6 +36,16 @@ export interface AccessCounts { readonly counts: ReadonlyArray } +export interface AdminMutations { + readonly __typename: 'AdminMutations' + readonly user: UserAdminMutations +} + +export interface AdminQueries { + readonly __typename: 'AdminQueries' + readonly user: UserAdminQueries +} + export interface BooleanPackageUserMetaFacet extends IPackageUserMetaFacet { readonly __typename: 'BooleanPackageUserMetaFacet' readonly path: Scalars['String'] @@ -309,11 +319,61 @@ export interface ManagedRoleInput { readonly policies: ReadonlyArray } +export interface Me { + readonly __typename: 'Me' + readonly name: Scalars['String'] + readonly email: Scalars['String'] + readonly isAdmin: Scalars['Boolean'] + readonly role: MyRole + readonly roles: ReadonlyArray +} + +export interface MutateUserAdminMutations { + readonly __typename: 'MutateUserAdminMutations' + readonly delete: OperationResult + readonly setEmail: UserResult + readonly setRole: UserResult + readonly addRoles: UserResult + readonly removeRoles: UserResult + readonly setAdmin: UserResult + readonly setActive: UserResult + readonly resetPassword: OperationResult +} + +export interface MutateUserAdminMutationssetEmailArgs { + email: Scalars['String'] +} + +export interface MutateUserAdminMutationssetRoleArgs { + role: Scalars['String'] + extraRoles: Maybe> + append?: Scalars['Boolean'] +} + +export interface MutateUserAdminMutationsaddRolesArgs { + roles: ReadonlyArray +} + +export interface MutateUserAdminMutationsremoveRolesArgs { + roles: ReadonlyArray + fallback: Maybe +} + +export interface MutateUserAdminMutationssetAdminArgs { + admin: Scalars['Boolean'] +} + +export interface MutateUserAdminMutationssetActiveArgs { + active: Scalars['Boolean'] +} + export interface Mutation { readonly __typename: 'Mutation' + readonly switchRole: SwitchRoleResult readonly packageConstruct: PackageConstructResult readonly packagePromote: PackagePromoteResult readonly packageRevisionDelete: PackageRevisionDeleteResult + readonly admin: AdminMutations readonly bucketAdd: BucketAddResult readonly bucketUpdate: BucketUpdateResult readonly bucketRemove: BucketRemoveResult @@ -333,6 +393,10 @@ export interface Mutation { readonly browsingSessionDispose: BrowsingSessionDisposeResult } +export interface MutationswitchRoleArgs { + roleName: Scalars['String'] +} + export interface MutationpackageConstructArgs { params: PackagePushParams src: PackageConstructSource @@ -424,6 +488,11 @@ export interface MutationbrowsingSessionDisposeArgs { id: Scalars['ID'] } +export interface MyRole { + readonly __typename: 'MyRole' + readonly name: Scalars['String'] +} + export interface NotificationConfigurationError { readonly __typename: 'NotificationConfigurationError' readonly _: Maybe @@ -504,6 +573,8 @@ export interface OperationError { readonly context: Maybe } +export type OperationResult = Ok | InvalidInput | OperationError + export interface Package { readonly __typename: 'Package' readonly bucket: Scalars['String'] @@ -757,6 +828,7 @@ export type PolicyResult = Policy | InvalidInput | OperationError export interface Query { readonly __typename: 'Query' + readonly me: Maybe readonly config: Config readonly bucketConfigs: ReadonlyArray readonly bucketConfig: Maybe @@ -768,6 +840,7 @@ export interface Query { readonly searchMoreObjects: ObjectsSearchMoreResult readonly searchMorePackages: PackagesSearchMoreResult readonly subscription: SubscriptionState + readonly admin: AdminQueries readonly policies: ReadonlyArray readonly policy: Maybe readonly roles: ReadonlyArray @@ -1014,6 +1087,8 @@ export interface SubscriptionState { readonly timestamp: Scalars['Datetime'] } +export type SwitchRoleResult = Me | InvalidInput | OperationError + export interface TestStats { readonly __typename: 'TestStats' readonly passed: Scalars['Int'] @@ -1059,3 +1134,50 @@ export interface UnmanagedRoleInput { readonly name: Scalars['String'] readonly arn: Scalars['String'] } + +export interface User { + readonly __typename: 'User' + readonly name: Scalars['String'] + readonly email: Scalars['String'] + readonly dateJoined: Scalars['Datetime'] + readonly lastLogin: Scalars['Datetime'] + readonly isActive: Scalars['Boolean'] + readonly isAdmin: Scalars['Boolean'] + readonly isSsoOnly: Scalars['Boolean'] + readonly isService: Scalars['Boolean'] + readonly role: Maybe + readonly extraRoles: ReadonlyArray +} + +export interface UserAdminMutations { + readonly __typename: 'UserAdminMutations' + readonly create: UserResult + readonly mutate: Maybe +} + +export interface UserAdminMutationscreateArgs { + input: UserInput +} + +export interface UserAdminMutationsmutateArgs { + name: Scalars['String'] +} + +export interface UserAdminQueries { + readonly __typename: 'UserAdminQueries' + readonly list: ReadonlyArray + readonly get: Maybe +} + +export interface UserAdminQueriesgetArgs { + name: Scalars['String'] +} + +export interface UserInput { + readonly name: Scalars['String'] + readonly email: Scalars['String'] + readonly role: Scalars['String'] + readonly extraRoles: Maybe> +} + +export type UserResult = User | InvalidInput | OperationError diff --git a/catalog/app/utils/Dialogs.tsx b/catalog/app/utils/Dialogs.tsx index 243a2f7dec9..efd4370c056 100644 --- a/catalog/app/utils/Dialogs.tsx +++ b/catalog/app/utils/Dialogs.tsx @@ -6,9 +6,9 @@ import type { Resolver } from 'utils/defer' type DialogState = 'open' | 'closing' | 'closed' -type ExtraDialogProps = Omit +export type ExtraDialogProps = Omit -export type Close = [R] extends [never] ? () => void : Resolver['resolve'] +export type Close = [R] extends [never] ? () => void : Resolver['resolve'] type Render = (props: { close: Close }) => JSX.Element @@ -24,12 +24,15 @@ export const useDialogs = () => { const open = React.useCallback( (render: Render, props?: ExtraDialogProps) => { + if (state !== 'closed') { + throw new Error('Another dialog is already open') + } const { resolver, promise } = defer() setDialog({ render, props, resolver }) setState('open') return promise }, - [setDialog, setState], + [state, setDialog, setState], ) const close = React.useCallback( @@ -65,3 +68,5 @@ export const useDialogs = () => { export const use = useDialogs export type Dialogs = ReturnType + +export type Open = Dialogs['open'] diff --git a/catalog/app/utils/GlobalDialogs.tsx b/catalog/app/utils/GlobalDialogs.tsx new file mode 100644 index 00000000000..eac15ce8045 --- /dev/null +++ b/catalog/app/utils/GlobalDialogs.tsx @@ -0,0 +1,29 @@ +import invariant from 'invariant' +import * as React from 'react' + +import * as Dialogs from 'utils/Dialogs' + +export type { Open, Close, ExtraDialogProps } from 'utils/Dialogs' + +const Ctx = React.createContext(null) + +export function useOpenDialog() { + const open = React.useContext(Ctx) + invariant(open, 'useOpenDialog() must be used within ') + return open +} + +export { useOpenDialog as use } + +export default function WithGlobalDialogs({ + children, + ...props +}: React.PropsWithChildren) { + const dialogs = Dialogs.use() + return ( + + {children} + {dialogs.render(props)} + + ) +} diff --git a/catalog/app/utils/GraphQL/Provider.tsx b/catalog/app/utils/GraphQL/Provider.tsx index b03a302c816..75197214f48 100644 --- a/catalog/app/utils/GraphQL/Provider.tsx +++ b/catalog/app/utils/GraphQL/Provider.tsx @@ -16,6 +16,7 @@ const devtools = process.env.NODE_ENV === 'development' ? [DevTools.devtoolsExch const BUCKET_CONFIGS_QUERY = urql.gql`{ bucketConfigs { name } }` const POLICIES_QUERY = urql.gql`{ policies { id } }` const ROLES_QUERY = urql.gql`{ roles { id } }` +const USERS_QUERY = urql.gql`{ admin { user { list { name } } } }` const DEFAULT_ROLE_QUERY = urql.gql`{ defaultRole { id } }` function handlePackageCreation(result: any, cache: GraphCache.Cache) { @@ -110,6 +111,7 @@ export default function GraphQLProvider({ children }: React.PropsWithChildren<{} Status: () => null, StatusReport: (r) => (typeof r.timestamp === 'string' ? r.timestamp : null), StatusReportList: () => null, + Unavailable: () => null, SubscriptionState: () => null, TestStats: () => null, TestStatsTimeSeries: () => null, @@ -130,6 +132,14 @@ export default function GraphQLProvider({ children }: React.PropsWithChildren<{} PackagesSearchResultSet: () => null, InvalidInput: () => null, InputError: () => null, + Me: (me) => me.name as string, + MyRole: (r) => r.name as string, + User: (u) => (u.name as string) ?? null, + AdminQueries: () => null, + UserAdminQueries: () => null, + AdminMutations: () => null, + UserAdminMutations: () => null, + MutateUserAdminMutations: () => null, }, updates: { Mutation: { @@ -305,8 +315,30 @@ export default function GraphQLProvider({ children }: React.PropsWithChildren<{} packagePromote: (result, _vars, cache) => { handlePackageCreation(result.packagePromote, cache) }, + admin: (result: any, _vars, cache, info) => { + // XXX: newer versions of GraphCache support updaters on arbitrary types + if (result.admin?.user?.create?.__typename === 'User') { + // Add created User to user list + // XXX: sort? + const addUser = R.append(result.admin.user.create) + cache.updateQuery( + { query: USERS_QUERY }, + R.evolve({ admin: { user: { list: addUser } } }), + ) + } + if (result.admin?.user?.mutate?.delete?.__typename === 'Ok') { + // XXX: handle "user not found" somehow? + // Remove deleted User from user list + const rmUser = R.reject(R.propEq('name', info.variables.name)) + cache.updateQuery( + { query: USERS_QUERY }, + R.evolve({ admin: { user: { list: rmUser } } }), + ) + } + }, }, }, + // XXX: make an exchange for handling optimistic responses optimistic: { bucketRemove: () => ({ __typename: 'BucketRemoveSuccess' }), roleDelete: () => ({ __typename: 'RoleDeleteSuccess' }), diff --git a/catalog/app/utils/GraphQL/wrappers.ts b/catalog/app/utils/GraphQL/wrappers.ts index 15c12c58fff..d445be94383 100644 --- a/catalog/app/utils/GraphQL/wrappers.ts +++ b/catalog/app/utils/GraphQL/wrappers.ts @@ -229,13 +229,14 @@ export function useQueryS( throw err } -interface RunMutationContext extends Partial { +interface RunMutationContext extends Partial { silent?: boolean + optimisticResponse?: Data } type RunMutation = ( variables?: Variables, - context?: RunMutationContext, + context?: RunMutationContext, ) => Promise /** @@ -258,9 +259,14 @@ export function useMutation( const [, execMutation] = urql.useMutation(query) return React.useCallback( - async (variables?: Variables, context?: RunMutationContext) => { - const { silent = false, ...ctx } = context || {} - const result = await execMutation(variables, ctx) + async (variables?: Variables, context?: RunMutationContext) => { + // XXX: probably this logic for optimistic responses is not necessary + const { silent = false, optimisticResponse, ...ctx } = context || {} + const computedVariables = { + __optimisticResponse: optimisticResponse, + ...variables, + } as Variables + const result = await execMutation(computedVariables, ctx) if (!result.data) { const err = result.error || new MutationError(result) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c752a425337..1a777e2dd3d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,6 +14,14 @@ Entries inside each section should be ordered by type: ## Catalog, Lambdas !--> +# unreleased - YYYY-MM-DD +## Python API + +## CLI + +## Catalog, Lambdas +- [Added] Support multiple roles per user ([#3982](https://github.com/quiltdata/quilt/pull/3982)) + # 6.0.0a4 - 2024-06-18 ## Python API