Skip to content

Commit

Permalink
Merge pull request #1594 from gchq/BAI-1372-implement-permissions-con…
Browse files Browse the repository at this point in the history
…text

Bai 1372 implement permissions context
  • Loading branch information
ARADDCC012 authored Nov 5, 2024
2 parents 33a86b1 + 28ab35a commit d0fd369
Show file tree
Hide file tree
Showing 24 changed files with 302 additions and 254 deletions.
23 changes: 21 additions & 2 deletions frontend/actions/accessRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useSWR from 'swr'
import { AccessRequestInterface } from 'types/types'
import { AccessRequestInterface, AccessRequestUserPermissions } from 'types/types'
import { ErrorInfo, fetcher } from 'utils/fetcher'

const emptyAccessRequestList = []
Expand Down Expand Up @@ -30,12 +30,31 @@ export function useGetAccessRequest(modelId: string | undefined, accessRequestId

return {
mutateAccessRequest: mutate,
accessRequest: data ? data.accessRequest : undefined,
accessRequest: data?.accessRequest,
isAccessRequestLoading: isLoading,
isAccessRequestError: error,
}
}

export function useGetCurrentUserPermissionsForAccessRequest(entryId?: string, accessRequestId?: string) {
const { data, isLoading, error, mutate } = useSWR<
{
permissions: AccessRequestUserPermissions
},
ErrorInfo
>(
entryId && accessRequestId ? `/api/v2/model/${entryId}/access-request/${accessRequestId}/permissions/mine` : null,
fetcher,
)

return {
mutateAccessRequestUserPermissions: mutate,
accessRequestUserPermissions: data?.permissions,
isAccessRequestUserPermissionsLoading: isLoading,
isAccessRequestUserPermissionsError: error,
}
}

