Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
Make team member list update nicely
Browse files Browse the repository at this point in the history
  • Loading branch information
vdavid committed Jun 14, 2024
1 parent e9409d0 commit 102a073
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 51 deletions.
8 changes: 8 additions & 0 deletions client/web/src/cody/management/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export module Client {
return { method: 'GET', urlSuffix: '/team/current/members' }
}

export function updateTeamMember(requestBody: types.UpdateTeamMembersRequest): Call<types.ListTeamMembersResponse> {
return { method: 'PATCH', urlSuffix: '/team/current/members', requestBody }
}

// Invites

export function getInvite(teamId: string, inviteId: string): Call<types.TeamInvite> {
Expand All @@ -66,6 +70,10 @@ export module Client {
return { method: 'POST', urlSuffix: '/team/current/invites', requestBody }
}

export function resendInvite(inviteId: string): Call<Response> {
return { method: 'POST', urlSuffix: `/team/current/invites/${inviteId}/resend` }
}

export function acceptInvite(teamId: string, inviteId: string): Call<unknown> {
return { method: 'POST', urlSuffix: `/team/${teamId}/invites/${inviteId}/accept` }
}
Expand Down
13 changes: 10 additions & 3 deletions client/web/src/cody/management/api/react-query/invites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ export const useSendInvite = (): UseMutationResult<Response, Error, CreateTeamIn
})
}

export const useResendInvite = (): UseMutationResult<Response, Error, { inviteId: string }> => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({ inviteId }) => callCodyProApi(Client.resendInvite(inviteId)),
onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.invites.teamInvites() }),
})
}

export const useAcceptInvite = (): UseMutationResult<unknown, Error, { teamId: string; inviteId: string }> => {
const queryClient = useQueryClient()
return useMutation({
Expand All @@ -57,11 +65,10 @@ export const useAcceptInvite = (): UseMutationResult<unknown, Error, { teamId: s
})
}

export const useCancelInvite = (): UseMutationResult<unknown, Error, { teamId: string; inviteId: string }> => {
export const useCancelInvite = (): UseMutationResult<Response, Error, { teamId: string; inviteId: string }> => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({ teamId, inviteId }) => callCodyProApi(Client.cancelInvite(teamId, inviteId)),
onSuccess: (_, { teamId, inviteId }) =>
queryClient.invalidateQueries({ queryKey: queryKeys.invites.invite(teamId, inviteId) }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.invites.all }),
})
}
18 changes: 16 additions & 2 deletions client/web/src/cody/management/api/react-query/teams.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { useQuery, type UseQueryResult } from '@tanstack/react-query'
import {
useMutation,
useQuery,
useQueryClient,
type UseQueryResult,
type UseMutationResult,
} from '@tanstack/react-query'

import { Client } from '../client'
import type { ListTeamMembersResponse } from '../teamMembers'
import type { ListTeamMembersResponse, UpdateTeamMembersRequest } from '../types'

import { callCodyProApi } from './callCodyProApi'
import { queryKeys } from './queryKeys'
Expand All @@ -14,3 +20,11 @@ export const useTeamMembers = (): UseQueryResult<ListTeamMembersResponse | undef
return response?.json()
},
})

export const useUpdateTeamMember = (): UseMutationResult<Response, Error, UpdateTeamMembersRequest> => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async requestBody => callCodyProApi(Client.updateTeamMember(requestBody)),
onSettled: () => queryClient.invalidateQueries({ queryKey: queryKeys.teams.teamMembers() }),
})
}
2 changes: 1 addition & 1 deletion client/web/src/cody/management/api/teamMembers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface ListTeamMembersResponse {
}

export interface UpdateTeamMembersRequest {
addMembver?: TeamMemberRef
addMember?: TeamMemberRef
removeMember?: TeamMemberRef
updateMemberRole?: TeamMemberRef
}
20 changes: 11 additions & 9 deletions client/web/src/cody/team/CodyManageTeamPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,22 @@ const AuthenticatedCodyManageTeamPage: React.FunctionComponent<CodyManageTeamPag
</CodyAlert>
)}

{isAdmin && !!remainingInviteCount && (
{isAdmin && !!remainingInviteCount && !!subscriptionSummaryQueryResult.data && (
<InviteUsers
teamId={subscriptionSummaryQueryResult.data?.teamId}
teamId={subscriptionSummaryQueryResult.data.teamId}
remainingInviteCount={remainingInviteCount}
telemetryRecorder={telemetryRecorder}
/>
)}
<TeamMemberList
teamId={subscriptionSummaryQueryResult.data?.teamId ?? null}
teamMembers={teamMembers || []}
invites={teamInvites || []}
isAdmin={isAdmin}
telemetryRecorder={telemetryRecorder}
/>
{!!subscriptionSummaryQueryResult.data && (
<TeamMemberList
teamId={subscriptionSummaryQueryResult.data.teamId}
teamMembers={teamMembers || []}
invites={teamInvites || []}
isAdmin={isAdmin}
telemetryRecorder={telemetryRecorder}
/>
)}
</Page>
</>
)
Expand Down
2 changes: 1 addition & 1 deletion client/web/src/cody/team/InviteUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useSendInvite } from '../management/api/react-query/invites'
import { isValidEmailAddress } from '../util'

interface InviteUsersProps extends TelemetryV2Props {
teamId: string | undefined
teamId: string
remainingInviteCount: number
}

