Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 215dd7b

Browse files
authored
feat: show version on login page (#13033)
1 parent a69fc65 commit 215dd7b

File tree

19 files changed

+101
-83
lines changed

19 files changed

+101
-83
lines changed

site/src/api/queries/appearance.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ const initialAppearanceData = getMetadataAsJSON<AppearanceConfig>("appearance");
88
const appearanceConfigKey = ["appearance"] as const;
99

1010
export const appearance = (): UseQueryOptions<AppearanceConfig> => {
11-
return {
12-
// We either have our initial data or should immediately
13-
// fetch and never again!
14-
...cachedQuery(initialAppearanceData),
11+
// We either have our initial data or should immediately fetch and never again!
12+
return cachedQuery({
13+
initialData: initialAppearanceData,
1514
queryKey: ["appearance"],
1615
queryFn: () => API.getAppearance(),
17-
};
16+
});
1817
};
1918

2019
export const updateAppearance = (queryClient: QueryClient) => {

site/src/api/queries/buildInfo.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ const initialBuildInfoData = getMetadataAsJSON<BuildInfoResponse>("build-info");
88
const buildInfoKey = ["buildInfo"] as const;
99

1010
export const buildInfo = (): UseQueryOptions<BuildInfoResponse> => {
11-
return {
12-
// We either have our initial data or should immediately
13-
// fetch and never again!
14-
...cachedQuery(initialBuildInfoData),
11+
// The version of the app can't change without reloading the page.
12+
return cachedQuery({
13+
initialData: initialBuildInfoData,
1514
queryKey: buildInfoKey,
1615
queryFn: () => API.getBuildInfo(),
17-
};
16+
});
1817
};

site/src/api/queries/entitlements.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const initialEntitlementsData = getMetadataAsJSON<Entitlements>("entitlements");
88
const entitlementsQueryKey = ["entitlements"] as const;
99

1010
export const entitlements = (): UseQueryOptions<Entitlements> => {
11-
return {
12-
...cachedQuery(initialEntitlementsData),
11+
return cachedQuery({
12+
initialData: initialEntitlementsData,
1313
queryKey: entitlementsQueryKey,
1414
queryFn: () => API.getEntitlements(),
15-
};
15+
});
1616
};
1717

1818
export const refreshEntitlements = (queryClient: QueryClient) => {

site/src/api/queries/experiments.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const initialExperimentsData = getMetadataAsJSON<Experiments>("experiments");
88
const experimentsKey = ["experiments"] as const;
99

1010
export const experiments = (): UseQueryOptions<Experiments> => {
11-
return {
12-
...cachedQuery(initialExperimentsData),
11+
return cachedQuery({
12+
initialData: initialExperimentsData,
1313
queryKey: experimentsKey,
1414
queryFn: () => API.getExperiments(),
15-
} satisfies UseQueryOptions<Experiments>;
15+
});
1616
};
1717

1818
export const availableExperiments = () => {

site/src/api/queries/users.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ const meKey = ["me"];
129129
export const me = (): UseQueryOptions<User> & {
130130
queryKey: QueryKey;
131131
} => {
132-
return {
133-
...cachedQuery(initialUserData),
132+
return cachedQuery({
133+
initialData: initialUserData,
134134
queryKey: meKey,
135135
queryFn: API.getAuthenticatedUser,
136-
};
136+
});
137137
};
138138

139139
export function apiKey(): UseQueryOptions<GenerateAPIKeyResponse> {
@@ -144,12 +144,12 @@ export function apiKey(): UseQueryOptions<GenerateAPIKeyResponse> {
144144
}
145145

146146
export const hasFirstUser = (): UseQueryOptions<boolean> => {
147-
return {
147+
return cachedQuery({
148148
// This cannot be false otherwise it will not fetch!
149-
...cachedQuery(typeof initialUserData !== "undefined" ? true : undefined),
149+
initialData: Boolean(initialUserData) || undefined,
150150
queryKey: ["hasFirstUser"],
151151
queryFn: API.hasFirstUser,
152-
};
152+
});
153153
};
154154

155155
export const login = (

site/src/api/queries/util.ts

+25-21
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
import type { UseQueryOptions } from "react-query";
22

3-
// cachedQuery allows the caller to only make a request
4-
// a single time, and use `initialData` if it is provided.
5-
//
6-
// This is particularly helpful for passing values injected
7-
// via metadata. We do this for the initial user fetch, buildinfo,
8-
// and a few others to reduce page load time.
9-
export const cachedQuery = <T>(initialData?: T): Partial<UseQueryOptions<T>> =>
10-
// Only do this if there is initial data,
11-
// otherwise it can conflict with tests.
12-
initialData
13-
? {
14-
cacheTime: Infinity,
15-
staleTime: Infinity,
16-
refetchOnMount: false,
17-
refetchOnReconnect: false,
18-
refetchOnWindowFocus: false,
19-
initialData,
20-
}
21-
: {
22-
initialData,
23-
};
3+
/**
4+
* cachedQuery allows the caller to only make a request a single time, and use
5+
* `initialData` if it is provided. This is particularly helpful for passing
6+
* values injected via metadata. We do this for the initial user fetch,
7+
* buildinfo, and a few others to reduce page load time.
8+
*/
9+
export const cachedQuery = <
10+
TQueryOptions extends UseQueryOptions<TData>,
11+
TData,
12+
>(
13+
options: TQueryOptions,
14+
): TQueryOptions =>
15+
// Only do this if there is initial data, otherwise it can conflict with tests.
16+
({
17+
...(options.initialData
18+
? {
19+
cacheTime: Infinity,
20+
staleTime: Infinity,
21+
refetchOnMount: false,
22+
refetchOnReconnect: false,
23+
refetchOnWindowFocus: false,
24+
}
25+
: {}),
26+
...options,
27+
});

site/src/contexts/ProxyContext.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
useEffect,
88
useState,
99
} from "react";
10-
import { type UseQueryOptions, useQuery } from "react-query";
10+
import { useQuery } from "react-query";
1111
import { getWorkspaceProxies, getWorkspaceProxyRegions } from "api/api";
1212
import { cachedQuery } from "api/queries/util";
1313
import type { Region, WorkspaceProxy } from "api/typesGenerated";
@@ -131,11 +131,13 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
131131
error: proxiesError,
132132
isLoading: proxiesLoading,
133133
isFetched: proxiesFetched,
134-
} = useQuery({
135-
...cachedQuery(initialData),
136-
queryKey,
137-
queryFn: query,
138-
} as UseQueryOptions<readonly Region[]>);
134+
} = useQuery(
135+
cachedQuery({
136+
initialData,
137+
queryKey,
138+
queryFn: query,
139+
}),
140+
);
139141

140142
// Every time we get a new proxiesResponse, update the latency check
141143
// to each workspace proxy.

site/src/modules/dashboard/DashboardProvider.tsx

+1-9
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import {
77
} from "react";
88
import { useQuery } from "react-query";
99
import { appearance } from "api/queries/appearance";
10-
import { buildInfo } from "api/queries/buildInfo";
1110
import { entitlements } from "api/queries/entitlements";
1211
import { experiments } from "api/queries/experiments";
1312
import type {
1413
AppearanceConfig,
15-
BuildInfoResponse,
1614
Entitlements,
1715
Experiments,
1816
} from "api/typesGenerated";
@@ -27,7 +25,6 @@ interface Appearance {
2725
}
2826

2927
export interface DashboardValue {
30-
buildInfo: BuildInfoResponse;
3128
entitlements: Entitlements;
3229
experiments: Experiments;
3330
appearance: Appearance;
@@ -38,16 +35,12 @@ export const DashboardContext = createContext<DashboardValue | undefined>(
3835
);
3936

4037
export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
41-
const buildInfoQuery = useQuery(buildInfo());
4238
const entitlementsQuery = useQuery(entitlements());
4339
const experimentsQuery = useQuery(experiments());
4440
const appearanceQuery = useQuery(appearance());
4541

4642
const isLoading =
47-
!buildInfoQuery.data ||
48-
!entitlementsQuery.data ||
49-
!appearanceQuery.data ||
50-
!experimentsQuery.data;
43+
!entitlementsQuery.data || !appearanceQuery.data || !experimentsQuery.data;
5144

5245
const [configPreview, setConfigPreview] = useState<AppearanceConfig>();
5346

@@ -84,7 +77,6 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
8477
return (
8578
<DashboardContext.Provider
8679
value={{
87-
buildInfo: buildInfoQuery.data,
8880
entitlements: entitlementsQuery.data,
8981
experiments: experimentsQuery.data,
9082
appearance: {

site/src/modules/dashboard/Navbar/Navbar.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import type { FC } from "react";
2+
import { useQuery } from "react-query";
3+
import { buildInfo } from "api/queries/buildInfo";
24
import { useAuthenticated } from "contexts/auth/RequireAuth";
35
import { useProxy } from "contexts/ProxyContext";
46
import { useDashboard } from "modules/dashboard/useDashboard";
57
import { useFeatureVisibility } from "../useFeatureVisibility";
68
import { NavbarView } from "./NavbarView";
79

810
export const Navbar: FC = () => {
9-
const { appearance, buildInfo } = useDashboard();
11+
const { appearance } = useDashboard();
12+
const buildInfoQuery = useQuery(buildInfo());
1013
const { user: me, permissions, signOut } = useAuthenticated();
1114
const featureVisibility = useFeatureVisibility();
1215
const canViewAuditLog =
@@ -19,7 +22,7 @@ export const Navbar: FC = () => {
1922
<NavbarView
2023
user={me}
2124
logo_url={appearance.config.logo_url}
22-
buildInfo={buildInfo}
25+
buildInfo={buildInfoQuery.data}
2326
supportLinks={appearance.config.support_links}
2427
onSignOut={signOut}
2528
canViewAuditLog={canViewAuditLog}

site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
22
import Badge from "@mui/material/Badge";
3-
import type { FC, ReactNode } from "react";
3+
import type { FC } from "react";
44
import type * as TypesGen from "api/typesGenerated";
55
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
66
import {
@@ -17,7 +17,6 @@ export interface UserDropdownProps {
1717
buildInfo?: TypesGen.BuildInfoResponse;
1818
supportLinks?: readonly TypesGen.LinkConfig[];
1919
onSignOut: () => void;
20-
children?: ReactNode;
2120
}
2221

2322
export const UserDropdown: FC<UserDropdownProps> = ({

site/src/modules/resources/AgentVersion.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const AgentVersion: FC<AgentVersionProps> = ({
2424
);
2525

2626
if (status === agentVersionStatus.Updated) {
27-
return <span>Updated</span>;
27+
return null;
2828
}
2929

3030
return (

site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.stories.tsx

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { DashboardContext } from "modules/dashboard/DashboardProvider";
33
import {
4+
MockAppearanceConfig,
5+
MockBuildInfo,
46
MockCanceledWorkspace,
57
MockCancelingWorkspace,
68
MockDeletedWorkspace,
79
MockDeletingWorkspace,
10+
MockEntitlementsWithScheduling,
11+
MockExperiments,
812
MockFailedWorkspace,
913
MockPendingWorkspace,
1014
MockStartingWorkspace,
1115
MockStoppedWorkspace,
1216
MockStoppingWorkspace,
1317
MockWorkspace,
14-
MockBuildInfo,
15-
MockEntitlementsWithScheduling,
16-
MockExperiments,
17-
MockAppearanceConfig,
1818
} from "testHelpers/entities";
1919
import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge";
2020

@@ -27,11 +27,18 @@ const MockedAppearance = {
2727
const meta: Meta<typeof WorkspaceStatusBadge> = {
2828
title: "modules/workspaces/WorkspaceStatusBadge",
2929
component: WorkspaceStatusBadge,
30+
parameters: {
31+
queries: [
32+
{
33+
key: ["buildInfo"],
34+
data: MockBuildInfo,
35+
},
36+
],
37+
},
3038
decorators: [
3139
(Story) => (
3240
<DashboardContext.Provider
3341
value={{
34-
buildInfo: MockBuildInfo,
3542
entitlements: MockEntitlementsWithScheduling,
3643
experiments: MockExperiments,
3744
appearance: MockedAppearance,

site/src/pages/LoginPage/LoginPage.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
33
import { useQuery } from "react-query";
44
import { Navigate, useLocation, useNavigate } from "react-router-dom";
5+
import { buildInfo } from "api/queries/buildInfo";
56
import { authMethods } from "api/queries/users";
67
import { useAuthContext } from "contexts/auth/AuthProvider";
78
import { getApplicationName } from "utils/appearance";
@@ -22,6 +23,7 @@ export const LoginPage: FC = () => {
2223
const redirectTo = retrieveRedirect(location.search);
2324
const applicationName = getApplicationName();
2425
const navigate = useNavigate();
26+
const buildInfoQuery = useQuery(buildInfo());
2527

2628
if (isSignedIn) {
2729
// If the redirect is going to a workspace application, and we
@@ -65,6 +67,7 @@ export const LoginPage: FC = () => {
6567
authMethods={authMethodsQuery.data}
6668
error={signInError}
6769
isLoading={isLoading || authMethodsQuery.isLoading}
70+
buildInfo={buildInfoQuery.data}
6871
isSigningIn={isSigningIn}
6972
onSignIn={async ({ email, password }) => {
7073
await signIn(email, password);

site/src/pages/LoginPage/LoginPageView.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Interpolation, Theme } from "@emotion/react";
22
import type { FC } from "react";
33
import { useLocation } from "react-router-dom";
4-
import type { AuthMethods } from "api/typesGenerated";
4+
import type { AuthMethods, BuildInfoResponse } from "api/typesGenerated";
55
import { CoderIcon } from "components/Icons/CoderIcon";
66
import { Loader } from "components/Loader/Loader";
77
import { getApplicationName, getLogoURL } from "utils/appearance";
@@ -12,6 +12,7 @@ export interface LoginPageViewProps {
1212
authMethods: AuthMethods | undefined;
1313
error: unknown;
1414
isLoading: boolean;
15+
buildInfo?: BuildInfoResponse;
1516
isSigningIn: boolean;
1617
onSignIn: (credentials: { email: string; password: string }) => void;
1718
}
@@ -20,6 +21,7 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
2021
authMethods,
2122
error,
2223
isLoading,
24+
buildInfo,
2325
isSigningIn,
2426
onSignIn,
2527
}) => {
@@ -64,7 +66,10 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
6466
/>
6567
)}
6668
<footer css={styles.footer}>
67-
Copyright © {new Date().getFullYear()} Coder Technologies, Inc.
69+
<div>
70+
Copyright &copy; {new Date().getFullYear()} Coder Technologies, Inc.
71+
</div>
72+
<div>{buildInfo?.version}</div>
6873
</footer>
6974
</div>
7075
</div>

site/src/pages/WorkspacePage/Workspace.stories.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const meta: Meta<typeof Workspace> = {
2727
component: Workspace,
2828
parameters: {
2929
queries: [
30+
{
31+
key: ["buildInfo"],
32+
data: Mocks.MockBuildInfo,
33+
},
3034
{
3135
key: ["portForward", Mocks.MockWorkspaceAgent.id],
3236
data: Mocks.MockListeningPortsResponse,
@@ -37,7 +41,6 @@ const meta: Meta<typeof Workspace> = {
3741
(Story) => (
3842
<DashboardContext.Provider
3943
value={{
40-
buildInfo: Mocks.MockBuildInfo,
4144
entitlements: Mocks.MockEntitlementsWithScheduling,
4245
experiments: Mocks.MockExperiments,
4346
appearance: MockedAppearance,

0 commit comments

Comments
 (0)