• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacklok / codegate-ui / 15655096074

14 Jun 2025 06:50PM CUT coverage: 66.419% (-0.03%) from 66.452%
15655096074

Pull #436

github

web-flow
Merge a3b3d5e0a into 02fdc1752
Pull Request #436: chore(deps-dev): bump @vitejs/plugin-react-swc from 3.8.0 to 3.10.2

428 of 707 branches covered (60.54%)

Branch coverage included in aggregate %.

913 of 1312 relevant lines covered (69.59%)

40.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.43
/src/features/header/components/header-active-workspace-selector.tsx
1
import { useQueryListWorkspaces } from '@/hooks/use-query-list-workspaces'
2
import {
3
  Button,
4
  DialogTrigger,
5
  Input,
6
  LinkButton,
7
  ListBox,
8
  ListBoxItem,
9
  Popover,
10
  SearchField,
11
  Separator,
12
} from '@stacklok/ui-kit'
13
import { useQueryClient } from '@tanstack/react-query'
14
import { useState } from 'react'
15
import { useMutationActivateWorkspace } from '../../../hooks/use-mutation-activate-workspace'
16
import clsx from 'clsx'
17
import { useQueryActiveWorkspaceName } from '../../../hooks/use-query-active-workspace-name'
18
import { hrefs } from '@/lib/hrefs'
19
import { twMerge } from 'tailwind-merge'
20
import ChevronDown from '@untitled-ui/icons-react/build/cjs/ChevronDown'
21
import { SearchMd, Settings01 } from '@untitled-ui/icons-react'
22
import { useLocation, useNavigate } from 'react-router-dom'
23

24
const ROUTES_REQUIRING_REDIRECT = [/^\/$/, /^\/prompt\/(.*)$/]
4✔
25

26
export function HeaderActiveWorkspaceSelector() {
27
  const queryClient = useQueryClient()
9✔
28

29
  const navigate = useNavigate()
9✔
30
  const location = useLocation()
9✔
31
  const { pathname } = location
9✔
32

33
  const { data: workspacesResponse } = useQueryListWorkspaces()
9✔
34
  const { mutateAsync: activateWorkspace } = useMutationActivateWorkspace()
9✔
35

36
  const { data: activeWorkspaceName } = useQueryActiveWorkspaceName()
9✔
37

38
  const [isOpen, setIsOpen] = useState(false)
9✔
39
  const [searchWorkspace, setSearchWorkspace] = useState('')
9✔
40
  const workspaces = workspacesResponse?.workspaces ?? []
9✔
41
  const filteredWorkspaces = workspaces.filter((workspace) =>
9✔
42
    workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase())
20✔
43
  )
44

45
  const handleWorkspaceClick = (name: string) => {
9✔
46
    activateWorkspace({ body: { name } }).then(() => {
×
47
      // eslint-disable-next-line no-restricted-syntax
48
      queryClient.invalidateQueries({ refetchType: 'all' }) // Global setting, refetch **everything**
×
49
      if (ROUTES_REQUIRING_REDIRECT.some((route) => route.test(pathname))) {
×
50
        navigate('/')
×
51
      }
52
      setIsOpen(false)
×
53
    })
54
  }
55

56
  return (
57
    <DialogTrigger isOpen={isOpen} onOpenChange={(test) => setIsOpen(test)}>
1✔
58
      <Button variant="tertiary" className="flex cursor-pointer">
59
        Active workspace:{' '}
60
        <span className="font-bold">{activeWorkspaceName ?? 'default'}</span>
13✔
61
        <ChevronDown />
62
      </Button>
63

64
      <Popover className="w-[32rem] p-3" placement="bottom left">
65
        <div>
66
          <div>
67
            <SearchField
68
              onChange={setSearchWorkspace}
69
              autoFocus
70
              aria-label="search"
71
            >
72
              <Input icon={<SearchMd />} />
73
            </SearchField>
74
          </div>
75

76
          <ListBox
77
            aria-label="Workspaces"
78
            items={filteredWorkspaces}
79
            selectedKeys={activeWorkspaceName ? [activeWorkspaceName] : []}
9✔
80
            onAction={(v) => {
81
              handleWorkspaceClick(v?.toString())
×
82
            }}
83
            className="-mx-1 my-2 max-h-80 overflow-auto"
84
            renderEmptyState={() => (
85
              <p className="text-center">No workspaces found</p>
86
            )}
87
          >
88
            {(item) => (
89
              <ListBoxItem
90
                id={item.name}
91
                key={item.name}
92
                textValue={item.name}
93
                data-is-selected={item.name === activeWorkspaceName}
94
                className={clsx(
95
                  `group/selector m-1 grid cursor-pointer grid-cols-[auto_1.5rem] rounded-sm py-2
96
                  text-base hover:bg-gray-200`,
97
                  {
98
                    '!bg-gray-900 !text-gray-25 hover:bg-gray-900 hover:!text-gray-25':
99
                      item.is_active,
100
                  }
101
                )}
102
              >
103
                <span className="block truncate">{item.name}</span>
104

105
                <LinkButton
106
                  href={hrefs.workspaces.edit(item.name)}
107
                  onPress={() => setIsOpen(false)}
×
108
                  isIcon
109
                  variant="tertiary"
110
                  className={twMerge(
111
                    'ml-auto size-6 opacity-0 transition-opacity group-hover/selector:opacity-100',
112
                    item.is_active
4✔
113
                      ? 'hover:bg-gray-800 pressed:bg-gray-700'
114
                      : 'hover:bg-gray-50 hover:text-primary'
115
                  )}
116
                >
117
                  <Settings01
118
                    className={twMerge(
119
                      item.is_active ? 'text-gray-25' : 'text-secondary'
4✔
120
                    )}
121
                  />
122
                </LinkButton>
123
              </ListBoxItem>
124
            )}
125
          </ListBox>
126
          <Separator className="" />
127
          <LinkButton
128
            href="/workspaces"
129
            onPress={() => setIsOpen(false)}
×
130
            variant="tertiary"
131
            className="mt-2 flex h-10 justify-start gap-2 pl-2 text-secondary"
132
          >
133
            <Settings01 />
134
            Manage workspaces
135
          </LinkButton>
136
        </div>
137
      </Popover>
138
    </DialogTrigger>
139
  )
140
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc