mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
feat(github): filter Tasks PRs by state and by who, like GitLab MRs (#38903)
* feat(github): filter Tasks PRs by state and by who, like GitLab MRs GitHub PRs in Tasks get the same two chip groups as GitLab MRs: state (Open, Merged, Closed, All) and who (Everyone, Mine, Reviewing). The chips read and write the search query, so labels and typed text survive a click and nothing new is saved. A combination equal to one of the old presets is applied as that preset, so saved views, prefetch and the phone see the same strings as before. Tracked in STA-14084. * fix(github): keep right-click on Open saving the open-PRs default view * fix(github): wrap the PR state and who chips as two groups
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import type { TaskPageComposerActionsModel } from '../use-task-page-composer-actions'
|
||||
import { getGitHubTaskKindPresets } from '@/components/task-page/task-page-localized-options'
|
||||
import {
|
||||
getGitHubPRStateChips,
|
||||
getGitHubPRWhoChips,
|
||||
getIssueTaskQueryPresets
|
||||
} from '@/components/task-page/task-page-localized-options'
|
||||
import { cn } from '@/lib/utils'
|
||||
import PRFilterDropdowns from '@/components/github/PRFilterDropdowns'
|
||||
import { Search, X, Plus, LoaderCircle, RefreshCw } from 'lucide-react'
|
||||
@@ -17,6 +21,49 @@ import IssueSourceSelector, { issueSourceChipClass } from '@/components/github/I
|
||||
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
|
||||
import { repoMatchesHostIdentity } from '@/store/slices/repo-host-identity'
|
||||
import { deriveGitHubTaskPreset } from './task-page-preset-highlight'
|
||||
import {
|
||||
deriveGitHubPRStateChip,
|
||||
deriveGitHubPRWhoChip,
|
||||
gitHubPRWhoChange,
|
||||
gitHubPRWhoPreset
|
||||
} from './task-page-pr-chips'
|
||||
|
||||
function PresetChip({
|
||||
label,
|
||||
active,
|
||||
onSelect,
|
||||
onSetDefault
|
||||
}: {
|
||||
label: string
|
||||
active: boolean
|
||||
onSelect: () => void
|
||||
/** Right-click saves this chip as the default Tasks view. */
|
||||
onSetDefault?: () => void
|
||||
}): React.JSX.Element {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={active}
|
||||
onClick={onSelect}
|
||||
onContextMenu={
|
||||
onSetDefault
|
||||
? (event) => {
|
||||
event.preventDefault()
|
||||
onSetDefault()
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={cn(
|
||||
'rounded-md border px-2.5 py-1 text-xs font-medium transition',
|
||||
active
|
||||
? 'border-border/50 bg-foreground/90 text-background shadow-xs'
|
||||
: 'border-border/60 bg-background text-foreground shadow-xs hover:bg-muted/60'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
export function TaskPageGitHubFilters({
|
||||
model
|
||||
}: {
|
||||
@@ -55,6 +102,8 @@ export function TaskPageGitHubFilters({
|
||||
githubTasksBusy
|
||||
} = model
|
||||
const highlightedPreset = deriveGitHubTaskPreset(activeGithubTaskKind, appliedTaskQuery)
|
||||
const prStateChip = deriveGitHubPRStateChip(appliedTaskQuery)
|
||||
const prWhoChip = deriveGitHubPRWhoChip(appliedTaskQuery)
|
||||
// Why: filter metadata for the primary slug is read from the host that owns the repo it came from.
|
||||
const primaryGithubFilterSource = perRepoSourceState.find((state) =>
|
||||
activeGithubTaskKind === 'prs' ? state.sources?.prs : state.sources?.issues
|
||||
@@ -73,14 +122,56 @@ export function TaskPageGitHubFilters({
|
||||
className="flex min-w-0 flex-col gap-2.5 rounded-md rounded-b-none border border-border/50 bg-muted/35 px-3 py-2.5"
|
||||
data-contextual-tour-target="tasks-search-presets"
|
||||
>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{getGitHubTaskKindPresets(activeGithubTaskKind).map((option) => {
|
||||
const active = highlightedPreset === option.id
|
||||
return (
|
||||
<button
|
||||
{activeGithubTaskKind === 'prs' ? (
|
||||
// Why: each group wraps as one unit so the divider always separates state from who.
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{getGitHubPRStateChips().map(({ id, label }) => (
|
||||
<PresetChip
|
||||
key={id}
|
||||
label={label}
|
||||
active={prStateChip === id}
|
||||
onSelect={() => {
|
||||
// Why: re-applying a lit chip would only rewrite the query and refetch the same rows.
|
||||
if (prStateChip !== id) {
|
||||
applyPRFilterChange({ state: id })
|
||||
}
|
||||
}}
|
||||
// Why: keeps main's right-click on Open, which saved the open-PRs preset.
|
||||
onSetDefault={id === 'open' ? () => handleSetDefaultTaskPreset('prs') : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span aria-hidden className="mx-0.5 h-4 w-px bg-border/60" />
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{getGitHubPRWhoChips().map(({ id, label }) => (
|
||||
<PresetChip
|
||||
key={id}
|
||||
label={label}
|
||||
active={prWhoChip === id}
|
||||
onSelect={() => {
|
||||
if (prWhoChip !== id) {
|
||||
applyPRFilterChange(gitHubPRWhoChange(id))
|
||||
}
|
||||
}}
|
||||
// Why: the default view is a saved preset, and only the open-state combinations are presets.
|
||||
onSetDefault={
|
||||
prStateChip === 'open'
|
||||
? () => handleSetDefaultTaskPreset(gitHubPRWhoPreset(id))
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{getIssueTaskQueryPresets().map((option) => (
|
||||
<PresetChip
|
||||
key={option.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
label={option.label}
|
||||
active={highlightedPreset === option.id}
|
||||
onSelect={() => {
|
||||
const query = option.query
|
||||
setTaskSearchInput(query)
|
||||
setAppliedTaskSearch(query)
|
||||
@@ -91,22 +182,11 @@ export function TaskPageGitHubFilters({
|
||||
})
|
||||
setTaskRefreshNonce((current) => current + 1)
|
||||
}}
|
||||
onContextMenu={(event) => {
|
||||
event.preventDefault()
|
||||
handleSetDefaultTaskPreset(option.id)
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-md border px-2.5 py-1 text-xs font-medium transition',
|
||||
active
|
||||
? 'border-border/50 bg-foreground/90 text-background shadow-xs'
|
||||
: 'border-border/60 bg-background text-foreground shadow-xs hover:bg-muted/60'
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
onSetDefault={() => handleSetDefaultTaskPreset(option.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<PRFilterDropdowns
|
||||
parsed={appliedTaskQuery}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseTaskQuery } from '../../../../../shared/task-query'
|
||||
import { getTaskPresetQuery } from '../../../../../shared/task-preset-query'
|
||||
import {
|
||||
deriveGitHubPRStateChip,
|
||||
deriveGitHubPRWhoChip,
|
||||
gitHubPRWhoChange,
|
||||
matchGitHubPRPreset,
|
||||
patchGitHubTaskQuery
|
||||
} from './task-page-pr-chips'
|
||||
|
||||
function chips(query: string) {
|
||||
const parsed = parseTaskQuery(query)
|
||||
return [deriveGitHubPRStateChip(parsed), deriveGitHubPRWhoChip(parsed)]
|
||||
}
|
||||
|
||||
describe('GitHub PR state × who chips', () => {
|
||||
it('lights the open row for each saved preset and nothing for someone else', () => {
|
||||
expect(chips(getTaskPresetQuery('prs'))).toEqual(['open', 'everyone'])
|
||||
expect(chips(getTaskPresetQuery('my-prs'))).toEqual(['open', 'mine'])
|
||||
expect(chips(getTaskPresetQuery('review'))).toEqual(['open', 'reviewing'])
|
||||
expect(chips('is:pr is:merged author:alice')).toEqual(['merged', null])
|
||||
expect(chips('is:pr assignee:bob')).toEqual(['all', 'everyone'])
|
||||
})
|
||||
|
||||
it('applies a preset-equal combination as that preset and keeps any other filter custom', () => {
|
||||
const mine = patchGitHubTaskQuery(
|
||||
patchGitHubTaskQuery('is:pr is:merged', { state: 'open' }),
|
||||
gitHubPRWhoChange('mine')
|
||||
)
|
||||
expect(matchGitHubPRPreset(mine)).toBe('my-prs')
|
||||
const labelled = patchGitHubTaskQuery('is:pr is:open label:bug fix', gitHubPRWhoChange('mine'))
|
||||
expect(labelled).toBe('is:pr is:open author:@me label:bug fix')
|
||||
expect(matchGitHubPRPreset(labelled)).toBeNull()
|
||||
expect(patchGitHubTaskQuery('is:pr is:open is:draft', { state: 'merged' })).toBe(
|
||||
'is:pr is:merged'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { PRFilterChange } from '@/components/github/PRFilterSections'
|
||||
import { parseTaskQuery, serializeTaskQuery, withQualifier } from '../../../../../shared/task-query'
|
||||
import type { ParsedTaskQuery } from '../../../../../shared/task-query'
|
||||
import { getTaskPresetQuery } from '../../../../../shared/task-preset-query'
|
||||
import type { TaskViewPresetId } from '../../../../../shared/ui-chrome-types'
|
||||
|
||||
export type GitHubPRStateChip = 'open' | 'merged' | 'closed' | 'all'
|
||||
export type GitHubPRWhoChip = 'everyone' | 'mine' | 'reviewing'
|
||||
|
||||
// Why: saved PR presets are the open-state rows of the state × who grid.
|
||||
const PR_PRESET_BY_WHO: Record<GitHubPRWhoChip, TaskViewPresetId> = {
|
||||
everyone: 'prs',
|
||||
mine: 'my-prs',
|
||||
reviewing: 'review'
|
||||
}
|
||||
|
||||
/** Patch the query's qualifiers, keeping every other filter and free text. */
|
||||
export function patchGitHubTaskQuery(query: string, change: PRFilterChange): string {
|
||||
let next = query
|
||||
if ('author' in change) {
|
||||
next = withQualifier(next, 'author', change.author ?? null)
|
||||
}
|
||||
if ('assignee' in change) {
|
||||
next = withQualifier(next, 'assignee', change.assignee ?? null)
|
||||
}
|
||||
if ('labels' in change) {
|
||||
next = withQualifier(next, 'labels', change.labels ?? [])
|
||||
}
|
||||
if ('state' in change && change.state) {
|
||||
next = withQualifier(next, 'state', change.state)
|
||||
if (change.state !== 'open') {
|
||||
next = withQualifier(next, 'draft', null)
|
||||
}
|
||||
}
|
||||
if ('draft' in change) {
|
||||
next = withQualifier(next, 'draft', change.draft ? 'true' : 'false')
|
||||
}
|
||||
if ('reviewer' in change) {
|
||||
// Why: the two reviewer qualifiers are mutually exclusive — clear the other whenever one is set so the chip matches the query.
|
||||
const reviewer = change.reviewer ?? null
|
||||
if (reviewer === null) {
|
||||
next = withQualifier(next, 'reviewRequested', null)
|
||||
next = withQualifier(next, 'reviewedBy', null)
|
||||
} else if (reviewer.kind === 'requested') {
|
||||
next = withQualifier(next, 'reviewedBy', null)
|
||||
next = withQualifier(next, 'reviewRequested', reviewer.login)
|
||||
} else {
|
||||
next = withQualifier(next, 'reviewRequested', null)
|
||||
next = withQualifier(next, 'reviewedBy', reviewer.login)
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
/** The saved PR preset this exact query is, so restore, prefetch keys and the phone see today's preset strings. */
|
||||
export function matchGitHubPRPreset(query: string): TaskViewPresetId | null {
|
||||
const normalized = serializeTaskQuery(parseTaskQuery(query))
|
||||
for (const preset of Object.values(PR_PRESET_BY_WHO)) {
|
||||
if (serializeTaskQuery(parseTaskQuery(getTaskPresetQuery(preset))) === normalized) {
|
||||
return preset
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function deriveGitHubPRStateChip(query: ParsedTaskQuery): GitHubPRStateChip {
|
||||
// Why: a PR search with no state qualifier lists every state.
|
||||
return query.state === null ? 'all' : query.state
|
||||
}
|
||||
|
||||
/** Who means author or reviewer, independent of assignee, labels and text; other people light no chip. */
|
||||
export function deriveGitHubPRWhoChip(query: ParsedTaskQuery): GitHubPRWhoChip | null {
|
||||
const isMe = (login: string | null): boolean => login?.toLowerCase() === '@me'
|
||||
if (query.reviewedBy !== null) {
|
||||
return null
|
||||
}
|
||||
if (query.author === null) {
|
||||
if (query.reviewRequested === null) {
|
||||
return 'everyone'
|
||||
}
|
||||
return isMe(query.reviewRequested) ? 'reviewing' : null
|
||||
}
|
||||
return query.reviewRequested === null && isMe(query.author) ? 'mine' : null
|
||||
}
|
||||
|
||||
export function gitHubPRWhoChange(who: GitHubPRWhoChip): PRFilterChange {
|
||||
if (who === 'mine') {
|
||||
return { author: '@me', reviewer: null }
|
||||
}
|
||||
if (who === 'reviewing') {
|
||||
return { author: null, reviewer: { kind: 'requested', login: '@me' } }
|
||||
}
|
||||
return { author: null, reviewer: null }
|
||||
}
|
||||
|
||||
export function gitHubPRWhoPreset(who: GitHubPRWhoChip): TaskViewPresetId {
|
||||
return PR_PRESET_BY_WHO[who]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// @vitest-environment happy-dom
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useAppStore } from '@/store'
|
||||
import { GITHUB_TASK_SEARCH_IDLE_MS } from '@/components/use-github-task-search-commit'
|
||||
import { createGlobalSettingsFixture } from '../../../../shared/global-settings-test-fixture'
|
||||
import {
|
||||
Harness,
|
||||
installSourceDisplayTestLifecycle,
|
||||
repo,
|
||||
view
|
||||
} from './use-task-page-source-display.test-fixture'
|
||||
|
||||
vi.mock('@/components/github/PRFilterDropdowns', () => ({ default: () => null }))
|
||||
|
||||
installSourceDisplayTestLifecycle()
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
function chip(name: string) {
|
||||
return screen.getByRole('button', { name })
|
||||
}
|
||||
|
||||
describe('GitHub PR state × who chips', () => {
|
||||
it('saves Open × Mine as the Mine preset after the persist effect and idle commit', () => {
|
||||
vi.useFakeTimers()
|
||||
useAppStore.setState({
|
||||
repos: [repo],
|
||||
settings: createGlobalSettingsFixture({ defaultRepoSelection: [repo.id] }),
|
||||
taskPageData: { taskSource: 'github' }
|
||||
})
|
||||
render(<Harness />)
|
||||
act(() => {
|
||||
view.setTaskSearchInput('is:pr is:merged')
|
||||
view.setAppliedTaskSearch('is:pr is:merged')
|
||||
view.setTaskResumeApplied(true)
|
||||
})
|
||||
expect(chip('Merged')).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
fireEvent.click(chip('Open'))
|
||||
fireEvent.click(chip('Mine'))
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(GITHUB_TASK_SEARCH_IDLE_MS + 1)
|
||||
})
|
||||
|
||||
expect(useAppStore.getState().taskResumeState).toMatchObject({
|
||||
githubItemsPreset: 'my-prs',
|
||||
githubItemsQuery: 'author:@me is:pr is:open'
|
||||
})
|
||||
expect(view.taskSearchInput).toBe('author:@me is:pr is:open')
|
||||
expect(chip('Open')).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(chip('Mine')).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
fireEvent.click(chip('Merged'))
|
||||
expect(useAppStore.getState().taskResumeState).toMatchObject({
|
||||
githubItemsPreset: null,
|
||||
githubItemsQuery: 'is:pr is:merged author:@me'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '../../../../shared/linear/issue-view-resume-state'
|
||||
import { getTaskPresetQuery } from '../../../../shared/task-preset-query'
|
||||
import type { GitLabMRInvolvement } from '../../../../shared/gitlab-types'
|
||||
import type { GitHubPRStateChip, GitHubPRWhoChip } from './github/task-page-pr-chips'
|
||||
import type { TaskProvider } from '../../../../shared/task-providers'
|
||||
import type { TaskViewPresetId } from '../../../../shared/ui-chrome-types'
|
||||
|
||||
@@ -86,7 +87,7 @@ export const getGitLabIssueFilters = createLocalizedCatalog(
|
||||
]
|
||||
)
|
||||
|
||||
const getIssueTaskQueryPresets = createLocalizedCatalog((): TaskQueryPreset[] => [
|
||||
export const getIssueTaskQueryPresets = createLocalizedCatalog((): TaskQueryPreset[] => [
|
||||
{
|
||||
id: 'issues',
|
||||
label: translate('auto.components.TaskPage.606a85c774', 'Open'),
|
||||
@@ -99,27 +100,29 @@ const getIssueTaskQueryPresets = createLocalizedCatalog((): TaskQueryPreset[] =>
|
||||
}
|
||||
])
|
||||
|
||||
const getPRTaskQueryPresets = createLocalizedCatalog((): TaskQueryPreset[] => [
|
||||
{
|
||||
id: 'prs',
|
||||
label: translate('auto.components.TaskPage.606a85c774', 'Open'),
|
||||
query: getTaskPresetQuery('prs')
|
||||
},
|
||||
{
|
||||
id: 'my-prs',
|
||||
label: translate('auto.components.TaskPage.7698af5263', 'Mine'),
|
||||
query: getTaskPresetQuery('my-prs')
|
||||
},
|
||||
{
|
||||
id: 'review',
|
||||
label: translate('auto.components.TaskPage.524f095d55', 'Needs review'),
|
||||
query: getTaskPresetQuery('review')
|
||||
}
|
||||
])
|
||||
export const getGitHubPRStateChips = createLocalizedCatalog(
|
||||
(): { id: GitHubPRStateChip; label: string }[] => [
|
||||
{ id: 'open', label: translate('auto.components.TaskPage.606a85c774', 'Open') },
|
||||
{ id: 'merged', label: translate('auto.components.TaskPage.37a82eaaf8', 'Merged') },
|
||||
{ id: 'closed', label: translate('auto.components.TaskPage.d09bf34db7', 'Closed') },
|
||||
{ id: 'all', label: translate('auto.components.TaskPage.c2268a9982', 'All') }
|
||||
]
|
||||
)
|
||||
|
||||
export function getGitHubTaskKindPresets(kind: GitHubTaskKind): TaskQueryPreset[] {
|
||||
return kind === 'prs' ? getPRTaskQueryPresets() : getIssueTaskQueryPresets()
|
||||
}
|
||||
// Same who choices and labels as GitLab MRs.
|
||||
export const getGitHubPRWhoChips = createLocalizedCatalog(
|
||||
(): { id: GitHubPRWhoChip; label: string }[] => [
|
||||
{
|
||||
id: 'everyone',
|
||||
label: translate('auto.components.TaskPage.gitlabInvolvementEveryone', 'Everyone')
|
||||
},
|
||||
{ id: 'mine', label: translate('auto.components.TaskPage.7698af5263', 'Mine') },
|
||||
{
|
||||
id: 'reviewing',
|
||||
label: translate('auto.components.TaskPage.gitlabInvolvementReviewing', 'Reviewing')
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
export const getSourceOptions = createLocalizedCatalog((): SourceOption[] => [
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
scopeGitHubTaskSearch,
|
||||
getDefaultPresetForGitHubTaskKind
|
||||
} from '../../../../shared/task-github-search'
|
||||
import { withQualifier } from '../../../../shared/task-query'
|
||||
import { matchGitHubPRPreset, patchGitHubTaskQuery } from './github/task-page-pr-chips'
|
||||
import type { TaskViewPresetId } from '../../../../shared/ui-chrome-types'
|
||||
import { toast } from 'sonner'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
@@ -36,46 +36,20 @@ export function useTaskPageSearchActions(model: TaskPageGitHubQuietRefreshModel)
|
||||
} = model
|
||||
const applyPRFilterChange = useCallback(
|
||||
(change: PRFilterChange): void => {
|
||||
let next = scopeGitHubTaskSearch(taskSearchInput, activeGithubTaskKind)
|
||||
// Round-trip through parseTaskQuery so dropdown patches preserve other filters and free text.
|
||||
if ('author' in change) {
|
||||
next = withQualifier(next, 'author', change.author ?? null)
|
||||
}
|
||||
if ('assignee' in change) {
|
||||
next = withQualifier(next, 'assignee', change.assignee ?? null)
|
||||
}
|
||||
if ('labels' in change) {
|
||||
next = withQualifier(next, 'labels', change.labels ?? [])
|
||||
}
|
||||
if ('state' in change && change.state) {
|
||||
next = withQualifier(next, 'state', change.state)
|
||||
if (change.state !== 'open') {
|
||||
next = withQualifier(next, 'draft', null)
|
||||
}
|
||||
}
|
||||
if ('draft' in change) {
|
||||
next = withQualifier(next, 'draft', change.draft ? 'true' : 'false')
|
||||
}
|
||||
if ('reviewer' in change) {
|
||||
// Why: the two reviewer qualifiers are mutually exclusive — clear the other whenever one is set so the chip matches the query.
|
||||
const reviewer = change.reviewer ?? null
|
||||
if (reviewer === null) {
|
||||
next = withQualifier(next, 'reviewRequested', null)
|
||||
next = withQualifier(next, 'reviewedBy', null)
|
||||
} else if (reviewer.kind === 'requested') {
|
||||
next = withQualifier(next, 'reviewedBy', null)
|
||||
next = withQualifier(next, 'reviewRequested', reviewer.login)
|
||||
} else {
|
||||
next = withQualifier(next, 'reviewRequested', null)
|
||||
next = withQualifier(next, 'reviewedBy', reviewer.login)
|
||||
}
|
||||
}
|
||||
setTaskSearchInput(next)
|
||||
setAppliedTaskSearch(next)
|
||||
setActiveTaskPreset(null)
|
||||
const next = patchGitHubTaskQuery(
|
||||
scopeGitHubTaskSearch(taskSearchInput, activeGithubTaskKind),
|
||||
change
|
||||
)
|
||||
// Why: a query equal to a saved PR preset is applied as that preset, or restore, prefetch keys and the phone see an unknown string.
|
||||
const preset = activeGithubTaskKind === 'prs' ? matchGitHubPRPreset(next) : null
|
||||
const query = preset ? getTaskPresetQuery(preset) : next
|
||||
setTaskSearchInput(query)
|
||||
setAppliedTaskSearch(query)
|
||||
setActiveTaskPreset(preset)
|
||||
setTaskResumeState({
|
||||
githubItemsPreset: null,
|
||||
githubItemsQuery: next
|
||||
githubItemsPreset: preset,
|
||||
githubItemsQuery: query
|
||||
})
|
||||
// Why: a filter change replaces every row's meaning; show the load skeleton so stale rows don't read as if the filter did nothing.
|
||||
setTasksFiltering(true)
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useTaskPageGitHubDetail } from './github/use-task-page-github-detail'
|
||||
import { useTaskPageGitHubCacheReconciliation } from './github/use-task-page-github-cache-reconciliation'
|
||||
import { useTaskPageSourceDisplay } from './use-task-page-source-display'
|
||||
import { TaskPageGitHubFilters } from './github/Filters'
|
||||
import { useTaskPageSearchActions } from './use-task-page-search-actions'
|
||||
import { TaskPageGitHubList } from './github/List'
|
||||
import { useTaskPageGitHubListProjection } from './github/use-task-page-github-list-projection'
|
||||
import type { TaskPageComposerActionsModel } from './use-task-page-composer-actions'
|
||||
@@ -127,8 +128,13 @@ export function Harness({
|
||||
useLayoutEffect(() => {
|
||||
view = model
|
||||
})
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the filter actions read the store bindings, search state and kind mounted here; modal and dialog fields only gate the find shortcut.
|
||||
const searchInput = { ...model } as unknown as Parameters<typeof useTaskPageSearchActions>[0]
|
||||
const searchActions = useTaskPageSearchActions(searchInput)
|
||||
const filtersFields = {
|
||||
...model,
|
||||
applyPRFilterChange: searchActions.applyPRFilterChange,
|
||||
handleSetDefaultTaskPreset: searchActions.handleSetDefaultTaskPreset,
|
||||
setIssueSourcePreference: runRequests
|
||||
? useAppStore.getState().setIssueSourcePreference
|
||||
: setIssueSourcePreference,
|
||||
|
||||
@@ -3036,7 +3036,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "Review erforderlich",
|
||||
"7698af5263": "Eigene",
|
||||
"94f0339621": "Mir zugewiesen",
|
||||
"c2268a9982": "Alle",
|
||||
|
||||
@@ -3259,7 +3259,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "Needs review",
|
||||
"7698af5263": "Mine",
|
||||
"94f0339621": "Assigned to me",
|
||||
"c2268a9982": "All",
|
||||
|
||||
@@ -2557,7 +2557,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "Necesita revisión",
|
||||
"7698af5263": "Mío",
|
||||
"94f0339621": "Asignado a mí",
|
||||
"c2268a9982": "Todo",
|
||||
|
||||
@@ -2781,7 +2781,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "En attente de relecture",
|
||||
"7698af5263": "À moi",
|
||||
"94f0339621": "Assignés à moi",
|
||||
"c2268a9982": "Tous",
|
||||
|
||||
@@ -2519,7 +2519,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "レビュー待ち",
|
||||
"7698af5263": "私の",
|
||||
"94f0339621": "私に割り当てられた Issue",
|
||||
"c2268a9982": "全て",
|
||||
|
||||
@@ -2519,7 +2519,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "리뷰 필요",
|
||||
"7698af5263": "내 항목",
|
||||
"94f0339621": "나에게 할당됨",
|
||||
"c2268a9982": "모두",
|
||||
|
||||
@@ -14906,7 +14906,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "Precisa de revisão",
|
||||
"7698af5263": "Meus",
|
||||
"94f0339621": "Atribuído a mim",
|
||||
"c2268a9982": "Todos",
|
||||
|
||||
@@ -3116,7 +3116,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "Нужна проверка",
|
||||
"7698af5263": "Мои",
|
||||
"94f0339621": "Назначенные мне",
|
||||
"c2268a9982": "Все",
|
||||
|
||||
@@ -3014,7 +3014,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "İnceleme gerekiyor",
|
||||
"7698af5263": "Benimkiler",
|
||||
"94f0339621": "Bana atananlar",
|
||||
"c2268a9982": "Tümü",
|
||||
|
||||
@@ -2973,7 +2973,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "待審查",
|
||||
"7698af5263": "我的",
|
||||
"94f0339621": "指派給我",
|
||||
"c2268a9982": "全部",
|
||||
|
||||
@@ -2519,7 +2519,6 @@
|
||||
"9cd11ba218": "Jira",
|
||||
"11a828abf8": "GitLab",
|
||||
"acef77f7ca": "GitHub",
|
||||
"524f095d55": "待评审",
|
||||
"7698af5263": "我的",
|
||||
"94f0339621": "分配给我",
|
||||
"c2268a9982": "全部",
|
||||
|
||||
Reference in New Issue
Block a user