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

stacklok / codegate-ui / 13027888929

29 Jan 2025 09:04AM UTC coverage: 67.461% (+0.3%) from 67.12%
13027888929

Pull #218

github

web-flow
Merge 8f9e7835f into f9461bcf8
Pull Request #218: feat: move health check to header

367 of 651 branches covered (56.37%)

Branch coverage included in aggregate %.

39 of 45 new or added lines in 4 files covered. (86.67%)

794 of 1070 relevant lines covered (74.21%)

63.5 hits per line

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

85.06
/src/features/header/components/header-status-menu.tsx
1
import {
2
  AlertCircle,
3
  CheckCircle2,
4
  ShieldCheck,
5
  ShieldX,
6
  ShieldAlert,
7
} from "lucide-react";
8
import { useCodeGateStatus } from "../hooks/use-codegate-status";
9
import { HealthStatus } from "../types";
10
import {
11
  Button,
12
  DialogTrigger,
13
  Loader,
14
  Link,
15
  Popover,
16
  Tooltip,
17
  TooltipTrigger,
18
} from "@stacklok/ui-kit";
19
import { Dialog } from "react-aria-components";
20

21
import { ReactNode } from "react";
22

23
type CodeGateStatus =
24
  | "healthy"
25
  | "update_available"
26
  | "unhealthy"
27
  | "loading"
28
  | "error_checking_status";
29

30
type CodeGateVersionStatus =
31
  | "up_to_date"
32
  | "update_available"
33
  | "loading"
34
  | "error_checking_version";
35

36
type CodeGateHealthCheckStatus =
37
  | "healthy"
38
  | "unhealthy"
39
  | "loading"
40
  | "error_checking_health";
41

42
function deriveOverallStatus(
43
  data: ReturnType<typeof useCodeGateStatus>["data"],
44
  isPending: boolean,
45
  isError: boolean,
46
): CodeGateStatus {
47
  if (isPending) return "loading";
22✔
48
  if (isError) return "error_checking_status";
11✔
49

50
  if (
9✔
51
    data?.health === HealthStatus.HEALTHY &&
15✔
52
    data.version?.error === null &&
53
    data.version?.is_latest === false
54
  )
55
    return "update_available";
1✔
56

57
  if (data?.health === HealthStatus.HEALTHY) return "healthy";
8✔
58

59
  return "unhealthy";
5✔
60
}
61

62
function deriveVersionStatus(
63
  data: ReturnType<typeof useCodeGateStatus>["data"],
64
  isPending: boolean,
65
  isError: boolean,
66
): CodeGateVersionStatus {
67
  if (isPending) return "loading";
22✔
68
  if (isError || data?.version?.error) return "error_checking_version";
11✔
69

70
  if (data?.version?.is_latest === false) return "update_available";
8✔
71
  return "up_to_date";
7✔
72
}
73

74
function deriveHealthCheckStatus(
75
  data: ReturnType<typeof useCodeGateStatus>["data"],
76
  isPending: boolean,
77
  isError: boolean,
78
): CodeGateHealthCheckStatus {
79
  if (isPending) return "loading";
22✔
80
  if (isError) return "error_checking_health";
11✔
81

82
  if (data?.health == HealthStatus.HEALTHY) return "healthy";
9✔
83
  return "unhealthy";
5✔
84
}
85

86
function getButtonText(status: CodeGateStatus): string {
87
  switch (status) {
22!
88
    case "error_checking_status":
89
      return "Error";
2✔
90
    case "healthy":
91
      return "Service healthy";
3✔
92
    case "loading":
93
      return "Loading";
11✔
94
    case "unhealthy":
95
      return "Service unhealthy";
5✔
96
    case "update_available":
97
      return "Update available";
1✔
98
    default:
NEW
99
      return status satisfies never;
×
100
  }
101
}
102

103
function getVersionText(
104
  status: CodeGateVersionStatus,
105
  data: ReturnType<typeof useCodeGateStatus>["data"],
106
): ReactNode {
107
  switch (status) {
22!
108
    case "error_checking_version":
109
      return "Error";
3✔
110
    case "loading":
111
      return "Loading";
11✔
112
    case "up_to_date":
113
      return "Up to date";
7✔
114
    case "update_available":
115
      return (
116
        <TooltipTrigger delay={0}>
117
          <Link
118
            className="flex gap-2 items-center text-primary justify-end overflow-hidden"
119
            variant="secondary"
120
            target="_blank"
121
            rel="noopener noreferrer"
122
            href="https://docs.codegate.ai/how-to/install#upgrade-codegate"
123
          >
124
            Update available
125
          </Link>
126
          <Tooltip className="text-right">
127
            <span className="block">
128
              Current version: {data?.version?.current_version}
129
            </span>
130
            <span className="block">
131
              Latest version: {data?.version?.latest_version}
132
            </span>
133
          </Tooltip>
134
        </TooltipTrigger>
135
      );
136
    default:
NEW
137
      return status satisfies never;
×
138
  }
139
}
140