export function postAccessRequest(modelId: string, schemaId: string, form: Record<string, unknown>) {
return fetch(`/api/v2/model/${modelId}/access-requests`, {
method: 'post',
Expand Down
20 changes: 18 additions & 2 deletions frontend/actions/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import qs from 'querystring'
import useSWR from 'swr'

import { EntryForm, EntryInterface, EntryKindKeys, ModelImage, Role } from '../types/types'
import { EntryForm, EntryInterface, EntryKindKeys, EntryUserPermissions, ModelImage, Role } from '../types/types'
import { ErrorInfo, fetcher } from '../utils/fetcher'

const emptyModelList = []
Expand Down Expand Up @@ -65,7 +65,7 @@ export function useGetModel(id: string | undefined, kind: EntryKindKeys) {

return {
mutateModel: mutate,
model: data ? data.model : undefined,
model: data?.model,
isModelLoading: isLoading,
isModelError: error,
}
Expand Down Expand Up @@ -125,6 +125,22 @@ export function useGetModelRolesCurrentUser(id?: string) {
}
}

export function useGetCurrentUserPermissionsForEntry(entryId?: string) {
const { data, isLoading, error, mutate } = useSWR<
{
permissions: EntryUserPermissions
},
ErrorInfo
>(entryId ? `/api/v2/model/${entryId}/permissions/mine` : null, fetcher)

return {
mutateEntryUserPermissions: mutate,
entryUserPermissions: data?.permissions,
isEntryUserPermissionsLoading: isLoading,
isEntryUserPermissionsError: error,
}
}

export async function postModel(form: EntryForm) {
return fetch(`/api/v2/models`, {
method: 'post',
Expand Down
21 changes: 13 additions & 8 deletions frontend/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { AppProps } from 'next/app'
import Head from 'next/head'
import { SnackbarProvider } from 'notistack'
import { useEffect } from 'react'
import UserPermissionsContext from 'src/contexts/userPermissionsContext'
import useUserPermissions from 'src/hooks/UserPermissionsHook'
import Wrapper from 'src/Wrapper'

import ThemeModeContext from '../src/contexts/themeModeContext'
Expand All @@ -23,6 +25,7 @@ export default function MyApp(props: AppProps) {
const { Component, pageProps } = props
const themeModeValue = useThemeMode()
const unsavedChangesValue = useUnsavedChanges()
const userPermissionsValue = useUserPermissions()

// This is set so that 'react-markdown-editor' respects the theme set by MUI.
useEffect(() => {
Expand All @@ -39,14 +42,16 @@ export default function MyApp(props: AppProps) {
<ThemeProvider theme={themeModeValue.theme}>
<UnsavedChangesContext.Provider value={unsavedChangesValue}>
<ThemeModeContext.Provider value={themeModeValue}>
<SnackbarProvider>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale='en-gb'>
<CssBaseline />
<Wrapper>
<Component {...pageProps} />
</Wrapper>
</LocalizationProvider>
</SnackbarProvider>
<UserPermissionsContext.Provider value={userPermissionsValue}>
<SnackbarProvider>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale='en-gb'>
<CssBaseline />
<Wrapper>
<Component {...pageProps} />
</Wrapper>
</LocalizationProvider>
</SnackbarProvider>
</UserPermissionsContext.Provider>
</ThemeModeContext.Provider>
</UnsavedChangesContext.Provider>
</ThemeProvider>
Expand Down
26 changes: 10 additions & 16 deletions frontend/pages/data-card/[dataCardId].tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useGetModel } from 'actions/model'
import { useGetCurrentUser } from 'actions/user'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useContext, useMemo } from 'react'
import Loading from 'src/common/Loading'
import PageWithTabs, { PageTab } from 'src/common/PageWithTabs'
import Title from 'src/common/Title'
import UserPermissionsContext from 'src/contexts/userPermissionsContext'
import Overview from 'src/entry/overview/Overview'
import Settings from 'src/entry/settings/Settings'
import MultipleErrorWrapper from 'src/errors/MultipleErrorWrapper'
import { EntryKind } from 'types/types'
import { getCurrentUserRoles, getRequiredRolesText, hasRole } from 'utils/roles'

export default function DataCard() {
const router = useRouter()
Expand All @@ -19,14 +18,10 @@ export default function DataCard() {
isModelLoading: isDataCardLoading,
isModelError: isDataCardError,
} = useGetModel(dataCardId, EntryKind.DATA_CARD)
const { currentUser, isCurrentUserLoading, isCurrentUserError } = useGetCurrentUser()

const currentUserRoles = useMemo(() => getCurrentUserRoles(dataCard, currentUser), [dataCard, currentUser])
const { userPermissions } = useContext(UserPermissionsContext)

const [isReadOnly, requiredRolesText] = useMemo(() => {
const validRoles = ['owner']
return [!hasRole(currentUserRoles, validRoles), getRequiredRolesText(currentUserRoles, validRoles)]
}, [currentUserRoles])
const settingsPermission = useMemo(() => userPermissions['editEntry'], [userPermissions])

const tabs: PageTab[] = useMemo(
() =>
Expand All @@ -35,30 +30,29 @@ export default function DataCard() {
{
title: 'Overview',
path: 'overview',
view: <Overview entry={dataCard} currentUserRoles={currentUserRoles} />,
view: <Overview entry={dataCard} />,
},
{
title: 'Settings',
path: 'settings',
disabled: isReadOnly,
disabledText: requiredRolesText,
view: <Settings entry={dataCard} currentUserRoles={currentUserRoles} />,
disabled: !settingsPermission.hasPermission,
disabledText: settingsPermission.info,
view: <Settings entry={dataCard} />,
},
]
: [],
[currentUserRoles, dataCard, isReadOnly, requiredRolesText],
[dataCard, settingsPermission.hasPermission, settingsPermission.info],
)

const error = MultipleErrorWrapper(`Unable to load data card page`, {
isDataCardError,
isCurrentUserError,
})
if (error) return error

return (
<>
<Title text={dataCard ? dataCard.name : 'Loading...'} />
{(isDataCardLoading || isCurrentUserLoading) && <Loading />}
{isDataCardLoading && <Loading />}
{dataCard && (
<PageWithTabs
title={dataCard.name}
Expand Down
38 changes: 13 additions & 25 deletions frontend/pages/model/[modelId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { useGetModel } from 'actions/model'
import { useGetUiConfig } from 'actions/uiConfig'
import { useGetCurrentUser } from 'actions/user'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useContext, useMemo } from 'react'
import Loading from 'src/common/Loading'
import PageWithTabs, { PageTab } from 'src/common/PageWithTabs'
import Title from 'src/common/Title'
import UserPermissionsContext from 'src/contexts/userPermissionsContext'
import AccessRequests from 'src/entry/model/AccessRequests'
import InferenceServices from 'src/entry/model/InferenceServices'
import ModelImages from 'src/entry/model/ModelImages'
Expand All @@ -14,7 +15,7 @@ import Overview from 'src/entry/overview/Overview'
import Settings from 'src/entry/settings/Settings'
import MultipleErrorWrapper from 'src/errors/MultipleErrorWrapper'
import { EntryKind } from 'types/types'
import { getCurrentUserRoles, getRequiredRolesText, hasRole } from 'utils/roles'
import { getCurrentUserRoles } from 'utils/roles'

export default function Model() {
const router = useRouter()
Expand All @@ -23,12 +24,11 @@ export default function Model() {
const { currentUser, isCurrentUserLoading, isCurrentUserError } = useGetCurrentUser()
const { uiConfig, isUiConfigLoading, isUiConfigError } = useGetUiConfig()

const { userPermissions } = useContext(UserPermissionsContext)

const currentUserRoles = useMemo(() => getCurrentUserRoles(model, currentUser), [model, currentUser])

const [isReadOnly, requiredRolesText] = useMemo(() => {
const validRoles = ['owner']
return [!hasRole(currentUserRoles, validRoles), getRequiredRolesText(currentUserRoles, validRoles)]
}, [currentUserRoles])
const settingsPermission = useMemo(() => userPermissions['editEntry'], [userPermissions])

const tabs: PageTab[] = useMemo(
() =>
Expand All @@ -37,13 +37,7 @@ export default function Model() {
{
title: 'Overview',
path: 'overview',
view: (
<Overview
entry={model}
currentUserRoles={currentUserRoles}
readOnly={!!model.settings.mirror?.sourceModelId}
/>
),
view: <Overview entry={model} readOnly={!!model.settings.mirror?.sourceModelId} />,
},
{
title: 'Releases',
Expand All @@ -69,30 +63,24 @@ export default function Model() {
{
title: 'Registry',
path: 'registry',
view: (
<ModelImages
model={model}
currentUserRoles={currentUserRoles}
readOnly={!!model.settings.mirror?.sourceModelId}
/>
),
view: <ModelImages model={model} readOnly={!!model.settings.mirror?.sourceModelId} />,
},
{
title: 'Inferencing',
path: 'inferencing',
view: <InferenceServices model={model} currentUserRoles={currentUserRoles} />,
view: <InferenceServices model={model} />,
hidden: !uiConfig.inference.enabled,
},
{
title: 'Settings',
path: 'settings',
disabled: isReadOnly,
disabledText: requiredRolesText,
view: <Settings entry={model} currentUserRoles={currentUserRoles} />,
disabled: !settingsPermission.hasPermission,
disabledText: settingsPermission.info,
view: <Settings entry={model} />,
},
]
: [],
[model, uiConfig, currentUserRoles, isReadOnly, requiredRolesText],
[model, uiConfig, currentUserRoles, settingsPermission.hasPermission, settingsPermission.info],
)

function requestAccess() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
import { ArrowBack } from '@mui/icons-material'
import { Button, Card, Container, Divider, Link, Stack, Typography } from '@mui/material'
import { useGetInference } from 'actions/inferencing'
import { useGetModel } from 'actions/model'
import { useGetCurrentUser } from 'actions/user'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import Loading from 'src/common/Loading'
import Title from 'src/common/Title'
import EditableInference from 'src/entry/model/inferencing/EditableInference'
import MultipleErrorWrapper from 'src/errors/MultipleErrorWrapper'
import { EntryKind } from 'types/types'
import { getCurrentUserRoles } from 'utils/roles'

export default function InferenceSettings() {
const router = useRouter()
const { modelId, image, tag }: { modelId?: string; image?: string; tag?: string } = router.query
const { inference, isInferenceLoading, isInferenceError } = useGetInference(modelId, image, tag)
const { model, isModelLoading, isModelError } = useGetModel(modelId, EntryKind.MODEL)
const { currentUser, isCurrentUserLoading, isCurrentUserError } = useGetCurrentUser()

const currentUserRoles = useMemo(() => getCurrentUserRoles(model, currentUser), [model, currentUser])

const error = MultipleErrorWrapper(`Unable to load inference settings page`, {
isInferenceError,
isModelError,
isCurrentUserError,
})
if (error) return error

return (
<>
<Title text={inference ? `${inference.image}:${inference.tag}` : 'Loading...'} />
{!inference || isInferenceLoading || isModelLoading || isCurrentUserLoading ? (
{!inference || isInferenceLoading ? (
<Loading />
) : (
<Container maxWidth='md'>
Expand All @@ -47,7 +36,7 @@ export default function InferenceSettings() {
{inference ? `${inference.image}:${inference.tag}` : 'Loading...'}
</Typography>
</Stack>
{inference && <EditableInference inference={inference} currentUserRoles={currentUserRoles} />}
{inference && <EditableInference inference={inference} />}
</Stack>
</Card>
</Container>
Expand Down
1 change: 0 additions & 1 deletion frontend/pages/model/[modelId]/release/[semver].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export default function Release() {
{release && (
<EditableRelease
release={release}
currentUserRoles={currentUserRoles}
isEdit={isEdit}
onIsEditChange={setIsEdit}
readOnly={!!model?.settings.mirror?.sourceModelId}
Expand Down
Loading

0 comments on commit d0fd369

Please sign in to comment.