mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
fix(site): add workspace proxy section to health page (#10862)
* Adds workspace proxy section to health page * Conditionally places workspace proxy warnings in errors or warnings based on calculated severity * Adds some more stories we were missing for HealthPage
This commit is contained in:
@ -238,6 +238,34 @@ func TestHealthcheck(t *testing.T) {
|
|||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
healthy: false,
|
healthy: false,
|
||||||
failingSections: []string{healthcheck.SectionWorkspaceProxy},
|
failingSections: []string{healthcheck.SectionWorkspaceProxy},
|
||||||
|
}, {
|
||||||
|
name: "ProxyWarn",
|
||||||
|
checker: &testChecker{
|
||||||
|
DERPReport: derphealth.Report{
|
||||||
|
Healthy: true,
|
||||||
|
Severity: health.SeverityOK,
|
||||||
|
},
|
||||||
|
AccessURLReport: healthcheck.AccessURLReport{
|
||||||
|
Healthy: true,
|
||||||
|
Severity: health.SeverityOK,
|
||||||
|
},
|
||||||
|
WebsocketReport: healthcheck.WebsocketReport{
|
||||||
|
Healthy: true,
|
||||||
|
Severity: health.SeverityOK,
|
||||||
|
},
|
||||||
|
DatabaseReport: healthcheck.DatabaseReport{
|
||||||
|
Healthy: true,
|
||||||
|
Severity: health.SeverityOK,
|
||||||
|
},
|
||||||
|
WorkspaceProxyReport: healthcheck.WorkspaceProxyReport{
|
||||||
|
Healthy: true,
|
||||||
|
Warnings: []string{"foobar"},
|
||||||
|
Severity: health.SeverityWarning,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
severity: health.SeverityWarning,
|
||||||
|
healthy: true,
|
||||||
|
failingSections: []string{},
|
||||||
}, {
|
}, {
|
||||||
name: "AllFail",
|
name: "AllFail",
|
||||||
healthy: false,
|
healthy: false,
|
||||||
|
@ -2,8 +2,9 @@ package healthcheck
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ func (r *WorkspaceProxyReport) Run(ctx context.Context, opts *WorkspaceProxyRepo
|
|||||||
})
|
})
|
||||||
|
|
||||||
var total, healthy int
|
var total, healthy int
|
||||||
|
var errs []string
|
||||||
for _, proxy := range r.WorkspaceProxies.Regions {
|
for _, proxy := range r.WorkspaceProxies.Regions {
|
||||||
total++
|
total++
|
||||||
if proxy.Healthy {
|
if proxy.Healthy {
|
||||||
@ -86,34 +88,40 @@ func (r *WorkspaceProxyReport) Run(ctx context.Context, opts *WorkspaceProxyRepo
|
|||||||
|
|
||||||
if len(proxy.Status.Report.Errors) > 0 {
|
if len(proxy.Status.Report.Errors) > 0 {
|
||||||
for _, err := range proxy.Status.Report.Errors {
|
for _, err := range proxy.Status.Report.Errors {
|
||||||
r.appendError(xerrors.New(err))
|
errs = append(errs, fmt.Sprintf("%s: %s", proxy.Name, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Severity = calculateSeverity(total, healthy)
|
r.Severity = calculateSeverity(total, healthy)
|
||||||
r.Healthy = r.Severity.Value() < health.SeverityError.Value()
|
r.Healthy = r.Severity.Value() < health.SeverityError.Value()
|
||||||
|
switch r.Severity {
|
||||||
|
case health.SeverityWarning, health.SeverityOK:
|
||||||
|
r.Warnings = append(r.Warnings, errs...)
|
||||||
|
case health.SeverityError:
|
||||||
|
r.appendError(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
// Versions _must_ match. Perform this check last. This will clobber any other severity.
|
// Versions _must_ match. Perform this check last. This will clobber any other severity.
|
||||||
for _, proxy := range r.WorkspaceProxies.Regions {
|
for _, proxy := range r.WorkspaceProxies.Regions {
|
||||||
if vErr := checkVersion(proxy, opts.CurrentVersion); vErr != nil {
|
if vErr := checkVersion(proxy, opts.CurrentVersion); vErr != nil {
|
||||||
r.Healthy = false
|
r.Healthy = false
|
||||||
r.Severity = health.SeverityError
|
r.Severity = health.SeverityError
|
||||||
r.appendError(vErr)
|
r.appendError(fmt.Sprintf("%s: %s", proxy.Name, vErr.Error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendError appends errs onto r.Error.
|
// appendError appends errs onto r.Error.
|
||||||
// We only have one error, so multiple errors need to be squashed in there.
|
// We only have one error, so multiple errors need to be squashed in there.
|
||||||
func (r *WorkspaceProxyReport) appendError(errs ...error) {
|
func (r *WorkspaceProxyReport) appendError(es ...string) {
|
||||||
if len(errs) == 0 {
|
if len(es) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.Error != nil {
|
if r.Error != nil {
|
||||||
errs = append([]error{xerrors.New(*r.Error)}, errs...)
|
es = append([]string{*r.Error}, es...)
|
||||||
}
|
}
|
||||||
r.Error = ptr.Ref(errors.Join(errs...).Error())
|
r.Error = ptr.Ref(strings.Join(es, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkVersion(proxy codersdk.WorkspaceProxy, currentVersion string) error {
|
func checkVersion(proxy codersdk.WorkspaceProxy, currentVersion string) error {
|
||||||
|
@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_WorkspaceProxyReport_appendErrors(t *testing.T) {
|
func Test_WorkspaceProxyReport_appendErrors(t *testing.T) {
|
||||||
@ -20,7 +18,7 @@ func Test_WorkspaceProxyReport_appendErrors(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
prevErr string
|
prevErr string
|
||||||
errs []error
|
errs []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nil",
|
name: "nil",
|
||||||
@ -29,24 +27,24 @@ func Test_WorkspaceProxyReport_appendErrors(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "one error",
|
name: "one error",
|
||||||
expected: assert.AnError.Error(),
|
expected: assert.AnError.Error(),
|
||||||
errs: []error{assert.AnError},
|
errs: []string{assert.AnError.Error()},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one error, one prev",
|
name: "one error, one prev",
|
||||||
prevErr: "previous error",
|
prevErr: "previous error",
|
||||||
expected: "previous error\n" + assert.AnError.Error(),
|
expected: "previous error\n" + assert.AnError.Error(),
|
||||||
errs: []error{assert.AnError},
|
errs: []string{assert.AnError.Error()},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two errors",
|
name: "two errors",
|
||||||
expected: assert.AnError.Error() + "\nanother error",
|
expected: assert.AnError.Error() + "\nanother error",
|
||||||
errs: []error{assert.AnError, xerrors.Errorf("another error")},
|
errs: []string{assert.AnError.Error(), "another error"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two errors, one prev",
|
name: "two errors, one prev",
|
||||||
prevErr: "previous error",
|
prevErr: "previous error",
|
||||||
expected: "previous error\n" + assert.AnError.Error() + "\nanother error",
|
expected: "previous error\n" + assert.AnError.Error() + "\nanother error",
|
||||||
errs: []error{assert.AnError, xerrors.Errorf("another error")},
|
errs: []string{assert.AnError.Error(), "another error"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
tt := tt
|
tt := tt
|
||||||
|
@ -19,14 +19,106 @@ type Story = StoryObj<typeof HealthPageView>;
|
|||||||
|
|
||||||
export const Example: Story = {};
|
export const Example: Story = {};
|
||||||
|
|
||||||
|
export const AccessURLUnhealthy: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
healthy: false,
|
||||||
|
severity: "error",
|
||||||
|
access_url: {
|
||||||
|
...MockHealth.access_url,
|
||||||
|
healthy: false,
|
||||||
|
error: "ouch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AccessURLWarning: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
healthy: true,
|
||||||
|
severity: "warning",
|
||||||
|
access_url: {
|
||||||
|
...MockHealth.access_url,
|
||||||
|
healthy: true,
|
||||||
|
warnings: ["foobar"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DatabaseUnhealthy: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
healthy: false,
|
||||||
|
severity: "error",
|
||||||
|
database: {
|
||||||
|
...MockHealth.database,
|
||||||
|
healthy: false,
|
||||||
|
error: "ouch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DatabaseWarning: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
healthy: true,
|
||||||
|
severity: "warning",
|
||||||
|
database: {
|
||||||
|
...MockHealth.database,
|
||||||
|
healthy: true,
|
||||||
|
warnings: ["foobar"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WebsocketUnhealthy: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
healthy: false,
|
||||||
|
severity: "error",
|
||||||
|
websocket: {
|
||||||
|
...MockHealth.websocket,
|
||||||
|
healthy: false,
|
||||||
|
error: "ouch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WebsocketWarning: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
healthy: true,
|
||||||
|
severity: "warning",
|
||||||
|
websocket: {
|
||||||
|
...MockHealth.websocket,
|
||||||
|
healthy: true,
|
||||||
|
warnings: ["foobar"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const UnhealthyDERP: Story = {
|
export const UnhealthyDERP: Story = {
|
||||||
args: {
|
args: {
|
||||||
healthStatus: {
|
healthStatus: {
|
||||||
...MockHealth,
|
...MockHealth,
|
||||||
healthy: false,
|
healthy: false,
|
||||||
|
severity: "error",
|
||||||
derp: {
|
derp: {
|
||||||
...MockHealth.derp,
|
...MockHealth.derp,
|
||||||
healthy: false,
|
healthy: false,
|
||||||
|
error: "ouch",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -36,6 +128,7 @@ export const DERPWarnings: Story = {
|
|||||||
args: {
|
args: {
|
||||||
healthStatus: {
|
healthStatus: {
|
||||||
...MockHealth,
|
...MockHealth,
|
||||||
|
severity: "warning",
|
||||||
derp: {
|
derp: {
|
||||||
...MockHealth.derp,
|
...MockHealth.derp,
|
||||||
warnings: ["foobar"],
|
warnings: ["foobar"],
|
||||||
@ -43,3 +136,31 @@ export const DERPWarnings: Story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ProxyUnhealthy: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
severity: "error",
|
||||||
|
healthy: false,
|
||||||
|
workspace_proxy: {
|
||||||
|
...MockHealth.workspace_proxy,
|
||||||
|
healthy: false,
|
||||||
|
error: "ouch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProxyWarning: Story = {
|
||||||
|
args: {
|
||||||
|
healthStatus: {
|
||||||
|
...MockHealth,
|
||||||
|
severity: "warning",
|
||||||
|
workspace_proxy: {
|
||||||
|
...MockHealth.workspace_proxy,
|
||||||
|
warnings: ["foobar"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -29,6 +29,7 @@ const sections = {
|
|||||||
access_url: "Access URL",
|
access_url: "Access URL",
|
||||||
websocket: "Websocket",
|
websocket: "Websocket",
|
||||||
database: "Database",
|
database: "Database",
|
||||||
|
workspace_proxy: "Workspace Proxy",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function HealthPage() {
|
export default function HealthPage() {
|
||||||
|
Reference in New Issue
Block a user