v2t: add v2t to src/teams (#61808)

This commit is contained in:
Dan Adler 2024-05-08 04:59:27 -07:00 committed by GitHub
parent 36c2ed91f4
commit ee376b66e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 178 additions and 57 deletions

View File

@ -282,7 +282,11 @@ export const routes: RouteObject[] = [
},
{
path: PageRoutes.Teams,
element: <LegacyRoute render={props => <TeamsArea {...props} />} />,
element: (
<LegacyRoute
render={props => <TeamsArea {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />}
/>
),
},
{
path: PageRoutes.Organizations,

View File

@ -2,6 +2,7 @@ import * as React from 'react'
import { Routes, Route } from 'react-router-dom'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent'
import { LoadingSpinner } from '@sourcegraph/wildcard'
@ -21,7 +22,7 @@ const TeamListPage = lazyComponent<TeamListPageProps, 'TeamListPage'>(
)
const NewTeamPage = lazyComponent<NewTeamPageProps, 'NewTeamPage'>(() => import('./new/NewTeamPage'), 'NewTeamPage')
export interface Props {
export interface Props extends TelemetryV2Props {
authenticatedUser: AuthenticatedUser
isSourcegraphDotCom: boolean
}
@ -37,7 +38,7 @@ const AuthenticatedTeamsArea: React.FunctionComponent<React.PropsWithChildren<Pr
return (
<React.Suspense fallback={<LoadingSpinner className="m-2" />}>
<Routes>
<Route path="new" element={<NewTeamPage />} errorElement={<RouteError />} />
<Route path="new" element={<NewTeamPage {...props} />} errorElement={<RouteError />} />
<Route path="" element={<TeamListPage {...props} />} errorElement={<RouteError />} />
<Route path=":teamName/*" element={<TeamArea {...props} />} errorElement={<RouteError />} />
<Route path="*" element={<NotFoundPage pageType="team" />} errorElement={<RouteError />} />

View File

@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react'
import { logger } from '@sourcegraph/common'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, ErrorAlert, Form, H3, Label, Modal } from '@sourcegraph/wildcard'
import { LoaderButton } from '../../components/LoaderButton'
@ -9,7 +10,7 @@ import { ParentTeamSelect } from '../new/team-select/ParentTeamSelect'
import { useAssignParentTeam } from './backend'
interface EditParentTeamModalProps {
interface EditParentTeamModalProps extends TelemetryV2Props {
teamID: Scalars['ID']
teamName: string
parentTeamName: string | null
@ -24,6 +25,7 @@ export const EditParentTeamModal: React.FunctionComponent<React.PropsWithChildre
parentTeamName: currentParentTeamName,
onCancel,
afterEdit,
telemetryRecorder,
}) => {
const labelId = 'editParentTeam'
@ -42,13 +44,15 @@ export const EditParentTeamModal: React.FunctionComponent<React.PropsWithChildre
}
try {
await editTeam({ variables: { id: teamID, parentTeamName: parentTeam } })
telemetryRecorder.recordEvent('team.parentTeam', 'edit')
afterEdit()
} catch (error) {
// Non-request error. API errors will be available under `error` above.
logger.error(error)
telemetryRecorder.recordEvent('team.parentTeam', 'editFail')
}
},
[afterEdit, teamID, parentTeam, editTeam]
[afterEdit, teamID, parentTeam, editTeam, telemetryRecorder]
)
return (

View File

@ -1,6 +1,7 @@
import React, { useCallback } from 'react'
import { logger } from '@sourcegraph/common'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, ErrorAlert, Form, H3, Modal, Text } from '@sourcegraph/wildcard'
import { LoaderButton } from '../../components/LoaderButton'
@ -8,7 +9,7 @@ import type { Scalars } from '../../graphql-operations'
import { useRemoveParentTeam } from './backend'
interface RemoveParentTeamModalProps {
interface RemoveParentTeamModalProps extends TelemetryV2Props {
teamID: Scalars['ID']
teamName: string
onCancel: () => void
@ -20,6 +21,7 @@ export const RemoveParentTeamModal: React.FunctionComponent<React.PropsWithChild
teamName,
onCancel,
afterEdit,
telemetryRecorder,
}) => {
const labelId = 'removeParentTeam'
@ -33,13 +35,15 @@ export const RemoveParentTeamModal: React.FunctionComponent<React.PropsWithChild
}
try {
await editTeam({ variables: { id: teamID } })
telemetryRecorder.recordEvent('team.parentTeam', 'remove')
afterEdit()
} catch (error) {
telemetryRecorder.recordEvent('team.parentTeam', 'removeFail')
// Non-request error. API errors will be available under `error` above.
logger.error(error)
}
},
[afterEdit, teamID, editTeam]
[afterEdit, teamID, editTeam, telemetryRecorder]
)
return (

View File

@ -4,6 +4,7 @@ import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import MapSearchIcon from 'mdi-react/MapSearchIcon'
import { Route, Routes, useParams } from 'react-router-dom'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent'
import { LoadingSpinner, ErrorMessage } from '@sourcegraph/wildcard'
@ -37,7 +38,7 @@ const NotFoundPage: React.FunctionComponent<React.PropsWithChildren<unknown>> =
export interface TeamAreaRoute extends RouteV6Descriptor<TeamAreaRouteContext> {}
export interface TeamAreaProps {
export interface TeamAreaProps extends TelemetryV2Props {
/**
* The currently authenticated user.
*/
@ -47,7 +48,7 @@ export interface TeamAreaProps {
/**
* Properties passed to all page components in the team area.
*/
export interface TeamAreaRouteContext {
export interface TeamAreaRouteContext extends TelemetryV2Props {
/** The team that is the subject of the page. */
team: TeamAreaTeamFields
@ -58,7 +59,7 @@ export interface TeamAreaRouteContext {
authenticatedUser: AuthenticatedUser
}
export const TeamArea: React.FunctionComponent<TeamAreaProps> = ({ authenticatedUser }) => {
export const TeamArea: React.FunctionComponent<TeamAreaProps> = ({ authenticatedUser, telemetryRecorder }) => {
const { teamName } = useParams<{ teamName: string }>()
const { data, loading, error, refetch } = useTeam(teamName!)
@ -84,6 +85,7 @@ export const TeamArea: React.FunctionComponent<TeamAreaProps> = ({ authenticated
authenticatedUser,
team: data.team,
onTeamUpdate: refetch,
telemetryRecorder,
}
return (

View File

@ -1,19 +1,26 @@
import { useEffect } from 'react'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Page } from '../../components/Page'
import type { TeamAreaTeamFields } from '../../graphql-operations'
import { ChildTeamListPage } from '../list/TeamListPage'
import { TeamHeader } from './TeamHeader'
export interface TeamChildTeamsPageProps {
export interface TeamChildTeamsPageProps extends TelemetryV2Props {
/** The team that is the subject of the page. */
team: TeamAreaTeamFields
}
export const TeamChildTeamsPage: React.FunctionComponent<TeamChildTeamsPageProps> = ({ team }) => (
<Page>
<TeamHeader team={team} className="mb-3" />
<div className="container">
<ChildTeamListPage parentTeam={team.name} />
</div>
</Page>
)
export const TeamChildTeamsPage: React.FunctionComponent<TeamChildTeamsPageProps> = ({ team, telemetryRecorder }) => {
useEffect(() => telemetryRecorder.recordEvent('team.childTeams', 'view'), [telemetryRecorder])
return (
<Page>
<TeamHeader team={team} className="mb-3" />
<div className="container">
<ChildTeamListPage parentTeam={team.name} telemetryRecorder={telemetryRecorder} />
</div>
</Page>
)
}

View File

@ -1,19 +1,26 @@
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Page } from '../../components/Page'
import type { TeamAreaTeamFields } from '../../graphql-operations'
import { TeamMemberListPage } from '../members/TeamMemberListPage'
import { TeamHeader } from './TeamHeader'
export interface TeamMembersPageProps {
export interface TeamMembersPageProps extends TelemetryV2Props {
/** The team that is the subject of the page. */
team: TeamAreaTeamFields
}
export const TeamMembersPage: React.FunctionComponent<TeamMembersPageProps> = ({ team }) => (
export const TeamMembersPage: React.FunctionComponent<TeamMembersPageProps> = ({ team, telemetryRecorder }) => (
<Page>
<TeamHeader team={team} className="mb-3" />
<div className="container">
<TeamMemberListPage teamID={team.id} teamName={team.name} viewerCanAdminister={team.viewerCanAdminister} />
<TeamMemberListPage
teamID={team.id}
teamName={team.name}
viewerCanAdminister={team.viewerCanAdminister}
telemetryRecorder={telemetryRecorder}
/>
</div>
</Page>
)

View File

@ -1,10 +1,11 @@
import React, { useCallback, useState } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { mdiDelete, mdiPencil } from '@mdi/js'
import { logger } from '@sourcegraph/common'
import { TeamAvatar } from '@sourcegraph/shared/src/components/TeamAvatar'
import { UserAvatar } from '@sourcegraph/shared/src/components/UserAvatar'
import { type TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, ErrorAlert, Form, H3, Icon, Input, Label, Link, Modal, Text } from '@sourcegraph/wildcard'
import { TEAM_DISPLAY_NAME_MAX_LENGTH } from '..'
@ -17,7 +18,7 @@ import { EditParentTeamModal } from './EditParentTeamModal'
import { RemoveParentTeamModal } from './RemoveParentTeamModal'
import { TeamHeader } from './TeamHeader'
export interface TeamProfilePageProps {
export interface TeamProfilePageProps extends TelemetryV2Props {
/** The team that is the subject of the page. */
team: TeamAreaTeamFields
@ -25,11 +26,17 @@ export interface TeamProfilePageProps {
onTeamUpdate: () => void
}
export const TeamProfilePage: React.FunctionComponent<TeamProfilePageProps> = ({ team, onTeamUpdate }) => {
export const TeamProfilePage: React.FunctionComponent<TeamProfilePageProps> = ({
team,
onTeamUpdate,
telemetryRecorder,
}) => {
const [openModal, setOpenModal] = useState<
'edit-display-name' | 'edit-parent-team' | 'remove-parent-team' | undefined
>()
useEffect(() => telemetryRecorder.recordEvent('team.profile', 'view'), [telemetryRecorder])
const onEditDisplayName = useCallback<React.MouseEventHandler>(event => {
event.preventDefault()
setOpenModal('edit-display-name')
@ -116,6 +123,7 @@ export const TeamProfilePage: React.FunctionComponent<TeamProfilePageProps> = ({
{openModal === 'edit-parent-team' && (
<EditParentTeamModal
telemetryRecorder={telemetryRecorder}
onCancel={closeModal}
afterEdit={afterAction}
teamID={team.id}
@ -126,6 +134,7 @@ export const TeamProfilePage: React.FunctionComponent<TeamProfilePageProps> = ({
{openModal === 'remove-parent-team' && (
<RemoveParentTeamModal
telemetryRecorder={telemetryRecorder}
onCancel={closeModal}
afterEdit={afterAction}
teamID={team.id}

View File

@ -1,3 +1,4 @@
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
import { mockAuthenticatedUser } from '@sourcegraph/shared/src/testing/searchContexts/testHelpers'
import type { TeamAreaRouteContext } from './TeamArea'
@ -30,5 +31,6 @@ export const testContext: TeamAreaRouteContext = {
url: '/users/alice',
},
},
telemetryRecorder: noOpTelemetryRecorder,
onTeamUpdate: () => {},
}

View File

@ -1,6 +1,7 @@
import React, { useCallback } from 'react'
import { logger } from '@sourcegraph/common'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, H3, Modal, ErrorAlert } from '@sourcegraph/wildcard'
import { LoaderButton } from '../../components/LoaderButton'
@ -8,7 +9,7 @@ import type { ListTeamFields } from '../../graphql-operations'
import { useDeleteTeam } from './backend'
export interface DeleteTeamModalProps {
export interface DeleteTeamModalProps extends TelemetryV2Props {
team: ListTeamFields
onCancel: () => void
@ -19,6 +20,7 @@ export const DeleteTeamModal: React.FunctionComponent<React.PropsWithChildren<De
team,
onCancel,
afterDelete,
telemetryRecorder,
}) => {
const labelId = 'deleteTeam'
@ -31,13 +33,15 @@ export const DeleteTeamModal: React.FunctionComponent<React.PropsWithChildren<De
try {
await deleteTeam({ variables: { id: team.id } })
telemetryRecorder.recordEvent('team', 'delete')
afterDelete()
} catch (error) {
// Non-request error. API errors will be available under `error` above.
logger.error(error)
telemetryRecorder.recordEvent('team', 'deleteFail')
}
},
[afterDelete, team.id, deleteTeam]
[afterDelete, team.id, deleteTeam, telemetryRecorder]
)
return (

View File

@ -2,6 +2,7 @@ import type { MockedResponse } from '@apollo/client/testing'
import type { Meta, StoryFn } from '@storybook/react'
import { getDocumentNode } from '@sourcegraph/http-client'
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
import { WebStory } from '../../components/WebStory'
import type { ListTeamsResult } from '../../graphql-operations'
@ -39,7 +40,9 @@ export const EmptyList: StoryFn = function EmptyList() {
},
}
return <WebStory mocks={[mockResponse]}>{() => <TeamListPage />}</WebStory>
return (
<WebStory mocks={[mockResponse]}>{() => <TeamListPage telemetryRecorder={noOpTelemetryRecorder} />}</WebStory>
)
}
export const ListWithItems: StoryFn = function ListWithItems() {
@ -103,5 +106,7 @@ export const ListWithItems: StoryFn = function ListWithItems() {
},
}
return <WebStory mocks={[mockResponse]}>{() => <TeamListPage />}</WebStory>
return (
<WebStory mocks={[mockResponse]}>{() => <TeamListPage telemetryRecorder={noOpTelemetryRecorder} />}</WebStory>
)
}

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { mdiAccountMultiple, mdiPlus } from '@mdi/js'
import classNames from 'classnames'
import { type TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, Link, Icon, PageHeader, Container, useDebounce, ProductStatusBadge } from '@sourcegraph/wildcard'
import type { UseShowMorePaginationResult } from '../../components/FilteredConnection/hooks/useShowMorePagination'
@ -23,17 +24,21 @@ import type { ListTeamFields, ListTeamsOfParentResult, ListTeamsResult } from '.
import { useChildTeams, useTeams } from './backend'
import { TeamNode } from './TeamNode'
export interface TeamListPageProps {}
export interface TeamListPageProps extends TelemetryV2Props {}
/**
* A page displaying the teams on this site.
*/
export const TeamListPage: React.FunctionComponent<React.PropsWithChildren<TeamListPageProps>> = () => {
export const TeamListPage: React.FunctionComponent<React.PropsWithChildren<TeamListPageProps>> = ({
telemetryRecorder,
}) => {
const [searchValue, setSearchValue] = useState('')
const query = useDebounce(searchValue, 200)
const connection = useTeams(query)
useEffect(() => telemetryRecorder.recordEvent('teams.list', 'view'), [telemetryRecorder])
return (
<Page className="mb-3">
<PageTitle title="Teams" />
@ -61,13 +66,19 @@ export const TeamListPage: React.FunctionComponent<React.PropsWithChildren<TeamL
</PageHeader>
<Container className="mb-3">
<TeamList searchValue={searchValue} setSearchValue={setSearchValue} query={query} {...connection} />
<TeamList
searchValue={searchValue}
setSearchValue={setSearchValue}
query={query}
telemetryRecorder={telemetryRecorder}
{...connection}
/>
</Container>
</Page>
)
}
export interface ChildTeamListPageProps {
export interface ChildTeamListPageProps extends TelemetryV2Props {
parentTeam: string
}
@ -76,6 +87,7 @@ export interface ChildTeamListPageProps {
*/
export const ChildTeamListPage: React.FunctionComponent<React.PropsWithChildren<ChildTeamListPageProps>> = ({
parentTeam,
telemetryRecorder,
}) => {
const [searchValue, setSearchValue] = useState('')
const query = useDebounce(searchValue, 200)
@ -90,13 +102,21 @@ export const ChildTeamListPage: React.FunctionComponent<React.PropsWithChildren<
</Button>
</div>
<Container className="mb-3">
<TeamList searchValue={searchValue} setSearchValue={setSearchValue} query={query} {...connection} />
<TeamList
searchValue={searchValue}
setSearchValue={setSearchValue}
query={query}
telemetryRecorder={telemetryRecorder}
{...connection}
/>
</Container>
</>
)
}
interface TeamListProps extends UseShowMorePaginationResult<ListTeamsResult | ListTeamsOfParentResult, ListTeamFields> {
interface TeamListProps
extends UseShowMorePaginationResult<ListTeamsResult | ListTeamsOfParentResult, ListTeamFields>,
TelemetryV2Props {
searchValue: string
setSearchValue: (value: string) => void
query: string
@ -114,6 +134,7 @@ export const TeamList: React.FunctionComponent<TeamListProps> = ({
setSearchValue,
query,
className,
telemetryRecorder,
}) => (
<ConnectionContainer className={classNames(className)}>
<ConnectionForm
@ -126,7 +147,7 @@ export const TeamList: React.FunctionComponent<TeamListProps> = ({
{loading && !connection && <ConnectionLoading />}
<ConnectionList as="ul" className="list-group" aria-label="Teams">
{connection?.nodes?.map(node => (
<TeamNode key={node.id} node={node} refetchAll={refetchAll} />
<TeamNode key={node.id} node={node} refetchAll={refetchAll} telemetryRecorder={telemetryRecorder} />
))}
</ConnectionList>
{connection && (

View File

@ -5,6 +5,7 @@ import classNames from 'classnames'
import { pluralize } from '@sourcegraph/common'
import { TeamAvatar } from '@sourcegraph/shared/src/components/TeamAvatar'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, Link, Icon, Tooltip, useDebounce } from '@sourcegraph/wildcard'
import { Collapsible } from '../../components/Collapsible'
@ -16,14 +17,18 @@ import { TeamList } from './TeamListPage'
import styles from './TeamNode.module.scss'
export interface TeamNodeProps {
export interface TeamNodeProps extends TelemetryV2Props {
node: ListTeamFields
refetchAll: () => void
}
type OpenModal = 'delete'
export const TeamNode: React.FunctionComponent<React.PropsWithChildren<TeamNodeProps>> = ({ node, refetchAll }) => {
export const TeamNode: React.FunctionComponent<React.PropsWithChildren<TeamNodeProps>> = ({
node,
refetchAll,
telemetryRecorder,
}) => {
const [openModal, setOpenModal] = useState<OpenModal | undefined>()
const onClickDelete = useCallback<React.MouseEventHandler>(event => {
@ -54,7 +59,7 @@ export const TeamNode: React.FunctionComponent<React.PropsWithChildren<TeamNodeP
wholeTitleClickable={false}
defaultExpanded={false}
>
<ChildTeamList parentTeam={node.name} />
<ChildTeamList parentTeam={node.name} telemetryRecorder={telemetryRecorder} />
</Collapsible>
)}
{node.childTeams.totalCount === 0 && (
@ -66,7 +71,14 @@ export const TeamNode: React.FunctionComponent<React.PropsWithChildren<TeamNodeP
)}
</li>
{openModal === 'delete' && <DeleteTeamModal onCancel={closeModal} afterDelete={afterAction} team={node} />}
{openModal === 'delete' && (
<DeleteTeamModal
onCancel={closeModal}
afterDelete={afterAction}
team={node}
telemetryRecorder={telemetryRecorder}
/>
)}
</>
)
}
@ -109,7 +121,11 @@ const NodeContent: React.FunctionComponent<NodeContentProps> = ({ node, onClickD
</div>
)
const ChildTeamList: React.FunctionComponent<{ parentTeam: string }> = ({ parentTeam }) => {
interface ChildTeamListProps extends TelemetryV2Props {
parentTeam: string
}
const ChildTeamList: React.FunctionComponent<ChildTeamListProps> = ({ parentTeam, telemetryRecorder }) => {
const [searchValue, setSearchValue] = useState('')
const query = useDebounce(searchValue, 200)
@ -120,6 +136,7 @@ const ChildTeamList: React.FunctionComponent<{ parentTeam: string }> = ({ parent
searchValue={searchValue}
setSearchValue={setSearchValue}
query={query}
telemetryRecorder={telemetryRecorder}
{...connection}
className={classNames(styles.childTeams, 'py-2 pl-3 pr-2')}
/>

View File

@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react'
import { logger } from '@sourcegraph/common'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, H3, Modal, ErrorAlert, Form, Label } from '@sourcegraph/wildcard'
import { LoaderButton } from '../../components/LoaderButton'
@ -9,7 +10,7 @@ import type { Scalars } from '../../graphql-operations'
import { useAddTeamMembers } from './backend'
import { UserSelect } from './user-select/UserSelect'
export interface AddTeamMemberModalProps {
export interface AddTeamMemberModalProps extends TelemetryV2Props {
teamID: Scalars['ID']
teamName: string
@ -22,6 +23,7 @@ export const AddTeamMemberModal: React.FunctionComponent<React.PropsWithChildren
teamName,
onCancel,
afterAdd,
telemetryRecorder,
}) => {
const labelId = 'addTeamMember'
@ -42,13 +44,15 @@ export const AddTeamMemberModal: React.FunctionComponent<React.PropsWithChildren
variables: { team: teamID, members: selectedMembers.map(member => ({ userID: member })) },
})
telemetryRecorder.recordEvent('team.members', 'add')
afterAdd()
} catch (error) {
// Non-request error. API errors will be available under `error` above.
logger.error(error)
telemetryRecorder.recordEvent('team.members', 'addFail')
}
},
[afterAdd, teamID, selectedMembers, addMembers]
[afterAdd, teamID, selectedMembers, addMembers, telemetryRecorder]
)
return (

View File

@ -1,6 +1,7 @@
import React, { useCallback } from 'react'
import { logger } from '@sourcegraph/common'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, H3, Modal, ErrorAlert } from '@sourcegraph/wildcard'
import { LoaderButton } from '../../components/LoaderButton'
@ -8,7 +9,7 @@ import type { ListTeamMemberFields, Scalars } from '../../graphql-operations'
import { useRemoveTeamMembers } from './backend'
export interface RemoveTeamMemberModalProps {
export interface RemoveTeamMemberModalProps extends TelemetryV2Props {
teamID: Scalars['ID']
teamName: string
member: ListTeamMemberFields
@ -23,6 +24,7 @@ export const RemoveTeamMemberModal: React.FunctionComponent<React.PropsWithChild
member,
onCancel,
afterRemove,
telemetryRecorder,
}) => {
const labelId = 'removeTeamMember'
@ -35,13 +37,15 @@ export const RemoveTeamMemberModal: React.FunctionComponent<React.PropsWithChild
try {
await removeMembers({ variables: { team: teamID, members: [{ userID: member.id }] } })
telemetryRecorder.recordEvent('team.members', 'remove')
afterRemove()
} catch (error) {
// Non-request error. API errors will be available under `error` above.
logger.error(error)
telemetryRecorder.recordEvent('team.members', 'removeFail')
}
},
[afterRemove, teamID, member.id, removeMembers]
[afterRemove, teamID, member.id, removeMembers, telemetryRecorder]
)
return (

View File

@ -1,7 +1,8 @@
import React, { useCallback, useState } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { mdiPlus } from '@mdi/js'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, Container, Icon, useDebounce } from '@sourcegraph/wildcard'
import {
@ -20,7 +21,7 @@ import { AddTeamMemberModal } from './AddTeamMemberModal'
import { useTeamMembers } from './backend'
import { TeamMemberNode } from './TeamMemberNode'
interface Props {
interface Props extends TelemetryV2Props {
teamID: Scalars['ID']
teamName: string
viewerCanAdminister: boolean
@ -35,6 +36,7 @@ export const TeamMemberListPage: React.FunctionComponent<React.PropsWithChildren
teamID,
teamName,
viewerCanAdminister,
telemetryRecorder,
}) => {
const [openModal, setOpenModal] = useState<OpenModal | undefined>()
const [searchValue, setSearchValue] = useState('')
@ -54,6 +56,8 @@ export const TeamMemberListPage: React.FunctionComponent<React.PropsWithChildren
refetchAll()
}, [refetchAll])
useEffect(() => telemetryRecorder.recordEvent('team.members', 'view'), [telemetryRecorder])
return (
<>
<div className="d-flex justify-content-end mb-3">
@ -80,6 +84,7 @@ export const TeamMemberListPage: React.FunctionComponent<React.PropsWithChildren
teamName={teamName}
refetchAll={refetchAll}
viewerCanAdminister={viewerCanAdminister}
telemetryRecorder={telemetryRecorder}
/>
))}
</ConnectionList>
@ -100,7 +105,13 @@ export const TeamMemberListPage: React.FunctionComponent<React.PropsWithChildren
</Container>
{openModal === 'add-member' && (
<AddTeamMemberModal onCancel={closeModal} afterAdd={afterAction} teamID={teamID} teamName={teamName} />
<AddTeamMemberModal
onCancel={closeModal}
afterAdd={afterAction}
teamID={teamID}
teamName={teamName}
telemetryRecorder={telemetryRecorder}
/>
)}
</>
)

View File

@ -3,6 +3,7 @@ import React, { useCallback, useState } from 'react'
import { mdiDelete } from '@mdi/js'
import classNames from 'classnames'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Button, Link, Icon, Tooltip } from '@sourcegraph/wildcard'
import type { ListTeamMemberFields, Scalars } from '../../graphql-operations'
@ -11,7 +12,7 @@ import { RemoveTeamMemberModal } from './RemoveTeamMemberModal'
import styles from './TeamMemberNode.module.scss'
export interface TeamMemberNodeProps {
export interface TeamMemberNodeProps extends TelemetryV2Props {
viewerCanAdminister: boolean
teamID: Scalars['ID']
teamName: string
@ -27,6 +28,7 @@ export const TeamMemberNode: React.FunctionComponent<React.PropsWithChildren<Tea
teamName,
node,
refetchAll,
telemetryRecorder,
}) => {
const [openModal, setOpenModal] = useState<OpenModal | undefined>()
@ -76,6 +78,7 @@ export const TeamMemberNode: React.FunctionComponent<React.PropsWithChildren<Tea
teamID={teamID}
teamName={teamName}
member={node}
telemetryRecorder={telemetryRecorder}
/>
)}
</>

View File

@ -1,5 +1,7 @@
import type { Meta, StoryFn } from '@storybook/react'
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
import { WebStory } from '../../components/WebStory'
import { NewTeamPage } from './NewTeamPage'
@ -13,5 +15,5 @@ const config: Meta = {
export default config
export const Default: StoryFn = function Default() {
return <WebStory>{() => <NewTeamPage />}</WebStory>
return <WebStory>{() => <NewTeamPage telemetryRecorder={noOpTelemetryRecorder} />}</WebStory>
}

View File

@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { Container, PageHeader, Link, Input, ErrorAlert, Form, Label } from '@sourcegraph/wildcard'
import { TEAM_DISPLAY_NAME_MAX_LENGTH, TEAM_NAME_MAX_LENGTH, VALID_TEAM_NAME_REGEXP } from '..'
@ -14,9 +15,11 @@ import { ParentTeamSelect } from './team-select/ParentTeamSelect'
import styles from './NewTeamPage.module.scss'
export interface NewTeamPageProps {}
export interface NewTeamPageProps extends TelemetryV2Props {}
export const NewTeamPage: React.FunctionComponent<React.PropsWithChildren<NewTeamPageProps>> = () => {
export const NewTeamPage: React.FunctionComponent<React.PropsWithChildren<NewTeamPageProps>> = ({
telemetryRecorder,
}) => {
const navigate = useNavigate()
const location = useLocation()
@ -30,6 +33,8 @@ export const NewTeamPage: React.FunctionComponent<React.PropsWithChildren<NewTea
return null
})
useEffect(() => telemetryRecorder.recordEvent('teams.new', 'view'), [telemetryRecorder])
const onNameChange: React.ChangeEventHandler<HTMLInputElement> = event => {
const hyphenatedName = event.currentTarget.value.replaceAll(/\s/g, '-')
setName(hyphenatedName)
@ -55,12 +60,17 @@ export const NewTeamPage: React.FunctionComponent<React.PropsWithChildren<NewTea
displayName: displayName !== '' ? displayName : null,
parentTeam: parentTeam !== '' ? parentTeam : null,
},
}).catch(error => {
// eslint-disable-next-line no-console
console.error(error)
})
.then(() => {
telemetryRecorder.recordEvent('teams.new', 'create')
})
.catch(error => {
// eslint-disable-next-line no-console
console.error(error)
telemetryRecorder.recordEvent('teams.new', 'createFail')
})
},
[displayName, name, createTeam, parentTeam]
[displayName, name, createTeam, parentTeam, telemetryRecorder]
)
useEffect(() => {