141
function getHealthCheckText(status: CodeGateHealthCheckStatus): string {
142
  switch (status) {
22!
143
    case "healthy":
144
      return "Healthy";
4✔
145
    case "loading":
146
      return "Loading";
11✔
147
    case "error_checking_health":
148
      return "Error";
2✔
149
    case "unhealthy":
150
      return "Unhealthy";
5✔
151
    default:
NEW
152
      return status satisfies never;
×
153
  }
154
}
155

156
function ButtonIcon({
157
  status,
158
  className,
159
}: {
160
  status: CodeGateStatus;
161
  className?: string;
162
}) {
163
  switch (status) {
11!
164
    case "error_checking_status":
165
      return <AlertCircle className={className} />;
166
    case "healthy":
167
      return <ShieldCheck className={className} />;
168
    case "loading":
169
      return <Loader className={className} />;
170
    case "unhealthy":
171
      return <ShieldX className={className} />;
172
    case "update_available":
173
      return <ShieldAlert className={className} />;
174
    default:
NEW
175
      return status satisfies never;
×
176
  }
177
}
178

179
function HealthCheckIcon({
180
  healthCheckStatus,
181
  className,
182
}: {
183
  healthCheckStatus: CodeGateHealthCheckStatus;
184
  className?: string;
185
}): ReactNode {
186
  switch (healthCheckStatus) {
3!
187
    case "error_checking_health":
188
    case "unhealthy":
189
      return <AlertCircle className={className} />;
190
    case "healthy":
191
      return <CheckCircle2 className={className} />;
192
    case "loading":
193
      return <Loader className={className} />;
194
    default:
NEW
195
      return healthCheckStatus satisfies never;
×
196
  }
197
}
198

199
function VersionIcon({
200
  versionStatus: versionStatus,
201
  className,
202
}: {
203
  versionStatus: CodeGateVersionStatus;
204
  className?: string;
205
}) {
206
  switch (versionStatus) {
3!
207
    case "error_checking_version":
208
      return <AlertCircle className={className} />;
209
    case "update_available":
210
      return <AlertCircle className={className} />;
211
    case "up_to_date":
212
      return <CheckCircle2 className={className} />;
213
    case "loading":
214
      return <Loader className={className} />;
215
    default:
NEW
216
      return versionStatus satisfies never;
×
217
  }
218
}
219

220
function ButtonContent({
221
  status,
222
  isPending,
223
}: {
224
  status: CodeGateStatus;
225
  isPending: boolean;
226
}) {
227
  return (
228
    <Button
229
      variant="tertiary"
230
      className="flex gap-1 items-center text-secondary"
231
    >
232
      {getButtonText(status)}{" "}
233
      {isPending ? (
234
        <Loader className="size-4" />
11✔
235
      ) : (
236
        <ButtonIcon status={status} className="size-4" />
237
      )}
238
    </Button>
239
  );
240
}
241

242
function Row({
243
  title,
244
  value,
245
  icon,
246
}: {
247
  title: string;
248
  value: ReactNode;
249
  icon: ReactNode;
250
}) {
251
  return (
252
    <div className="py-1 mb-2 last:mb-0 flex items-center justify-between">
253
      <div className="text-secondary">{title}</div>
254
      <div className="text-primary flex gap-1 items-center">
255
        {value} {icon}
256
      </div>
257
    </div>
258
  );
259
}
260

261
function StatusPopover({
262
  versionStatus,
263
  healthCheckStatus,
264
  data,
265
}: {
266
  versionStatus: CodeGateVersionStatus;
267
  healthCheckStatus: CodeGateHealthCheckStatus;
268
  data: ReturnType<typeof useCodeGateStatus>["data"];
269
}) {
270
  return (
271
    <Popover className="px-3 py-2 min-w-64" placement="bottom end">
272
      <Dialog aria-label="CodeGate Status" className="outline-0">
273
        <Row
274
          title="CodeGate server"
275
          value={getHealthCheckText(healthCheckStatus)}
276
          icon={
277
            <HealthCheckIcon
278
              className="size-4"
279
              healthCheckStatus={healthCheckStatus}
280
            />
281
          }
282
        />
283
        <Row
284
          title="Updates"
285
          value={getVersionText(versionStatus, data)}
286
          icon={
287
            <VersionIcon className="size-4" versionStatus={versionStatus} />
288
          }
289
        />
290
      </Dialog>
291
    </Popover>
292
  );
293
}
294

295
export function HeaderStatusMenu() {
296
  const { data, isPending, isError } = useCodeGateStatus();
22✔
297

298
  const status = deriveOverallStatus(data, isPending, isError);
22✔
299
  const versionStatus = deriveVersionStatus(data, isPending, isError);
22✔
300
  const healthCheckStatus = deriveHealthCheckStatus(data, isPending, isError);
22✔
301

302
  return (
303
    <DialogTrigger>
304
      <ButtonContent status={status} isPending={isPending} />
305
      <StatusPopover
306
        healthCheckStatus={healthCheckStatus}
307
        versionStatus={versionStatus}
308
        data={data}
309
      />
310
    </DialogTrigger>
311
  );
312
}
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