mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
v2t: add v2t to src/teams (#61808)
This commit is contained in:
parent
36c2ed91f4
commit
ee376b66e7
@ -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,
|
||||
|
||||
@ -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 />} />
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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: () => {},
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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')}
|
||||
/>
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user