diff --git a/coderd/healthcheck/healthcheck_test.go b/coderd/healthcheck/healthcheck_test.go index 6ea8993209..7f7d22017c 100644 --- a/coderd/healthcheck/healthcheck_test.go +++ b/coderd/healthcheck/healthcheck_test.go @@ -238,6 +238,34 @@ func TestHealthcheck(t *testing.T) { severity: health.SeverityError, healthy: false, 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", healthy: false, diff --git a/coderd/healthcheck/workspaceproxy.go b/coderd/healthcheck/workspaceproxy.go index 3cfaf6e8c0..9acd527b94 100644 --- a/coderd/healthcheck/workspaceproxy.go +++ b/coderd/healthcheck/workspaceproxy.go @@ -2,8 +2,9 @@ package healthcheck import ( "context" - "errors" + "fmt" "sort" + "strings" "golang.org/x/xerrors" @@ -78,6 +79,7 @@ func (r *WorkspaceProxyReport) Run(ctx context.Context, opts *WorkspaceProxyRepo }) var total, healthy int + var errs []string for _, proxy := range r.WorkspaceProxies.Regions { total++ if proxy.Healthy { @@ -86,34 +88,40 @@ func (r *WorkspaceProxyReport) Run(ctx context.Context, opts *WorkspaceProxyRepo if len(proxy.Status.Report.Errors) > 0 { 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.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. for _, proxy := range r.WorkspaceProxies.Regions { if vErr := checkVersion(proxy, opts.CurrentVersion); vErr != nil { r.Healthy = false r.Severity = health.SeverityError - r.appendError(vErr) + r.appendError(fmt.Sprintf("%s: %s", proxy.Name, vErr.Error())) } } } // appendError appends errs onto r.Error. // We only have one error, so multiple errors need to be squashed in there. -func (r *WorkspaceProxyReport) appendError(errs ...error) { - if len(errs) == 0 { +func (r *WorkspaceProxyReport) appendError(es ...string) { + if len(es) == 0 { return } 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 { diff --git a/coderd/healthcheck/workspaceproxy_internal_test.go b/coderd/healthcheck/workspaceproxy_internal_test.go index 6b87b8de79..605971e233 100644 --- a/coderd/healthcheck/workspaceproxy_internal_test.go +++ b/coderd/healthcheck/workspaceproxy_internal_test.go @@ -9,8 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "golang.org/x/xerrors" ) func Test_WorkspaceProxyReport_appendErrors(t *testing.T) { @@ -20,7 +18,7 @@ func Test_WorkspaceProxyReport_appendErrors(t *testing.T) { name string expected string prevErr string - errs []error + errs []string }{ { name: "nil", @@ -29,24 +27,24 @@ func Test_WorkspaceProxyReport_appendErrors(t *testing.T) { { name: "one error", expected: assert.AnError.Error(), - errs: []error{assert.AnError}, + errs: []string{assert.AnError.Error()}, }, { name: "one error, one prev", prevErr: "previous error", expected: "previous error\n" + assert.AnError.Error(), - errs: []error{assert.AnError}, + errs: []string{assert.AnError.Error()}, }, { name: "two errors", 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", prevErr: "previous 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 diff --git a/site/src/pages/HealthPage/HealthPage.stories.tsx b/site/src/pages/HealthPage/HealthPage.stories.tsx index 92887fb9e3..9849edb539 100644 --- a/site/src/pages/HealthPage/HealthPage.stories.tsx +++ b/site/src/pages/HealthPage/HealthPage.stories.tsx @@ -19,14 +19,106 @@ type Story = StoryObj; 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 = { args: { healthStatus: { ...MockHealth, healthy: false, + severity: "error", derp: { ...MockHealth.derp, healthy: false, + error: "ouch", }, }, }, @@ -36,6 +128,7 @@ export const DERPWarnings: Story = { args: { healthStatus: { ...MockHealth, + severity: "warning", derp: { ...MockHealth.derp, 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"], + }, + }, + }, +}; diff --git a/site/src/pages/HealthPage/HealthPage.tsx b/site/src/pages/HealthPage/HealthPage.tsx index cd1c94b9c0..8825f01435 100644 --- a/site/src/pages/HealthPage/HealthPage.tsx +++ b/site/src/pages/HealthPage/HealthPage.tsx @@ -29,6 +29,7 @@ const sections = { access_url: "Access URL", websocket: "Websocket", database: "Database", + workspace_proxy: "Workspace Proxy", } as const; export default function HealthPage() {