Expand Down
24 changes: 14 additions & 10 deletions client/web/src/cody/team/TeamMemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { H2, Text, Badge, Link, ButtonLink } from '@sourcegraph/wildcard'

import { CodyAlert } from '../components/CodyAlert'
import { CodyContainer } from '../components/CodyContainer'
import { useCancelInvite, useResendInvite } from '../management/api/react-query/invites'
import { useUpdateTeamMember } from '../management/api/react-query/teams'
import type { TeamMember, TeamInvite } from '../management/api/types'
import { requestSSC } from '../util'

import styles from './TeamMemberList.module.scss'

interface TeamMemberListProps extends TelemetryV2Props {
teamId: string | null
teamId: string
teamMembers: TeamMember[]
invites: Omit<TeamInvite, 'sentBy'>[]
isAdmin: boolean
Expand All @@ -41,6 +42,9 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
}) => {
const [loading, setLoading] = useState(false)
const [actionResult, setActionResult] = useState<{ message: string; isError: boolean } | null>(null)
const updateTeamMemberMutation = useUpdateTeamMember()
const cancelInviteMutation = useCancelInvite()
const resendInviteMutation = useResendInvite()
const updateRole = useCallback(
async (accountId: string, newRole: 'member' | 'admin'): Promise<void> => {
if (!loading) {
Expand All @@ -51,7 +55,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
})

try {
const response = await requestSSC('/team/current/members', 'PATCH', {
const response = await updateTeamMemberMutation.mutateAsync.call(undefined, {
updateMemberRole: { accountId, teamRole: newRole },
})
if (!response.ok) {
Expand All @@ -73,7 +77,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
}
}
},
[loading, telemetryRecorder, teamId]
[loading, telemetryRecorder, teamId, updateTeamMemberMutation.mutateAsync]
)

const revokeInvite = useCallback(
Expand All @@ -83,7 +87,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
setLoading(true)
telemetryRecorder.recordEvent('cody.team.revokeInvite', 'click', { privateMetadata: { teamId } })

const response = await requestSSC(`/team/current/invites/${inviteId}/cancel`, 'POST')
const response = await cancelInviteMutation.mutateAsync.call(undefined, { teamId, inviteId })
if (!response.ok) {
setLoading(false)
setActionResult({
Expand All @@ -96,7 +100,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
}
}
},
[loading, telemetryRecorder, teamId]
[loading, telemetryRecorder, teamId, cancelInviteMutation.mutateAsync]
)

const resendInvite = useCallback(
Expand All @@ -106,7 +110,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
setLoading(true)
telemetryRecorder.recordEvent('cody.team.resendInvite', 'click', { privateMetadata: { teamId } })

const response = await requestSSC(`/team/current/invites/${inviteId}/resend`, 'POST')
const response = await resendInviteMutation.mutateAsync.call(undefined, { inviteId })
if (!response.ok) {
setLoading(false)
setActionResult({
Expand All @@ -121,7 +125,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({

telemetryRecorder.recordEvent('cody.team.resendInvite', 'click', { privateMetadata: { teamId } })
},
[loading, telemetryRecorder, teamId]
[loading, telemetryRecorder, teamId, resendInviteMutation.mutateAsync]
)

const removeMember = useCallback(
Expand All @@ -130,7 +134,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
setLoading(true)
telemetryRecorder.recordEvent('cody.team.removeMember', 'click', { privateMetadata: { teamId } })

const response = await requestSSC('/team/current/members', 'PATCH', {
const response = await updateTeamMemberMutation.mutateAsync.call(undefined, {
removeMember: { accountId, teamRole: 'member' },
})
if (!response.ok) {
Expand All @@ -145,7 +149,7 @@ export const TeamMemberList: FunctionComponent<TeamMemberListProps> = ({
}
}
},
[telemetryRecorder, teamId, loading]
[loading, telemetryRecorder, teamId, updateTeamMemberMutation.mutateAsync]
)

const adminCount = useMemo(() => teamMembers?.filter(member => member.role === 'admin').length ?? 0, [teamMembers])
Expand Down
25 changes: 0 additions & 25 deletions client/web/src/cody/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,3 @@ export function isValidEmailAddress(emailAddress: string): boolean {
* and keep in mind that the backend validation has the final say, validation in the web app is only for UX improvement.
*/
const emailRegex = /^[^@]+@[^@]+\.[^@]+$/

/**
* So the request is kinda made to two backends. Dotcom's `.api/ssc/proxy` endpoint
* exchanges the Sourcegraph session credentials for a SAMS access token
* and then proxy the request to the SSC backend.
* @param sscUrl The SSC API URL to call. Example: "/checkout/session".
* @param method E.g. "POST".
* @param params The body to send to the SSC API. Will be JSON-encoded.
* In the case of GET and HEAD, use the query string instead.
*/
export function requestSSC(sscUrl: string, method: string, params?: object): Promise<Response> {
// /.api/ssc/proxy endpoint exchanges the Sourcegraph session credentials for a SAMS access token.
// And then proxy the request onto the SSC backend, which will actually create the
// checkout session.
return fetch(`/.api/ssc/proxy${sscUrl}`, {
// Pass along the "sgs" session cookie to identify the caller.
credentials: 'same-origin',
headers: {
...window.context.xhrHeaders,
'Content-Type': 'application/json',
},
method,
...(!['GET', 'HEAD'].includes(method) && params ? { body: JSON.stringify(params) } : null),
})
}

0 comments on commit 102a073

Please sign in to comment.