Skip to content

Commit

Permalink
feat(console): #1171 component with privilege (#1260)
Browse files Browse the repository at this point in the history
* feat: use auth with project update/delete

* feat: support project delete

* fix: wrong auth key

* feat: #1171 component with privilege

* refactor: simplified auth code
  • Loading branch information
waynelwz authored Sep 20, 2022
1 parent 778cab1 commit debce5f
Show file tree
Hide file tree
Showing 18 changed files with 263 additions and 116 deletions.
10 changes: 10 additions & 0 deletions console/src/api/ApiHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useFirstRender } from '@/hooks/useFirstRender'
import { useProject } from '@project/hooks/useProject'
import { useFetchProject } from '@/domain/project/hooks/useFetchProject'
import qs from 'qs'
import { useFetchProjectRole } from '@/domain/project/hooks/useFetchProjectRole'
import { useProjectRole } from '@/domain/project/hooks/useProjectRole'

export default function ApiHeader() {
const location = useLocation()
Expand All @@ -27,6 +29,8 @@ export default function ApiHeader() {
const projectId = React.useMemo(() => location?.pathname.match(/^\/projects\/(\d*)\/?/)?.[1], [location])
const projectInfo = useFetchProject(projectId)
const { setProject } = useProject()
const { role: roleCode } = useFetchProjectRole(projectId as string)
const { setRole } = useProjectRole()

useFirstRender(() => {
// @ts-ignore
Expand Down Expand Up @@ -128,5 +132,11 @@ export default function ApiHeader() {
}
}, [projectInfo.data, setProject, projectId])

useEffect(() => {
if (roleCode) {
setRole(roleCode)
}
}, [roleCode, setRole])

return <></>
}
42 changes: 11 additions & 31 deletions console/src/api/WithAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,6 @@
import { useProjectRole } from '@/domain/project/hooks/useProjectRole'
import React from 'react'

export const Privileges = {
'member.delete': true,
'member.update': true,
'member.create': true,
'member.read': true,
}
export type IPrivileges = typeof Privileges

export enum Role {
OWNER = 'OWNER',
GUEST = 'GUEST',
MAINTAINER = 'MAINTAINER',
NONE = 'NONE',
}

export const RolePrivilege: Record<Role, any> = {
OWNER: {
...Privileges,
},
MAINTAINER: {
...Privileges,
},
GUEST: {
...Privileges,
'member.delete': false,
'member.update': false,
'member.create': false,
},
NONE: {},
}
import { IPrivileges, Privileges, Role, RolePrivilege } from './const'

function hasPrivilege(role: Role, id: string) {
return RolePrivilege[role]?.[id] ?? false
Expand All @@ -52,3 +23,12 @@ export default function WithAuth({
if (!isPrivileged) return <></>
return children
}

export function WithCurrentAuth({ id, children }: { id: keyof IPrivileges; children: React.ReactElement | any }) {
const { role } = useProjectRole()
return (
<WithAuth role={role} id={id}>
{children}
</WithAuth>
)
}
35 changes: 35 additions & 0 deletions console/src/api/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const Privileges = {
'member.delete': true,
'member.update': true,
'member.create': true,
'member.read': true,
'project.update': true,
'project.delete': true,
'evaluation.action': true,
'evaluation.create': true,
'runtime.version.revert': true,
'model.version.revert': true,
'dataset.version.revert': true,
}
export type IPrivileges = typeof Privileges

export enum Role {
OWNER = 'OWNER',
GUEST = 'GUEST',
MAINTAINER = 'MAINTAINER',
NONE = 'NONE',
}

export const RolePrivilege: Record<Role, any> = {
OWNER: {
...Privileges,
},
MAINTAINER: {
...Privileges,
'member.update': false,
},
GUEST: {
'member.read': true,
},
NONE: {},
}
2 changes: 2 additions & 0 deletions console/src/domain/dataset/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export class DatasetObject {
this.summary[attr] = annos
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
throw e
}
Expand All @@ -125,6 +126,7 @@ export class DatasetObject {
this.mimeType = (this.data?.mime_type ?? '') as MIMES
this.type = (this.data?.type ?? '') as MIMES
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
throw e
}
Expand Down
37 changes: 37 additions & 0 deletions console/src/domain/project/hooks/useFetchProjectRole.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'
import { Role } from '@/api/const'
import { useQuery } from 'react-query'
import { useCurrentUser } from '../../../hooks/useCurrentUser'
import { listProjectRole } from '../services/project'

export function useFetchProjectRole(projectId: string) {
// eslint-disable-next-line react-hooks/exhaustive-deps
const { currentUser } = useCurrentUser()
const members = useQuery(
['fetchProjectMembers', projectId],
() => {
return listProjectRole(projectId)
},
{ refetchOnWindowFocus: false, enabled: false }
)

React.useEffect(() => {
if (projectId) {
members.refetch()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId])

const role = React.useMemo(() => {
if (!members.data) return Role.NONE
const member = members?.data?.find((m) => {
return m?.user?.id === currentUser?.id
})
if (!member) return Role.NONE
return (member?.role?.code as Role) ?? Role.NONE
}, [currentUser, members])

return {
role,
}
}
10 changes: 10 additions & 0 deletions console/src/domain/project/hooks/useProjectRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import useGlobalState from '@/hooks/global'

export const useProjectRole = () => {
const [role, setRole] = useGlobalState('role')

return {
role,
setRole,
}
}
23 changes: 0 additions & 23 deletions console/src/domain/project/hooks/useProjectRole.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions console/src/domain/project/services/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export async function createProject(data: ICreateProjectSchema): Promise<IProjec
return resp.data
}

export async function removeProject(projectId: string): Promise<string> {
const { data } = await axios.delete<string>(`/api/v1/project/${projectId}`)
return data
}

export async function changeProject(projectId: string, data: ICreateProjectSchema): Promise<IProjectSchema> {
const resp = await axios.put<IProjectSchema>(`/api/v1/project/${projectId}`, data)
return resp.data
Expand Down
2 changes: 2 additions & 0 deletions console/src/hooks/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IDatasetVersionDetailSchema } from '@/domain/dataset/schemas/datasetVer
import { IJobSchema } from '@/domain/job/schemas/job'
import { ITaskDetailSchema } from '@/domain/job/schemas/task'
import { ThemeType } from '@/theme'
import { Role } from '@/api/const'
import { IRuntimeDetailSchema } from '../domain/runtime/schemas/runtime'
import { IRuntimeVersionDetailSchema } from '../domain/runtime/schemas/runtimeVersion'

Expand Down Expand Up @@ -37,6 +38,7 @@ const initialState = {
task: undefined as ITaskDetailSchema | undefined,
taskLoading: false,
drawerExpanded: false,
role: Role.NONE as Role,
}

const { useGlobalState } = createGlobalState(initialState)
Expand Down
9 changes: 9 additions & 0 deletions console/src/i18n/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,15 @@ const locales0 = {
'Exit Fullscreen': {
en: 'Exit Fullscreen',
},
'Remove Project Success': {
en: 'Remove Project Success',
},
'Confirm Remove Project?': {
en: 'Confirm Remove Project? ',
},
'All the evaluations, datasets, models, and runtimes belong to the project will be removed.': {
en: 'All the evaluations, datasets, models, and runtimes belong to the project will be removed.',
},
}

export const locales: { [key in keyof typeof locales0]: ILocaleItem } = locales0
19 changes: 11 additions & 8 deletions console/src/pages/Dataset/DatasetVersionListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useParams } from 'react-router-dom'
import { useFetchDatasetVersions } from '@dataset/hooks/useFetchDatasetVersions'
import { toaster } from 'baseui/toast'
import { ButtonLink, TextLink } from '@/components/Link'
import { WithCurrentAuth } from '@/api/WithAuth'

export default function DatasetVersionListCard() {
const [page] = usePage()
Expand Down Expand Up @@ -57,14 +58,16 @@ export default function DatasetVersionListCard() {
datasetVersion.createdTime && formatTimestampDateTime(datasetVersion.createdTime),
datasetVersion.owner && <User user={datasetVersion.owner} />,
i ? (
<ButtonLink
key={datasetVersion.id}
onClick={() => {
handleAction(datasetVersion.id)
}}
>
{t('Revert')}
</ButtonLink>
<WithCurrentAuth id='dataset.version.revert'>
<ButtonLink
key={datasetVersion.id}
onClick={() => {
handleAction(datasetVersion.id)
}}
>
{t('Revert')}
</ButtonLink>
</WithCurrentAuth>
) : (
''
),
Expand Down
23 changes: 13 additions & 10 deletions console/src/pages/Evaluation/EvaluationListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { tableNameOfSummary } from '@/domain/datastore/utils'
import { useProject } from '@/domain/project/hooks/useProject'
import { TextLink } from '@/components/Link'
import { parseDecimal } from '@/utils'
import { WithCurrentAuth } from '@/api/WithAuth'
import EvaluationListCompare from './EvaluationListCompare'

const gridLayout = [
Expand Down Expand Up @@ -359,16 +360,18 @@ export default function EvaluationListCard() {
marginBottom: 0,
}}
extra={
<Button
startEnhancer={<IconFont type='add' kind='white' />}
size={ButtonSize.compact}
onClick={() => {
history.push('new_job')
}}
isLoading={evaluationsInfo.isLoading}
>
{t('create')}
</Button>
<WithCurrentAuth id='evaluation.create'>
<Button
startEnhancer={<IconFont type='add' kind='white' />}
size={ButtonSize.compact}
onClick={() => {
history.push('new_job')
}}
isLoading={evaluationsInfo.isLoading}
>
{t('create')}
</Button>
</WithCurrentAuth>
}
>
<StoreProvider initState={{}}>
Expand Down
4 changes: 3 additions & 1 deletion console/src/pages/Evaluation/EvaluationOverviewLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Panel } from 'baseui/accordion'
import { JobActionType, JobStatusType } from '@/domain/job/schemas/job'
import { toaster } from 'baseui/toast'
import Button from '@/components/Button'
import { WithCurrentAuth } from '@/api/WithAuth'

export interface IJobLayoutProps {
children: React.ReactNode
Expand Down Expand Up @@ -197,7 +198,8 @@ function EvaluationOverviewLayout({ children }: IJobLayoutProps) {
</>
),
}
return actions[job.jobStatus]

return <WithCurrentAuth id='evaluation.action'>{actions[job.jobStatus]}</WithCurrentAuth>
}, [job, t, handleAction])

return (
Expand Down
10 changes: 7 additions & 3 deletions console/src/pages/Model/ModelVersionListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useParams } from 'react-router-dom'
import { useFetchModelVersions } from '@model/hooks/useFetchModelVersions'
import { toaster } from 'baseui/toast'
import Button from '@/components/Button'
import { WithCurrentAuth } from '@/api/WithAuth'

export default function ModelVersionListCard() {
const [page] = usePage()
Expand All @@ -38,6 +39,7 @@ export default function ModelVersionListCard() {
},
[modelsInfo, projectId, modelId, t]
)

return (
<Card title={t('model versions')}>
<Table
Expand All @@ -50,9 +52,11 @@ export default function ModelVersionListCard() {
model.createdTime && formatTimestampDateTime(model.createdTime),
model.owner && <User user={model.owner} />,
i ? (
<Button as='link' size='mini' key={model.id} onClick={() => handleAction(model.id)}>
{t('Revert')}
</Button>
<WithCurrentAuth id='model.version.revert'>
<Button as='link' size='mini' key={model.id} onClick={() => handleAction(model.id)}>
{t('Revert')}
</Button>
</WithCurrentAuth>
) : (
''
),
Expand Down
Loading

0 comments on commit debce5f

Please sign in to comment.