Skip to content

Commit 1859579

Browse files
authored
feat: add version to footer (#882)
* Add endpoint for getting build info * Add build info XService * Add version with link to page footer Partially addresses #376. * Lift buildinfo package
1 parent 2e5859f commit 1859579

File tree

14 files changed

+204
-17
lines changed

14 files changed

+204
-17
lines changed
File renamed without changes.

cli/buildinfo/buildinfo_test.go renamed to buildinfo/buildinfo_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/stretchr/testify/require"
77
"golang.org/x/mod/semver"
88

9-
"github.com/coder/coder/cli/buildinfo"
9+
"github.com/coder/coder/buildinfo"
1010
)
1111

1212
func TestBuildInfo(t *testing.T) {

cli/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/mattn/go-isatty"
1212
"github.com/spf13/cobra"
1313

14-
"github.com/coder/coder/cli/buildinfo"
14+
"github.com/coder/coder/buildinfo"
1515
"github.com/coder/coder/cli/cliui"
1616
"github.com/coder/coder/cli/config"
1717
"github.com/coder/coder/codersdk"

coderd/coderd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ import (
77
"time"
88

99
"github.com/go-chi/chi/v5"
10+
"github.com/go-chi/render"
1011
"google.golang.org/api/idtoken"
1112

1213
chitrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi.v5"
1314

1415
"cdr.dev/slog"
16+
"github.com/coder/coder/buildinfo"
1517
"github.com/coder/coder/coderd/awsidentity"
1618
"github.com/coder/coder/coderd/database"
1719
"github.com/coder/coder/coderd/gitsshkey"
1820
"github.com/coder/coder/coderd/httpapi"
1921
"github.com/coder/coder/coderd/httpmw"
22+
"github.com/coder/coder/codersdk"
2023
"github.com/coder/coder/site"
2124
)
2225

@@ -59,6 +62,15 @@ func New(options *Options) (http.Handler, func()) {
5962
Message: "👋",
6063
})
6164
})
65+
r.Route("/buildinfo", func(r chi.Router) {
66+
r.Get("/", func(rw http.ResponseWriter, r *http.Request) {
67+
render.Status(r, http.StatusOK)
68+
render.JSON(rw, r, codersdk.BuildInfoResponse{
69+
ExternalURL: buildinfo.ExternalURL(),
70+
Version: buildinfo.Version(),
71+
})
72+
})
73+
})
6274
r.Route("/files", func(r chi.Router) {
6375
r.Use(
6476
httpmw.ExtractAPIKey(options.Database, nil),

coderd/coderd_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
package coderd_test
22

33
import (
4+
"context"
45
"testing"
56

67
"go.uber.org/goleak"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/buildinfo"
12+
"github.com/coder/coder/coderd/coderdtest"
713
)
814

915
func TestMain(m *testing.M) {
1016
goleak.VerifyTestMain(m)
1117
}
18+
19+
func TestBuildInfo(t *testing.T) {
20+
t.Parallel()
21+
client := coderdtest.New(t, nil)
22+
buildInfo, err := client.BuildInfo(context.Background())
23+
require.NoError(t, err)
24+
require.Equal(t, buildinfo.ExternalURL(), buildInfo.ExternalURL, "external URL")
25+
require.Equal(t, buildinfo.Version(), buildInfo.Version, "version")
26+
}

codersdk/buildinfo.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package codersdk
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
9+
// BuildInfoResponse contains build information for this instance of Coder.
10+
type BuildInfoResponse struct {
11+
// ExternalURL is a URL referencing the current Coder version. For production
12+
// builds, this will link directly to a release. For development builds, this
13+
// will link to a commit.
14+
ExternalURL string `json:"external_url"`
15+
// Version returns the semantic version of the build.
16+
Version string `json:"version"`
17+
}
18+
19+
// BuildInfo returns build information for this instance of Coder.
20+
func (c *Client) BuildInfo(ctx context.Context) (BuildInfoResponse, error) {
21+
res, err := c.request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
22+
if err != nil {
23+
return BuildInfoResponse{}, err
24+
}
25+
defer res.Body.Close()
26+
27+
if res.StatusCode != http.StatusOK {
28+
return BuildInfoResponse{}, readBodyAsError(res)
29+
}
30+
31+
var buildInfo BuildInfoResponse
32+
return buildInfo, json.NewDecoder(res.Body).Decode(&buildInfo)
33+
}

site/src/api/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,8 @@ export const getApiKey = async (): Promise<Types.APIKeyResponse> => {
6868
const response = await axios.post<Types.APIKeyResponse>("/api/v2/users/me/keys")
6969
return response.data
7070
}
71+
72+
export const getBuildInfo = async (): Promise<Types.BuildInfoResponse> => {
73+
const response = await axios.get("/api/v2/buildinfo")
74+
return response.data
75+
}

site/src/api/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/**
2+
* `BuildInfoResponse` must be kept in sync with the go struct in buildinfo.go.
3+
*/
4+
export interface BuildInfoResponse {
5+
external_url: string
6+
version: string
7+
}
8+
19
export interface LoginResponse {
210
session_token: string
311
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { screen } from "@testing-library/react"
22
import React from "react"
3-
import { render } from "../../test_helpers"
4-
import { Footer } from "./Footer"
3+
import { MockBuildInfo, render } from "../../test_helpers"
4+
import { Footer, Language } from "./Footer"
55

66
describe("Footer", () => {
77
it("renders content", async () => {
@@ -10,5 +10,6 @@ describe("Footer", () => {
1010

1111
// Then
1212
await screen.findByText("Copyright", { exact: false })
13+
await screen.findByText(Language.buildInfoText(MockBuildInfo))
1314
})
1415
})

site/src/components/Page/Footer.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1+
import Link from "@material-ui/core/Link"
12
import { makeStyles } from "@material-ui/core/styles"
23
import Typography from "@material-ui/core/Typography"
3-
import React from "react"
4+
import { useActor } from "@xstate/react"
5+
import React, { useContext } from "react"
6+
import { BuildInfoResponse } from "../../api/types"
7+
import { XServiceContext } from "../../xServices/StateContext"
8+
9+
export const Language = {
10+
buildInfoText: (buildInfo: BuildInfoResponse): string => {
11+
return `Coder ${buildInfo.version}`
12+
},
13+
}
414

515
export const Footer: React.FC = ({ children }) => {
616
const styles = useFooterStyles()
17+
const xServices = useContext(XServiceContext)
18+
const [buildInfoState] = useActor(xServices.buildInfoXService)
719

820
return (
921
<div className={styles.root}>
@@ -13,11 +25,13 @@ export const Footer: React.FC = ({ children }) => {
1325
{`Copyright \u00a9 ${new Date().getFullYear()} Coder Technologies, Inc. All rights reserved.`}
1426
</Typography>
1527
</div>
16-
<div className={styles.version}>
17-
<Typography color="textSecondary" variant="caption">
18-
v2 0.0.0-prototype
19-
</Typography>
20-
</div>
28+
{buildInfoState.context.buildInfo && (
29+
<div className={styles.buildInfo}>
30+
<Link variant="caption" href={buildInfoState.context.buildInfo.external_url}>
31+
{Language.buildInfoText(buildInfoState.context.buildInfo)}
32+
</Link>
33+
</div>
34+
)}
2135
</div>
2236
)
2337
}
@@ -29,11 +43,9 @@ const useFooterStyles = makeStyles((theme) => ({
2943
flex: "0",
3044
},
3145
copyRight: {
32-
backgroundColor: theme.palette.background.default,
3346
margin: theme.spacing(0.25),
3447
},
35-
version: {
36-
backgroundColor: theme.palette.background.default,
48+
buildInfo: {
3749
margin: theme.spacing(0.25),
3850
},
3951
}))

0 commit comments

Comments
 (0)