Skip to content

Commit

Permalink
Merge pull request #1455 from gchq/BAI-1376-Display-entry-collaborato…
Browse files Browse the repository at this point in the history
…rs-outside-of-the-settings-tab

Bai 1376 display entry collaborators outside of the settings tab
  • Loading branch information
IR96334 authored Aug 12, 2024
2 parents 5d83d36 + 7d60eb8 commit e22f736
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 446 deletions.
15 changes: 11 additions & 4 deletions frontend/pages/data-card/[dataCardId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useGetCurrentUser } from 'actions/user'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import Loading from 'src/common/Loading'
import PageWithTabs from 'src/common/PageWithTabs'
import PageWithTabs, { PageTab } from 'src/common/PageWithTabs'
import Title from 'src/common/Title'
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 } from 'utils/roles'
import { getCurrentUserRoles, getRequiredRolesText, hasRole } from 'utils/roles'

export default function DataCard() {
const router = useRouter()
Expand All @@ -23,7 +23,12 @@ export default function DataCard() {

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

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

const tabs: PageTab[] = useMemo(
() =>
dataCard
? [
Expand All @@ -35,11 +40,13 @@ export default function DataCard() {
{
title: 'Settings',
path: 'settings',
disabled: isReadOnly,
disabledText: requiredRolesText,
view: <Settings entry={dataCard} currentUserRoles={currentUserRoles} />,
},
]
: [],
[currentUserRoles, dataCard],
[currentUserRoles, dataCard, isReadOnly, requiredRolesText],
)

const error = MultipleErrorWrapper(`Unable to load data card page`, {
Expand Down
15 changes: 11 additions & 4 deletions frontend/pages/model/[modelId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useGetCurrentUser } from 'actions/user'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import Loading from 'src/common/Loading'
import PageWithTabs from 'src/common/PageWithTabs'
import PageWithTabs, { PageTab } from 'src/common/PageWithTabs'
import Title from 'src/common/Title'
import AccessRequests from 'src/entry/model/AccessRequests'
import InferenceServices from 'src/entry/model/InferenceServices'
Expand All @@ -14,7 +14,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 } from 'utils/roles'
import { getCurrentUserRoles, getRequiredRolesText, hasRole } from 'utils/roles'

export default function Model() {
const router = useRouter()
Expand All @@ -25,7 +25,12 @@ export default function Model() {

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

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

const tabs: PageTab[] = useMemo(
() =>
model && uiConfig
? [
Expand Down Expand Up @@ -81,11 +86,13 @@ export default function Model() {
{
title: 'Settings',
path: 'settings',
disabled: isReadOnly,
disabledText: requiredRolesText,
view: <Settings entry={model} currentUserRoles={currentUserRoles} />,
},
]
: [],
[model, uiConfig, currentUserRoles],
[model, uiConfig, currentUserRoles, isReadOnly, requiredRolesText],
)

function requestAccess() {
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/entry/EntityIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import GroupsIcon from '@mui/icons-material/Groups'
import PersonIcon from '@mui/icons-material/Person'
import { useMemo } from 'react'
import { CollaboratorEntry } from 'types/types'

type EntityIconProps = {
entryCollaborator: CollaboratorEntry
}

export default function EntityIcon({ entryCollaborator }: EntityIconProps) {
const isUser = useMemo(() => entryCollaborator.entity.startsWith('user:'), [entryCollaborator])
return isUser ? <PersonIcon color='primary' /> : <GroupsIcon color='secondary' />
}
20 changes: 20 additions & 0 deletions frontend/src/entry/EntityNameDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Typography } from '@mui/material'
import { useMemo } from 'react'
import UserDisplay from 'src/common/UserDisplay'
import { CollaboratorEntry, EntityKind } from 'types/types'

type EntityNameDisplayProps = {
entryCollaborator: CollaboratorEntry
}

export default function EntityNameDisplay({ entryCollaborator }: EntityNameDisplayProps) {
const [entryCollaboratorKind, entryCollaboratorName] = useMemo(
() => entryCollaborator.entity.split(':'),
[entryCollaborator],
)
return entryCollaboratorKind === EntityKind.USER || entryCollaboratorKind === EntityKind.GROUP ? (
<UserDisplay dn={entryCollaboratorName} />
) : (
<Typography fontWeight='bold'>{entryCollaboratorName}</Typography>
)
}
41 changes: 11 additions & 30 deletions frontend/src/entry/EntryDescriptionInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TextField, Tooltip } from '@mui/material'
import { TextField } from '@mui/material'
import { ChangeEvent } from 'react'
import LabelledInput from 'src/common/LabelledInput'

Expand All @@ -7,42 +7,23 @@ const htmlId = 'entry-description-input'
type EntryDescriptionInputProps = {
value: string
onChange: (value: string) => void
} & (
| {
isReadOnly: boolean
requiredRolesText: string
}
| {
isReadOnly?: never
requiredRolesText?: never
}
)
}

export default function EntryDescriptionInput({
value,
onChange,
isReadOnly = false,
requiredRolesText = '',
}: EntryDescriptionInputProps) {
export default function EntryDescriptionInput({ value, onChange }: EntryDescriptionInputProps) {
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value)
}

return (
<LabelledInput fullWidth required label='Description' htmlFor={htmlId}>
<Tooltip title={requiredRolesText}>
<span>
<TextField
fullWidth
id={htmlId}
value={value}
size='small'
disabled={isReadOnly}
onChange={handleChange}
data-test='entryDescriptionInput'
/>
</span>
</Tooltip>
<TextField
fullWidth
id={htmlId}
value={value}
size='small'
onChange={handleChange}
data-test='entryDescriptionInput'
/>
</LabelledInput>
)
}
47 changes: 13 additions & 34 deletions frontend/src/entry/EntryNameInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TextField, Tooltip } from '@mui/material'
import { TextField } from '@mui/material'
import { ChangeEvent } from 'react'
import LabelledInput from 'src/common/LabelledInput'
import { EntryKindKeys } from 'types/types'
Expand All @@ -11,46 +11,25 @@ type EntryNameInputProps = {
kind: EntryKindKeys
onChange: (value: string) => void
autoFocus?: boolean
} & (
| {
isReadOnly: boolean
requiredRolesText: string
}
| {
isReadOnly?: never
requiredRolesText?: never
}
)
}

export default function EntryNameInput({
value,
kind,
onChange,
autoFocus = false,
isReadOnly = false,
requiredRolesText = '',
}: EntryNameInputProps) {
export default function EntryNameInput({ value, kind, onChange, autoFocus = false }: EntryNameInputProps) {
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value)
}

return (
<LabelledInput fullWidth required label={`${toTitleCase(kind)} Name`} htmlFor={htmlId}>
<Tooltip title={requiredRolesText}>
<span>
<TextField
required
fullWidth
disabled={isReadOnly}
autoFocus={autoFocus}
value={value}
size='small'
id={htmlId}
onChange={handleChange}
data-test='entryNameInput'
/>
</span>
</Tooltip>
<TextField
required
fullWidth
autoFocus={autoFocus}
value={value}
size='small'
id={htmlId}
onChange={handleChange}
data-test='entryNameInput'
/>
</LabelledInput>
)
}
28 changes: 10 additions & 18 deletions frontend/src/entry/model/mirroredModels/ExportModelAgreement.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { LoadingButton } from '@mui/lab'
import { Box, Card, Checkbox, Container, FormControlLabel, Stack, Tooltip, Typography } from '@mui/material'
import { Box, Card, Checkbox, Container, FormControlLabel, Stack, Typography } from '@mui/material'
import { postModelExportToS3 } from 'actions/model'
import { ChangeEvent, FormEvent, useState } from 'react'
import ModelExportAgreementText from 'src/entry/model/mirroredModels/ModelExportAgreementText'
import useNotification from 'src/hooks/useNotification'
import MessageAlert from 'src/MessageAlert'
import { getErrorMessage } from 'utils/fetcher'

type ExportModelProps = {
type ExportModelAgreementProps = {
modelId: string
isReadOnly: boolean
requiredRolesText: string
}

export default function ExportModel({ modelId, isReadOnly, requiredRolesText }: ExportModelProps) {
export default function ExportModelAgreement({ modelId }: ExportModelAgreementProps) {
const [checked, setChecked] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [loading, setLoading] = useState(false)
Expand Down Expand Up @@ -55,19 +53,13 @@ export default function ExportModel({ modelId, isReadOnly, requiredRolesText }:
<Box component='form' onSubmit={handleSubmit}>
<Stack spacing={2} alignItems='start' justifyContent='start'>
<ModelExportAgreementText />
<Tooltip title={requiredRolesText}>
<FormControlLabel
control={<Checkbox checked={checked} disabled={isReadOnly} onChange={handleChecked} />}
label='I agree to the terms and conditions of this model export agreement'
/>
</Tooltip>
<Tooltip title={requiredRolesText}>
<span>
<LoadingButton variant='contained' loading={loading} disabled={!checked} type='submit'>
Submit
</LoadingButton>
</span>
</Tooltip>
<FormControlLabel
control={<Checkbox checked={checked} onChange={handleChecked} />}
label='I agree to the terms and conditions of this model export agreement'
/>
<LoadingButton variant='contained' loading={loading} disabled={!checked} type='submit'>
Submit
</LoadingButton>
<MessageAlert message={errorMessage} severity='error' />
</Stack>
</Box>
Expand Down
57 changes: 18 additions & 39 deletions frontend/src/entry/model/mirroredModels/ExportSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { LoadingButton } from '@mui/lab'
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Stack,
TextField,
Tooltip,
Typography,
} from '@mui/material'
import { Accordion, AccordionDetails, AccordionSummary, Box, Stack, TextField, Typography } from '@mui/material'
import { patchModel } from 'actions/model'
import { ChangeEvent, FormEvent, useState } from 'react'
import LabelledInput from 'src/common/LabelledInput'
Expand All @@ -21,11 +12,9 @@ import { getErrorMessage } from 'utils/fetcher'

type ExportSettingsProps = {
model: EntryInterface
isReadOnly: boolean
requiredRolesText: string
}

export default function ExportSettings({ model, isReadOnly, requiredRolesText }: ExportSettingsProps) {
export default function ExportSettings({ model }: ExportSettingsProps) {
const sendNotification = useNotification()
const [destinationModelId, setDestinationModelId] = useState(model.settings.mirror?.destinationModelId || '')
const [loading, setLoading] = useState(false)
Expand Down Expand Up @@ -66,7 +55,7 @@ export default function ExportSettings({ model, isReadOnly, requiredRolesText }:

return (
<>
<ExportModelAgreement modelId={model.id} isReadOnly={isReadOnly} requiredRolesText={requiredRolesText} />
<ExportModelAgreement modelId={model.id} />
<Accordion sx={{ borderTop: 'none' }}>
<AccordionSummary sx={{ pl: 0 }} expandIcon={<ExpandMoreIcon />}>
<Typography component='h3' variant='h6'>
Expand All @@ -77,17 +66,12 @@ export default function ExportSettings({ model, isReadOnly, requiredRolesText }:
<Box component='form' onSubmit={handleSave}>
<Stack spacing={2} alignItems='flex-start'>
<LabelledInput label={'Destination Model ID'} htmlFor={'destination-model-id'} required>
<Tooltip title={requiredRolesText}>
<span>
<TextField
id='destination-model-id'
value={destinationModelId}
disabled={isReadOnly}
onChange={handleDestinationModelId}
size='small'
/>
</span>
</Tooltip>
<TextField
id='destination-model-id'
value={destinationModelId}
onChange={handleDestinationModelId}
size='small'
/>
</LabelledInput>
{/*TODO - Add the ability to filter releases needed for export (This functionality is not available on the backend)
<ReleaseSelector
Expand All @@ -98,20 +82,15 @@ export default function ExportSettings({ model, isReadOnly, requiredRolesText }:
requiredRolesText={requiredRolesText}
/>
*/}
<Tooltip title={requiredRolesText}>
<span>
<LoadingButton
sx={{ width: 'fit-content' }}
variant='contained'
data-test='createAccessRequestButton'
loading={loading}
disabled={isReadOnly}
type='submit'
>
Save
</LoadingButton>
</span>
</Tooltip>
<LoadingButton
sx={{ width: 'fit-content' }}
variant='contained'
data-test='createAccessRequestButton'
loading={loading}
type='submit'
>
Save
</LoadingButton>
<MessageAlert message={errorMessage} severity='error' />
</Stack>
</Box>
Expand Down
Loading

0 comments on commit e22f736

Please sign in to comment.