fix(err): make assignee select consistent (#31185)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Hugues Pouillot
2025-04-17 15:49:45 +02:00
committed by GitHub
parent e7122a79d5
commit 865b875bfe
20 changed files with 489 additions and 273 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,20 +0,0 @@
import { useValues } from 'kea'
import React, { useMemo } from 'react'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { AssigneeDisplayType, assigneeSelectLogic } from './assigneeSelectLogic'
export const AssigneeDisplay = ({
children,
assignee,
}: {
children: (props: { displayAssignee: AssigneeDisplayType }) => React.ReactElement
assignee: ErrorTrackingIssue['assignee']
}): React.ReactElement => {
const { computeAssignee } = useValues(assigneeSelectLogic)
const displayAssignee = useMemo(() => computeAssignee(assignee), [assignee, computeAssignee])
return children({ displayAssignee })
}

View File

@@ -1,181 +0,0 @@
import { IconPlusSmall, IconX } from '@posthog/icons'
import { LemonButton, LemonButtonProps, LemonDropdown, LemonInput } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { useEffect, useState } from 'react'
import { urls } from 'scenes/urls'
import { ErrorTrackingIssue, ErrorTrackingIssueAssignee } from '~/queries/schema/schema-general'
import { AssigneeDisplay } from './AssigneeDisplay'
import { AssigneeDisplayType, assigneeSelectLogic } from './assigneeSelectLogic'
export const AssigneeSelect = ({
assignee,
onChange,
showName = false,
showIcon = true,
unassignedLabel = 'Unassigned',
...buttonProps
}: {
assignee: ErrorTrackingIssue['assignee']
onChange: (assignee: ErrorTrackingIssue['assignee']) => void
showName?: boolean
showIcon?: boolean
unassignedLabel?: string
} & Partial<Pick<LemonButtonProps, 'type' | 'size'>>): JSX.Element => {
const { search, groupOptions, memberOptions, userGroupsLoading, membersLoading } = useValues(assigneeSelectLogic)
const { setSearch, ensureAssigneeTypesLoaded } = useActions(assigneeSelectLogic)
const [showPopover, setShowPopover] = useState(false)
const _onChange = (value: ErrorTrackingIssue['assignee']): void => {
setSearch('')
setShowPopover(false)
onChange(value)
}
useEffect(() => {
ensureAssigneeTypesLoaded()
}, [ensureAssigneeTypesLoaded])
return (
<LemonDropdown
closeOnClickInside={false}
visible={showPopover}
matchWidth={false}
onVisibilityChange={(visible) => setShowPopover(visible)}
overlay={
<div className="max-w-100 deprecated-space-y-2 overflow-hidden">
<LemonInput
type="search"
placeholder="Search"
autoFocus
value={search}
onChange={setSearch}
fullWidth
/>
<ul className="deprecated-space-y-2">
{assignee && (
<li>
<LemonButton
fullWidth
role="menuitem"
size="small"
icon={<IconX />}
onClick={() => _onChange(null)}
>
Remove assignee
</LemonButton>
</li>
)}
<Section
title="Groups"
loading={userGroupsLoading}
search={!!search}
type="user_group"
items={groupOptions}
onSelect={_onChange}
activeId={assignee?.id}
emptyState={
<LemonButton
fullWidth
size="small"
icon={<IconPlusSmall />}
to={urls.settings('environment-error-tracking', 'user-groups')}
>
<div className="text-secondary">Create user group</div>
</LemonButton>
}
/>
<Section
title="Users"
loading={membersLoading}
search={!!search}
type="user"
items={memberOptions}
onSelect={_onChange}
activeId={assignee?.id}
/>
</ul>
</div>
}
>
<div>
<AssigneeDisplay assignee={assignee}>
{({ displayAssignee }) => (
<LemonButton
tooltip={displayAssignee.displayName}
icon={showIcon ? displayAssignee.icon : null}
{...buttonProps}
>
{showName ? (
<span className="pl-1">
{displayAssignee.id === 'unassigned'
? unassignedLabel
: displayAssignee.displayName}
</span>
) : null}
</LemonButton>
)}
</AssigneeDisplay>
</div>
</LemonDropdown>
)
}
const Section = ({
loading,
search,
type,
items,
onSelect,
activeId,
emptyState,
title,
}: {
title: string
loading: boolean
search: boolean
type: ErrorTrackingIssueAssignee['type']
items: AssigneeDisplayType[]
onSelect: (value: ErrorTrackingIssue['assignee']) => void
activeId?: string | number
emptyState?: JSX.Element
}): JSX.Element => {
return (
<li>
<section className="deprecated-space-y-px">
<h5 className="mx-2 my-0.5">{title}</h5>
{items.map((item) => (
<li key={item.id}>
<LemonButton
fullWidth
role="menuitem"
size="small"
icon={item.icon}
onClick={() => onSelect(activeId === item.id ? null : { type, id: item.id })}
active={activeId === item.id}
>
<span className="flex items-center justify-between gap-2 flex-1">
<span>{item.displayName}</span>
</span>
</LemonButton>
</li>
))}
{loading ? (
<div className="p-2 text-secondary italic truncate border-t">Loading...</div>
) : items.length === 0 ? (
search ? (
<div className="p-2 text-secondary italic truncate border-t">
<span>No matches</span>
</div>
) : (
<div className="border-t pt-1">{emptyState}</div>
)
) : null}
</section>
</li>
)
}

View File

@@ -1,6 +1,6 @@
import './ErrorTracking.scss'
import { LemonCard } from '@posthog/lemon-ui'
import { LemonButton, LemonCard } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { useEffect } from 'react'
@@ -8,7 +8,8 @@ import { SceneExport } from 'scenes/sceneTypes'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { AssigneeSelect } from './AssigneeSelect'
import { AssigneeIconDisplay, AssigneeLabelDisplay } from './components/Assignee/AssigneeDisplay'
import { AssigneeSelect } from './components/Assignee/AssigneeSelect'
import { IssueCard } from './components/IssueCard'
import { DateRangeFilter, FilterGroup, InternalAccountsFilter } from './ErrorTrackingFilters'
import { errorTrackingIssueSceneLogic } from './errorTrackingIssueSceneLogic'
@@ -49,12 +50,18 @@ export function ErrorTrackingIssueScene(): JSX.Element {
buttons={
<div className="flex gap-x-2">
{!issueLoading && issue?.status == 'active' && (
<AssigneeSelect
assignee={issue?.assignee}
onChange={updateAssignee}
type="secondary"
showName
/>
<AssigneeSelect assignee={issue?.assignee} onChange={updateAssignee}>
{(displayAssignee) => {
return (
<LemonButton
type="secondary"
icon={<AssigneeIconDisplay assignee={displayAssignee} />}
>
<AssigneeLabelDisplay assignee={displayAssignee} placeholder="Unassigned" />
</LemonButton>
)
}}
</AssigneeSelect>
)}
{!issueLoading && (
<GenericSelect

View File

@@ -1,9 +1,10 @@
import { LemonSelect } from '@posthog/lemon-ui'
import { LemonButton, LemonSelect } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { AssigneeSelect } from './AssigneeSelect'
import { AssigneeLabelDisplay } from './components/Assignee/AssigneeDisplay'
import { AssigneeSelect } from './components/Assignee/AssigneeSelect'
import { errorTrackingLogic } from './errorTrackingLogic'
import { errorTrackingSceneLogic } from './errorTrackingSceneLogic'
import { BulkActions } from './issue/BulkActions'
@@ -90,15 +91,15 @@ export const ErrorTrackingListOptions = (): JSX.Element => {
</div>
<div className="flex items-center gap-1">
<span>Assigned to:</span>
<AssigneeSelect
showName
showIcon={false}
assignee={assignee}
onChange={(assignee) => setAssignee(assignee)}
unassignedLabel="Any user"
type="secondary"
size="small"
/>
<AssigneeSelect assignee={assignee} onChange={(assignee) => setAssignee(assignee)}>
{(displayAssignee) => {
return (
<LemonButton type="secondary" size="small">
<AssigneeLabelDisplay assignee={displayAssignee} placeholder="Any user" />
</LemonButton>
)
}}
</AssigneeSelect>
</div>
</span>
)}

View File

@@ -1,4 +1,4 @@
import { IconGear } from '@posthog/icons'
import { IconChevronDown, IconGear } from '@posthog/icons'
import { LemonBanner, LemonButton, LemonCheckbox, LemonDivider, LemonSkeleton, Link, Tooltip } from '@posthog/lemon-ui'
import { BindLogic, useActions, useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
@@ -16,7 +16,8 @@ import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { QueryContext, QueryContextColumnComponent, QueryContextColumnTitleComponent } from '~/queries/types'
import { InsightLogicProps } from '~/types'
import { AssigneeSelect } from './AssigneeSelect'
import { AssigneeIconDisplay, AssigneeLabelDisplay } from './components/Assignee/AssigneeDisplay'
import { AssigneeSelect } from './components/Assignee/AssigneeSelect'
import { errorTrackingDataNodeLogic } from './errorTrackingDataNodeLogic'
import { DateRangeFilter, ErrorTrackingFilters, FilterGroup, InternalAccountsFilter } from './ErrorTrackingFilters'
import { errorTrackingIssueSceneLogic } from './errorTrackingIssueSceneLogic'
@@ -164,12 +165,26 @@ const CustomGroupTitleColumn: QueryContextColumnComponent = (props) => {
)}
<span>|</span>
<AssigneeSelect
showName={true}
showIcon={false}
assignee={record.assignee}
onChange={(assignee) => assignIssue(record.id, assignee)}
size="xsmall"
/>
>
{(anyAssignee) => {
return (
<div
className="flex items-center hover:bg-fill-button-tertiary-hover p-[0.1rem] rounded cursor-pointer"
role="button"
>
<AssigneeIconDisplay assignee={anyAssignee} size="xsmall" />
<AssigneeLabelDisplay
assignee={anyAssignee}
className="ml-1 text-xs text-secondary"
size="xsmall"
/>
<IconChevronDown />
</div>
)
}}
</AssigneeSelect>
</div>
</div>
</div>

View File

@@ -0,0 +1,87 @@
import { Meta } from '@storybook/react'
import { AssigneeDisplay, AssigneeIconDisplayProps } from './AssigneeDisplay'
const meta: Meta = {
title: 'ErrorTracking/AssigneeDisplay',
parameters: {
layout: 'centered',
viewMode: 'story',
},
args: {
sizes: ['xsmall', 'small', 'medium', 'large'],
},
}
export default meta
type SizedComponentProps = {
sizes: AssigneeIconDisplayProps['size'][]
}
export const UnassignedDisplays = ({ sizes }: SizedComponentProps): JSX.Element => {
return (
<div className="space-y-4">
{sizes.map((size) => (
<AssigneeDisplay key={size} size={size} assignee={null} />
))}
</div>
)
}
export const UserDisplays = ({ sizes }: SizedComponentProps): JSX.Element => {
return (
<div className="space-y-4">
{sizes.map((size) => (
<AssigneeDisplay
key={size}
size={size}
assignee={{
id: 1,
type: 'user',
user: {
id: 1,
uuid: '123e4567-e89b-12d3-a456-426614174000',
distinct_id: '123e4567-e89b-12d3-a456-426614174000',
first_name: 'John',
last_name: 'Doe',
email: 'john.doe@gmail.com',
},
}}
/>
))}
</div>
)
}
export const GroupDisplays = ({ sizes }: SizedComponentProps): JSX.Element => {
return (
<div className="space-y-4">
{sizes.map((size) => (
<AssigneeDisplay
key={size}
size={size}
assignee={{
id: '123',
type: 'group',
group: {
id: '123',
name: 'Group Name',
members: [],
},
}}
/>
))}
</div>
)
}
export const AllDisplays = ({ sizes }: SizedComponentProps): JSX.Element => {
return (
<div className="flex gap-4 justify-start items-start">
<UnassignedDisplays sizes={sizes} />
<UserDisplays sizes={sizes} />
<GroupDisplays sizes={sizes} />
</div>
)
}

View File

@@ -0,0 +1,126 @@
import { IconPerson } from '@posthog/icons'
import { ProfilePicture } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { fullName, UnexpectedNeverError } from 'lib/utils'
import { cn } from 'lib/utils/css-classes'
import React, { useMemo } from 'react'
import { match } from 'ts-pattern'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { Assignee, assigneeSelectLogic } from './assigneeSelectLogic'
export interface AssigneeAnyDisplayProps {
assignee: Assignee
}
export interface AssigneeResolverProps {
children: (props: { assignee: Assignee }) => React.ReactElement
assignee: ErrorTrackingIssue['assignee']
}
export const AssigneeResolver = ({ children, assignee }: AssigneeResolverProps): React.ReactElement => {
const { resolveAssignee } = useValues(assigneeSelectLogic)
const resolvedAssignee = useMemo(() => resolveAssignee(assignee), [assignee, resolveAssignee])
return children({ assignee: resolvedAssignee })
}
export interface AssigneeBaseDisplayProps {
assignee: Assignee
size?: 'xsmall' | 'small' | 'medium' | 'large'
}
export interface AssigneeIconDisplayProps extends AssigneeBaseDisplayProps {}
function getIconClassname(size: 'xsmall' | 'small' | 'medium' | 'large' = 'medium'): string {
switch (size) {
case 'xsmall':
return 'text-[0.6rem] h-3 w-3'
case 'small':
return 'text-[0.75rem] h-4 w-4'
case 'medium':
return 'text-[0.85rem] h-5 w-5'
case 'large':
return 'text-[1rem] h-6 w-6'
default:
throw new UnexpectedNeverError(size)
}
}
export const AssigneeIconDisplay = ({ assignee, size }: AssigneeIconDisplayProps): JSX.Element => {
return match(assignee)
.with({ type: 'group' }, ({ group }) => (
// The ideal way would be to use a Lettermark component here
// but there is no way to make it consistent with ProfilePicture at the moment
// TODO: Make sure the size prop are the same between ProfilePicture and Lettermark
<ProfilePicture
user={{ first_name: group.name, last_name: undefined, email: undefined }}
className={getIconClassname(size)}
/>
))
.with({ type: 'user' }, ({ user }) => <ProfilePicture user={user} className={getIconClassname(size)} />)
.otherwise(() => (
<IconPerson
className={cn(
'rounded-full border border-dashed border-secondary text-secondary flex items-center justify-center p-0.5',
getIconClassname(size)
)}
/>
))
}
export interface AssigneeLabelDisplayProps extends AssigneeBaseDisplayProps {
placeholder?: string
className?: string
}
export const AssigneeLabelDisplay = ({
assignee,
className,
size,
placeholder,
}: AssigneeLabelDisplayProps): JSX.Element => {
return (
<span
className={cn(className, {
'text-xs': size === 'xsmall',
'text-sm': size === 'small',
'text-base': size === 'medium',
'text-lg': size === 'large',
})}
>
{match(assignee)
.with({ type: 'group' }, ({ group }) => group.name)
.with({ type: 'user' }, ({ user }) => fullName(user))
.otherwise(() => placeholder || 'Unassigned')}
</span>
)
}
interface AssigneeDisplayProps
extends AssigneeBaseDisplayProps,
Omit<AssigneeLabelDisplayProps, 'className'>,
AssigneeIconDisplayProps {
className?: string
labelClassname?: string
}
export const AssigneeDisplay = ({
assignee,
placeholder,
className,
labelClassname,
size,
}: AssigneeDisplayProps): JSX.Element => {
return (
<div className={cn('flex justify-start items-center gap-1', className)}>
<AssigneeIconDisplay assignee={assignee} size={size} />
<AssigneeLabelDisplay
className={labelClassname}
size={size}
assignee={assignee}
placeholder={placeholder}
/>
</div>
)
}

View File

@@ -0,0 +1,132 @@
import { IconPlusSmall, IconX } from '@posthog/icons'
import { LemonButton, LemonInput } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { urls } from 'scenes/urls'
import { ErrorTrackingIssue, ErrorTrackingIssueAssignee } from '~/queries/schema/schema-general'
import { AssigneeIconDisplay, AssigneeLabelDisplay } from './AssigneeDisplay'
import { Assignee, assigneeSelectLogic } from './assigneeSelectLogic'
export interface AssigneeDropdownProps {
assignee: ErrorTrackingIssueAssignee | null
onChange: (assignee: ErrorTrackingIssueAssignee | null) => void
}
export function AssigneeDropdown({ assignee, onChange }: AssigneeDropdownProps): JSX.Element {
const { search, filteredGroups, filteredMembers, userGroupsLoading, membersLoading } =
useValues(assigneeSelectLogic)
const { setSearch } = useActions(assigneeSelectLogic)
return (
<div className="max-w-100 deprecated-space-y-2 overflow-hidden">
<LemonInput type="search" placeholder="Search" autoFocus value={search} onChange={setSearch} fullWidth />
<ul className="deprecated-space-y-2">
{assignee && (
<li>
<LemonButton
fullWidth
role="menuitem"
size="small"
icon={<IconX />}
onClick={() => onChange(null)}
>
Remove assignee
</LemonButton>
</li>
)}
<Section
title="Groups"
loading={userGroupsLoading}
search={!!search}
type="user_group"
items={filteredGroups.map((group) => ({
id: group.id,
type: 'group',
group: group,
}))}
onSelect={onChange}
activeId={assignee?.id}
emptyState={
<LemonButton
fullWidth
size="small"
icon={<IconPlusSmall />}
to={urls.settings('environment-error-tracking', 'user-groups')}
>
<div className="text-secondary">Create user group</div>
</LemonButton>
}
/>
<Section
title="Users"
loading={membersLoading}
search={!!search}
type="user"
items={filteredMembers.map((member) => ({
id: member.user.id,
type: 'user',
user: member.user,
}))}
onSelect={onChange}
activeId={assignee?.id}
/>
</ul>
</div>
)
}
const Section = ({
loading,
search,
type,
items,
onSelect,
activeId,
emptyState,
title,
}: {
title: string
loading: boolean
search: boolean
type: ErrorTrackingIssueAssignee['type']
items: Assignee[]
onSelect: (value: ErrorTrackingIssue['assignee']) => void
activeId?: string | number
emptyState?: JSX.Element
}): JSX.Element => {
return (
<li>
<section className="deprecated-space-y-px">
<h5 className="mx-2 my-0.5">{title}</h5>
{items.map((item) => (
<li key={item?.id || 'unassigned'}>
<LemonButton
fullWidth
role="menuitem"
size="small"
icon={<AssigneeIconDisplay assignee={item} />}
onClick={() => item?.id && onSelect(activeId === item.id ? null : { type, id: item.id })}
active={activeId === item?.id}
>
<AssigneeLabelDisplay assignee={item} />
</LemonButton>
</li>
))}
{loading ? (
<div className="p-2 text-secondary italic truncate border-t">Loading...</div>
) : items.length === 0 ? (
search ? (
<div className="p-2 text-secondary italic truncate border-t">
<span>No matches</span>
</div>
) : (
<div className="border-t pt-1">{emptyState}</div>
)
) : null}
</section>
</li>
)
}

View File

@@ -0,0 +1,48 @@
import { LemonDropdown } from '@posthog/lemon-ui'
import { useActions } from 'kea'
import { useEffect, useState } from 'react'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { AssigneeResolver } from './AssigneeDisplay'
import { AssigneeDropdown } from './AssigneeDropdown'
import { Assignee, assigneeSelectLogic } from './assigneeSelectLogic'
export const AssigneeSelect = ({
assignee,
onChange,
children,
}: {
assignee: ErrorTrackingIssue['assignee']
onChange: (assignee: ErrorTrackingIssue['assignee']) => void
children: (assignee: Assignee) => JSX.Element
}): JSX.Element => {
const { setSearch, ensureAssigneeTypesLoaded } = useActions(assigneeSelectLogic)
const [showPopover, setShowPopover] = useState(false)
const _onChange = (value: ErrorTrackingIssue['assignee']): void => {
setSearch('')
setShowPopover(false)
onChange(value)
}
useEffect(() => {
ensureAssigneeTypesLoaded()
}, [ensureAssigneeTypesLoaded])
return (
<LemonDropdown
closeOnClickInside={false}
visible={showPopover}
matchWidth={false}
onVisibilityChange={(visible) => setShowPopover(visible)}
overlay={<AssigneeDropdown assignee={assignee} onChange={_onChange} />}
>
<div>
<AssigneeResolver assignee={assignee}>
{({ assignee: resolvedAssignee }) => children(resolvedAssignee)}
</AssigneeResolver>
</div>
</LemonDropdown>
)
}

View File

@@ -1,12 +1,9 @@
import { IconPerson } from '@posthog/icons'
import { Lettermark, ProfilePicture } from '@posthog/lemon-ui'
import { actions, connect, kea, listeners, path, props, reducers, selectors } from 'kea'
import { fullName } from 'lib/utils'
import { membersLogic } from 'scenes/organization/membersLogic'
import { userGroupsLogic } from 'scenes/settings/environment/userGroupsLogic'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { OrganizationMemberType, UserGroup } from '~/types'
import type { OrganizationMemberType, UserGroup } from '~/types'
import type { assigneeSelectLogicType } from './assigneeSelectLogicType'
@@ -14,26 +11,20 @@ export type ErrorTrackingAssigneeSelectProps = {
assignee: ErrorTrackingIssue['assignee']
}
export type AssigneeDisplayType = { id: string | number; icon: JSX.Element; displayName?: string }
const groupDisplay = (group: UserGroup, index: number): AssigneeDisplayType => ({
id: group.id,
displayName: group.name,
icon: <Lettermark name={group.name} index={index} rounded />,
})
const userDisplay = (member: OrganizationMemberType): AssigneeDisplayType => ({
id: member.user.id,
displayName: fullName(member.user),
icon: <ProfilePicture size="md" user={member.user} />,
})
const unassignedDisplay: AssigneeDisplayType = {
id: 'unassigned',
displayName: 'Unassigned',
icon: <IconPerson className="rounded-full border border-dashed border-muted text-secondary p-0.5" />,
export type UserAssignee = {
id: number
type: 'user'
user: OrganizationMemberType['user']
}
export type GroupAssignee = {
id: string
type: 'group'
group: UserGroup
}
export type Assignee = UserAssignee | GroupAssignee | null
export const assigneeSelectLogic = kea<assigneeSelectLogicType>([
path(['scenes', 'error-tracking', 'assigneeSelectLogic']),
props({} as ErrorTrackingAssigneeSelectProps),
@@ -79,24 +70,33 @@ export const assigneeSelectLogic = kea<assigneeSelectLogicType>([
(membersLoading, userGroupsLoading): boolean => membersLoading || userGroupsLoading,
],
groupOptions: [(s) => [s.filteredGroups], (groups): AssigneeDisplayType[] => groups.map(groupDisplay)],
memberOptions: [(s) => [s.filteredMembers], (members): AssigneeDisplayType[] => members.map(userDisplay)],
computeAssignee: [
resolveAssignee: [
(s) => [s.userGroups, s.meFirstMembers],
(groups, members): ((assignee: ErrorTrackingIssue['assignee']) => AssigneeDisplayType) => {
(groups, members): ((assignee: ErrorTrackingIssue['assignee']) => Assignee) => {
return (assignee: ErrorTrackingIssue['assignee']) => {
if (assignee) {
if (assignee.type === 'user_group') {
const assignedGroup = groups.find((group) => group.id === assignee.id)
return assignedGroup ? groupDisplay(assignedGroup, 0) : unassignedDisplay
return assignedGroup
? {
id: assignedGroup.id,
type: 'group',
group: assignedGroup,
}
: null
}
const assignedMember = members.find((member) => member.user.id === assignee.id)
return assignedMember ? userDisplay(assignedMember) : unassignedDisplay
return assignedMember
? {
id: assignedMember.user.id,
type: 'user',
user: assignedMember.user,
}
: null
}
return unassignedDisplay
return null
}
},
],

View File

@@ -17,8 +17,8 @@ import { urls } from 'scenes/urls'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { ActivityScope } from '~/types'
import { AssigneeDisplay } from './AssigneeDisplay'
import { assigneeSelectLogic } from './assigneeSelectLogic'
import { AssigneeIconDisplay, AssigneeLabelDisplay, AssigneeResolver } from './components/Assignee/AssigneeDisplay'
import { assigneeSelectLogic } from './components/Assignee/assigneeSelectLogic'
type ErrorTrackingIssueAssignee = Exclude<ErrorTrackingIssue['assignee'], null>
@@ -30,14 +30,14 @@ function AssigneeRenderer({ assignee }: { assignee: ErrorTrackingIssueAssignee }
}, [ensureAssigneeTypesLoaded])
return (
<AssigneeDisplay assignee={assignee}>
{({ displayAssignee }) => (
<AssigneeResolver assignee={assignee}>
{({ assignee }) => (
<span className="flex gap-x-0.5">
{displayAssignee.icon}
<span>{displayAssignee.displayName}</span>
<AssigneeIconDisplay assignee={assignee} />
<AssigneeLabelDisplay assignee={assignee} />
</span>
)}
</AssigneeDisplay>
</AssigneeResolver>
)
}

View File

@@ -3,7 +3,8 @@ import { useActions, useValues } from 'kea'
import { ErrorTrackingIssue } from '~/queries/schema/schema-general'
import { AssigneeSelect } from '../AssigneeSelect'
import { AssigneeLabelDisplay } from '../components/Assignee/AssigneeDisplay'
import { AssigneeSelect } from '../components/Assignee/AssigneeSelect'
import { errorTrackingDataNodeLogic } from '../errorTrackingDataNodeLogic'
import { errorTrackingSceneLogic } from '../errorTrackingSceneLogic'
import { GenericSelect } from './GenericSelect'
@@ -93,15 +94,15 @@ export function BulkActions(): JSX.Element {
}
}}
/>
<AssigneeSelect
type="secondary"
size="small"
showName
showIcon={false}
unassignedLabel="Assign"
assignee={null}
onChange={(assignee) => assignIssues(selectedIssueIds, assignee)}
/>
<AssigneeSelect assignee={null} onChange={(assignee) => assignIssues(selectedIssueIds, assignee)}>
{(displayAssignee) => {
return (
<LemonButton type="secondary" size="small">
<AssigneeLabelDisplay assignee={displayAssignee} placeholder="Assign" />
</LemonButton>
)
}}
</AssigneeSelect>
</>
) : (
<></>