Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions app/pages/OrgsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { useMemo } from 'react'
import { Link, useLocation, useNavigate } from 'react-router-dom'

import type { Organization } from '@oxide/api'
import { canWrite } from '@oxide/api'
import { useEffectiveSiloRole } from '@oxide/api'
import { apiQueryClient } from '@oxide/api'
import { useApiQueryClient } from '@oxide/api'
import { useApiMutation, useApiQuery } from '@oxide/api'
import type { MenuAction } from '@oxide/table'
import { DateCell, linkCell, useQueryTable } from '@oxide/table'
import {
Button,
EmptyMessage,
Folder24Icon,
PageHeader,
Expand All @@ -33,7 +36,10 @@ const EmptyState = () => (
)

OrgsPage.loader = async () => {
await apiQueryClient.prefetchQuery('organizationList', { query: { limit: 10 } })
await Promise.all([
apiQueryClient.prefetchQuery('organizationList', { query: { limit: 10 } }),
apiQueryClient.prefetchQuery('policyView', {}),
])

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefetch silo policy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth a comment?

}

interface OrgsPageProps {
Expand All @@ -47,6 +53,8 @@ export default function OrgsPage({ modal }: OrgsPageProps) {
const { Table, Column } = useQueryTable('organizationList', {})
const queryClient = useApiQueryClient()

const siloRole = useEffectiveSiloRole()

const { data: orgs } = useApiQuery('organizationList', {
query: { limit: 10 }, // to have same params as QueryTable
})
Expand Down Expand Up @@ -92,9 +100,18 @@ export default function OrgsPage({ modal }: OrgsPageProps) {
<PageTitle icon={<Folder24Icon />}>Organizations</PageTitle>
</PageHeader>
<TableActions>
<Link to={pb.orgNew()} className={buttonStyle({ variant: 'default', size: 'sm' })}>
New Organization
</Link>
{canWrite(siloRole) ? (
<Link
to={pb.orgNew()}
className={buttonStyle({ variant: 'default', size: 'sm' })}
>
New Organization
</Link>
) : (
<Button size="sm" disabled>
New Organization
</Button>
)}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearly this is too noisy — we'd want to wrap this up into a disable-able custom link component with a disabled prop and a message to show in a tooltip when disabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled links are a bit of an odd pattern. We'd want to change the pointer and ensure we had aria-disabled present. I think it's definitely something we'd want to try out on a screen reader just to see what it resulted in.

</TableActions>
<Table emptyState={<EmptyState />} makeActions={makeActions}>
<Column accessor="name" cell={linkCell((orgName) => pb.projects({ orgName }))} />
Expand Down
18 changes: 18 additions & 0 deletions libs/api/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,21 @@ export function userRoleFromPolicies(
.map((ra) => ra.roleName)
return getEffectiveRole(myRoles) || null
}

// Not having pop-in when the requests resolve depends on the right prefetches
// having happened. This is probably too fragile. We need to explore this.
// https://tkdodo.eu/blog/react-query-meets-react-router#a-typescript-tip
export function useEffectiveSiloRole() {
const { data: me } = useApiQuery('sessionMe', {})
const { data: myGroups } = useApiQuery('sessionMeGroups', {})
const { data: siloPolicy } = useApiQuery('policyView', {})
return me && myGroups && siloPolicy
? userRoleFromPolicies(me, myGroups.items, [siloPolicy])
: null
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these requests are prefetched and therefore never undefined, but we don't know it statically


const readRoles = new Set<RoleKey>(['admin', 'collaborator', 'viewer'])
const writeRoles = new Set<RoleKey>(['admin', 'collaborator'])

export const canRead = (role: RoleKey | null) => (role ? readRoles.has(role) : false)
export const canWrite = (role: RoleKey | null) => (role ? writeRoles.has(role) : false)