Fix Linear filter chips showing UUIDs after dropdown closes (#12564)

Fetch metadata when filters are selected, not only while the popover
is open, so chip labels remain readable after closing the dropdown.
This commit is contained in:
Jinjing
2026-08-04 12:37:59 -07:00
committed by GitHub
parent e4aadcceff
commit e138d28fa6
3 changed files with 222 additions and 5 deletions
@@ -1,10 +1,48 @@
import { describe, expect, it } from 'vitest'
// @vitest-environment happy-dom
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { LinearTeam } from '../../../shared/types'
import {
clearLinearIssueAttributeFacet,
countLinearIssueAttributeFilters,
linearIssueAttributeFilterPillLabels
} from './linear-issue-attribute-filter-sections'
import type { LinearIssueAttributeFilter } from '../../../shared/linear-issue-attribute-filter'
import LinearIssueAttributeFilterDropdowns from './linear-issue-attribute-filter-dropdowns'
const metadataMocks = vi.hoisted(() => ({
useTeamsStates: vi.fn((teamIds: readonly string[]) => ({
data: teamIds.length > 0 ? [{ id: 'state-1', name: 'Todo' }] : [],
loading: false,
error: null
})),
useTeamsLabels: vi.fn((teamIds: readonly string[]) => ({
data: teamIds.length > 0 ? [{ id: 'label-1', name: 'Bug' }] : [],
loading: false,
error: null
})),
useTeamsMembers: vi.fn((teamIds: readonly string[]) => ({
data: teamIds.length > 0 ? [{ id: 'member-1', displayName: 'Ada Lovelace' }] : [],
loading: false,
error: null
}))
}))
vi.mock('@/hooks/useIssueMetadata', () => metadataMocks)
const roots: Root[] = []
afterEach(() => {
roots.splice(0).forEach((root) => {
act(() => root.unmount())
})
document.body.replaceChildren()
metadataMocks.useTeamsStates.mockClear()
metadataMocks.useTeamsLabels.mockClear()
metadataMocks.useTeamsMembers.mockClear()
})
const sample: LinearIssueAttributeFilter = {
stateIds: ['s1', 's2'],
@@ -38,3 +76,72 @@ describe('linear-issue-attribute-filter helpers', () => {
expect(pills[3]?.value).toBe('Bug')
})
})
describe('LinearIssueAttributeFilterDropdowns', () => {
it('keeps metadata lazy while closed with only static filter labels', () => {
const team: LinearTeam = { id: 'team-1', name: 'Engineering', key: 'ENG' }
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
roots.push(root)
act(() => {
root.render(
<LinearIssueAttributeFilterDropdowns
value={{
stateIds: [],
priorities: [1],
assignee: { kind: 'unassigned' },
labelIds: []
}}
onChange={() => undefined}
workspaceId="workspace-1"
isAllWorkspaces={false}
primaryTeam={team}
selectedTeamIds={[]}
availableTeams={[team]}
/>
)
})
expect(metadataMocks.useTeamsStates).toHaveBeenCalledWith([], undefined, null)
expect(metadataMocks.useTeamsLabels).toHaveBeenCalledWith([], undefined, null)
expect(metadataMocks.useTeamsMembers).toHaveBeenCalledWith([], undefined, null)
})
it('keeps readable metadata names available after the popover closes', () => {
const value: LinearIssueAttributeFilter = {
stateIds: ['state-1'],
priorities: [],
assignee: { kind: 'user', id: 'member-1' },
labelIds: ['label-1']
}
const team: LinearTeam = { id: 'team-1', name: 'Engineering', key: 'ENG' }
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
roots.push(root)
act(() => {
root.render(
<LinearIssueAttributeFilterDropdowns
value={value}
onChange={() => undefined}
workspaceId="workspace-1"
isAllWorkspaces={false}
primaryTeam={team}
selectedTeamIds={[]}
availableTeams={[team]}
/>
)
})
expect(container.textContent).toContain('Todo')
expect(container.textContent).toContain('Ada Lovelace')
expect(container.textContent).toContain('Bug')
expect(container.textContent).not.toContain('state-1')
expect(metadataMocks.useTeamsStates).toHaveBeenCalledWith(['team-1'], undefined, 'workspace-1')
expect(metadataMocks.useTeamsLabels).toHaveBeenCalledWith(['team-1'], undefined, 'workspace-1')
expect(metadataMocks.useTeamsMembers).toHaveBeenCalledWith(['team-1'], undefined, 'workspace-1')
})
})
@@ -76,9 +76,15 @@ export default function LinearIssueAttributeFilterDropdowns({
}: Props): React.JSX.Element {
const [popoverOpen, setPopoverOpen] = useState(false)
const [openSection, setOpenSection] = useState<LinearIssueFilterSectionKey | null>(null)
const activeCount = countLinearIssueAttributeFilters(value)
const metadataNeeded =
popoverOpen ||
value.stateIds.length > 0 ||
value.labelIds.length > 0 ||
value.assignee?.kind === 'user'
const activeTeamIds = useMemo(() => {
if (!popoverOpen || isAllWorkspaces) {
if (!metadataNeeded || isAllWorkspaces) {
return [] as string[]
}
return resolveLinearIssueAttributeFilterTeamIds({
@@ -86,10 +92,10 @@ export default function LinearIssueAttributeFilterDropdowns({
availableTeams,
primaryTeamId: primaryTeam?.id ?? null
})
}, [popoverOpen, isAllWorkspaces, selectedTeamIds, availableTeams, primaryTeam?.id])
}, [metadataNeeded, isAllWorkspaces, selectedTeamIds, availableTeams, primaryTeam?.id])
const concreteWorkspaceId =
popoverOpen && !isAllWorkspaces && workspaceId && workspaceId !== 'all' ? workspaceId : null
metadataNeeded && !isAllWorkspaces && workspaceId && workspaceId !== 'all' ? workspaceId : null
// Why: multi-team / All teams must union filter options across every selected team (#8739).
const states = useTeamsStates(activeTeamIds, settings, concreteWorkspaceId)
@@ -179,7 +185,6 @@ export default function LinearIssueAttributeFilterDropdowns({
[members.data]
)
const activeCount = countLinearIssueAttributeFilters(value)
const pills = linearIssueAttributeFilterPillLabels({
value,
stateNamesById,