Skip to content

feat: add region querying to pre-fetched html #8077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {

api.AGPL.Options.SetUserGroups = api.setUserGroups
api.AGPL.SiteHandler.AppearanceFetcher = api.fetchAppearanceConfig
api.AGPL.SiteHandler.RegionsFetcher = api.fetchRegions

oauthConfigs := &httpmw.OAuth2Configs{
Github: options.GithubOAuth2Config,
Expand Down
23 changes: 15 additions & 8 deletions enterprise/coderd/workspaceproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,30 @@ func (api *API) forceWorkspaceProxyHealthUpdate(ctx context.Context) {
// NOTE: this doesn't need a swagger definition since AGPL already has one, and
// this route overrides the AGPL one.
func (api *API) regions(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
//nolint:gocritic // this route intentionally requests resources that users
regions, err := api.fetchRegions(r.Context())
if err != nil {
httpapi.InternalServerError(rw, err)
return
}

httpapi.Write(r.Context(), rw, http.StatusOK, regions)
}

func (api *API) fetchRegions(ctx context.Context) (codersdk.RegionsResponse, error) {
//nolint:gocritic // this intentionally requests resources that users
// cannot usually access in order to give them a full list of available
// regions.
ctx = dbauthz.AsSystemRestricted(ctx)

primaryRegion, err := api.AGPL.PrimaryRegion(ctx)
if err != nil {
httpapi.InternalServerError(rw, err)
return
return codersdk.RegionsResponse{}, err
}
regions := []codersdk.Region{primaryRegion}

proxies, err := api.Database.GetWorkspaceProxies(ctx)
if err != nil {
httpapi.InternalServerError(rw, err)
return
return codersdk.RegionsResponse{}, err
}

// Only add additional regions if the proxy health is enabled.
Expand All @@ -81,9 +88,9 @@ func (api *API) regions(rw http.ResponseWriter, r *http.Request) {
}
}

httpapi.Write(ctx, rw, http.StatusOK, codersdk.RegionsResponse{
return codersdk.RegionsResponse{
Regions: regions,
})
}, nil
}

// @Summary Update workspace proxy
Expand Down
1 change: 1 addition & 0 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<meta property="entitlements" content="{{ .Entitlements }}" />
<meta property="appearance" content="{{ .Appearance }}" />
<meta property="experiments" content="{{ .Experiments }}" />
<meta property="regions" content="{{ .Regions }}" />
<!-- We need to set data-react-helmet to be able to override it in the workspace page -->
<link
rel="alternate icon"
Expand Down
67 changes: 50 additions & 17 deletions site/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type Handler struct {
buildInfoJSON string

AppearanceFetcher func(ctx context.Context) (codersdk.AppearanceConfig, error)
RegionsFetcher func(ctx context.Context) (codersdk.RegionsResponse, error)

Entitlements atomic.Pointer[codersdk.Entitlements]
Experiments atomic.Pointer[codersdk.Experiments]
Expand Down Expand Up @@ -231,6 +232,7 @@ type htmlState struct {
Entitlements string
Appearance string
Experiments string
Regions string
}

type csrfState struct {
Expand Down Expand Up @@ -313,33 +315,64 @@ func (h *Handler) renderHTMLWithState(rw http.ResponseWriter, r *http.Request, f
})
err := eg.Wait()
if err == nil {
user, err := json.Marshal(db2sdk.User(user, orgIDs))
if err == nil {
state.User = html.EscapeString(string(user))
}
entitlements := h.Entitlements.Load()
if entitlements != nil {
entitlements, err := json.Marshal(entitlements)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
user, err := json.Marshal(db2sdk.User(user, orgIDs))
if err == nil {
state.Entitlements = html.EscapeString(string(entitlements))
state.User = html.EscapeString(string(user))
}
}()
entitlements := h.Entitlements.Load()
if entitlements != nil {
wg.Add(1)
go func() {
defer wg.Done()
entitlements, err := json.Marshal(entitlements)
if err == nil {
state.Entitlements = html.EscapeString(string(entitlements))
}
}()
}
if h.AppearanceFetcher != nil {
cfg, err := h.AppearanceFetcher(ctx)
if err == nil {
appearance, err := json.Marshal(cfg)
wg.Add(1)
go func() {
defer wg.Done()
cfg, err := h.AppearanceFetcher(ctx)
if err == nil {
state.Appearance = html.EscapeString(string(appearance))
appearance, err := json.Marshal(cfg)
if err == nil {
state.Appearance = html.EscapeString(string(appearance))
}
}
}
}()
}
if h.RegionsFetcher != nil {
wg.Add(1)
go func() {
defer wg.Done()
regions, err := h.RegionsFetcher(ctx)
if err == nil {
regions, err := json.Marshal(regions)
if err == nil {
state.Regions = html.EscapeString(string(regions))
}
}
}()
}
experiments := h.Experiments.Load()
if experiments != nil {
experiments, err := json.Marshal(experiments)
if err == nil {
state.Experiments = html.EscapeString(string(experiments))
}
wg.Add(1)
go func() {
defer wg.Done()
experiments, err := json.Marshal(experiments)
if err == nil {
state.Experiments = html.EscapeString(string(experiments))
}
}()
}
wg.Wait()
}
}

Expand Down
19 changes: 19 additions & 0 deletions site/src/contexts/ProxyContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
)

const queryKey = ["get-proxies"]
// This doesn't seem like an idiomatic way to get react-query to use the
// initial data without performing an API request on mount, but it works.
//
// If anyone would like to clean this up in the future, it should seed data
// from the `meta` tag if it exists, and not fetch the regions route.
const [initialData] = useState(() => {
// Build info is injected by the Coder server into the HTML document.
const regions = document.querySelector("meta[property=regions]")
if (regions) {
const rawContent = regions.getAttribute("content")
try {
return JSON.parse(rawContent as string)
} catch (ex) {
// Ignore this and fetch as normal!
}
}
})
const {
data: proxiesResp,
error: proxiesError,
Expand All @@ -100,6 +117,8 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
} = useQuery({
queryKey,
queryFn: getWorkspaceProxies,
staleTime: initialData ? Infinity : undefined,
initialData,
})

// Every time we get a new proxiesResponse, update the latency check
Expand Down