Skip to content

Commit 5edfccf

Browse files
stirbyParkreinerBrunoQuaresmaaslilac
authored
chore: patch 2.13.1 (coder#13927)
* fix: let workspace pages download partial logs for unhealthy workspaces (coder#13761) * fix: get basic fix in for preventing download logs from blowing up UI * fix: make sure blob units can't go out of bounds * fix: make sure timeout is cleared on component unmount * fix: reduce risk of shared cache state breaking useAgentLogs * fix: allow partial downloading of logs * fix: make sure useMemo cache is used properly * wip: commit current progress on updated logs functionality * docs: rewrite comment for clarity * refactor: clean up current code * fix: update styles for unavailable logs * fix: resolve linter violations * fix: update type signature of getErrorDetail * fix: revert log/enabled logic for useAgentLogs * fix: remove memoization from DownloadLogsDialog * fix: update name of timeout state * refactor: make log web sockets logic more clear * docs: reword comment for clarity * fix: commit current style update progress * fix: finish style updates (cherry picked from commit 940afa1) * fix(site): enable dormant workspace to be deleted (coder#13850) (cherry picked from commit 01b30ea) * chore: remove `organizationIds` from `AuthProvider` (coder#13917) (cherry picked from commit 80cbffe) --------- Co-authored-by: Michael Smith <throwawayclover@gmail.com> Co-authored-by: Bruno Quaresma <bruno@coder.com> Co-authored-by: Kayla Washburn-Love <mckayla@hey.com>
1 parent 56bf386 commit 5edfccf

File tree

12 files changed

+271
-115
lines changed

12 files changed

+271
-115
lines changed

site/src/api/errors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,18 @@ export const getValidationErrorMessage = (error: unknown): string => {
110110
return validationErrors.map((error) => error.detail).join("\n");
111111
};
112112

113-
export const getErrorDetail = (error: unknown): string | undefined | null => {
113+
export const getErrorDetail = (error: unknown): string | undefined => {
114114
if (error instanceof Error) {
115115
return "Please check the developer console for more details.";
116116
}
117+
117118
if (isApiError(error)) {
118119
return error.response.data.detail;
119120
}
121+
120122
if (isApiErrorResponse(error)) {
121123
return error.detail;
122124
}
123-
return null;
125+
126+
return undefined;
124127
};

site/src/contexts/auth/AuthProvider.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export type AuthContextValue = {
3030
isUpdatingProfile: boolean;
3131
user: User | undefined;
3232
permissions: Permissions | undefined;
33-
organizationIds: readonly string[] | undefined;
3433
signInError: unknown;
3534
updateProfileError: unknown;
3635
signOut: () => void;
@@ -119,7 +118,6 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
119118
permissions: permissionsQuery.data as Permissions | undefined,
120119
signInError: loginMutation.error,
121120
updateProfileError: updateProfileMutation.error,
122-
organizationIds: userQuery.data?.organization_ids,
123121
}}
124122
>
125123
{children}

site/src/contexts/auth/RequireAuth.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ describe("useAuthenticated", () => {
9595
wrapper: createAuthWrapper({
9696
user: MockUser,
9797
permissions: MockPermissions,
98-
organizationIds: [],
9998
}),
10099
});
101100
}).not.toThrow();

site/src/contexts/auth/RequireAuth.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ type RequireKeys<T, R extends keyof T> = Omit<T, R> & {
7474
// values are not undefined when authenticated
7575
type AuthenticatedAuthContextValue = RequireKeys<
7676
AuthContextValue,
77-
"user" | "permissions" | "organizationIds"
77+
"user" | "permissions"
7878
>;
7979

8080
export const useAuthenticated = (): AuthenticatedAuthContextValue => {
@@ -88,9 +88,5 @@ export const useAuthenticated = (): AuthenticatedAuthContextValue => {
8888
throw new Error("Permissions are not available.");
8989
}
9090

91-
if (!auth.organizationIds) {
92-
throw new Error("Organization ID is not available.");
93-
}
94-
9591
return auth as AuthenticatedAuthContextValue;
9692
};

site/src/modules/dashboard/DashboardProvider.tsx

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
createContext,
3-
type FC,
4-
type PropsWithChildren,
5-
useState,
6-
} from "react";
1+
import { createContext, type FC, type PropsWithChildren } from "react";
72
import { useQuery } from "react-query";
83
import { appearance } from "api/queries/appearance";
94
import { entitlements } from "api/queries/entitlements";
@@ -15,12 +10,14 @@ import type {
1510
} from "api/typesGenerated";
1611
import { Loader } from "components/Loader/Loader";
1712
import { useAuthenticated } from "contexts/auth/RequireAuth";
18-
import { useEffectEvent } from "hooks/hookPolyfills";
1913
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
2014

2115
export interface DashboardValue {
16+
/**
17+
* @deprecated Do not add new usage of this value. It is being removed as part
18+
* of the multi-org work.
19+
*/
2220
organizationId: string;
23-
setOrganizationId: (id: string) => void;
2421
entitlements: Entitlements;
2522
experiments: Experiments;
2623
appearance: AppearanceConfig;
@@ -32,40 +29,22 @@ export const DashboardContext = createContext<DashboardValue | undefined>(
3229

3330
export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
3431
const { metadata } = useEmbeddedMetadata();
35-
const { user, organizationIds } = useAuthenticated();
32+
const { user } = useAuthenticated();
3633
const entitlementsQuery = useQuery(entitlements(metadata.entitlements));
3734
const experimentsQuery = useQuery(experiments(metadata.experiments));
3835
const appearanceQuery = useQuery(appearance(metadata.appearance));
3936

4037
const isLoading =
4138
!entitlementsQuery.data || !appearanceQuery.data || !experimentsQuery.data;
4239

43-
const lastUsedOrganizationId = localStorage.getItem(
44-
`user:${user.id}.lastUsedOrganizationId`,
45-
);
46-
const [activeOrganizationId, setActiveOrganizationId] = useState(() =>
47-
lastUsedOrganizationId && organizationIds.includes(lastUsedOrganizationId)
48-
? lastUsedOrganizationId
49-
: organizationIds[0],
50-
);
51-
52-
const setOrganizationId = useEffectEvent((id: string) => {
53-
if (!organizationIds.includes(id)) {
54-
throw new ReferenceError("Invalid organization ID");
55-
}
56-
localStorage.setItem(`user:${user.id}.lastUsedOrganizationId`, id);
57-
setActiveOrganizationId(id);
58-
});
59-
6040
if (isLoading) {
6141
return <Loader fullscreen />;
6242
}
6343

6444
return (
6545
<DashboardContext.Provider
6646
value={{
67-
organizationId: activeOrganizationId,
68-
setOrganizationId: setOrganizationId,
47+
organizationId: user.organization_ids[0] ?? "default",
6948
entitlements: entitlementsQuery.data,
7049
experiments: experimentsQuery.data,
7150
appearance: appearanceQuery.data,

site/src/modules/resources/AgentLogs/useAgentLogs.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,35 @@ export type UseAgentLogsOptions = Readonly<{
1515
enabled?: boolean;
1616
}>;
1717

18+
/**
19+
* Defines a custom hook that gives you all workspace agent logs for a given
20+
* workspace.
21+
*
22+
* Depending on the status of the workspace, all logs may or may not be
23+
* available.
24+
*/
1825
export function useAgentLogs(
1926
options: UseAgentLogsOptions,
2027
): readonly WorkspaceAgentLog[] | undefined {
2128
const { workspaceId, agentId, agentLifeCycleState, enabled = true } = options;
29+
2230
const queryClient = useQueryClient();
2331
const queryOptions = agentLogs(workspaceId, agentId);
24-
const query = useQuery({
25-
...queryOptions,
26-
enabled,
27-
});
28-
const logs = query.data;
32+
const { data: logs, isFetched } = useQuery({ ...queryOptions, enabled });
2933

34+
// Track the ID of the last log received when the initial logs response comes
35+
// back. If the logs are not complete, the ID will mark the start point of the
36+
// Web sockets response so that the remaining logs can be received over time
3037
const lastQueriedLogId = useRef(0);
3138
useEffect(() => {
32-
if (logs && lastQueriedLogId.current === 0) {
33-
lastQueriedLogId.current = logs[logs.length - 1].id;
39+
const isAlreadyTracking = lastQueriedLogId.current !== 0;
40+
if (isAlreadyTracking) {
41+
return;
42+
}
43+
44+
const lastLog = logs?.at(-1);
45+
if (lastLog !== undefined) {
46+
lastQueriedLogId.current = lastLog.id;
3447
}
3548
}, [logs]);
3649

@@ -42,7 +55,7 @@ export function useAgentLogs(
4255
});
4356

4457
useEffect(() => {
45-
if (agentLifeCycleState !== "starting" || !query.isFetched) {
58+
if (agentLifeCycleState !== "starting" || !isFetched) {
4659
return;
4760
}
4861

@@ -69,7 +82,7 @@ export function useAgentLogs(
6982
return () => {
7083
socket.close();
7184
};
72-
}, [addLogs, agentId, agentLifeCycleState, query.isFetched]);
85+
}, [addLogs, agentId, agentLifeCycleState, isFetched]);
7386

7487
return logs;
7588
}

site/src/pages/OrganizationSettingsPage/OrganizationSettingsLayout.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createContext, type FC, Suspense, useContext } from "react";
22
import { useQuery } from "react-query";
3-
import { Outlet, useParams } from "react-router-dom";
3+
import { Outlet, useLocation, useParams } from "react-router-dom";
44
import { myOrganizations } from "api/queries/users";
55
import type { Organization } from "api/typesGenerated";
66
import { Loader } from "components/Loader/Loader";
@@ -13,7 +13,7 @@ import NotFoundPage from "pages/404Page/404Page";
1313
import { Sidebar } from "./Sidebar";
1414

1515
type OrganizationSettingsContextValue = {
16-
currentOrganizationId: string;
16+
currentOrganizationId?: string;
1717
organizations: Organization[];
1818
};
1919

@@ -32,13 +32,18 @@ export const useOrganizationSettings = (): OrganizationSettingsContextValue => {
3232
};
3333

3434
export const OrganizationSettingsLayout: FC = () => {
35-
const { permissions, organizationIds } = useAuthenticated();
35+
const location = useLocation();
36+
const { permissions } = useAuthenticated();
3637
const { experiments } = useDashboard();
3738
const { organization } = useParams() as { organization: string };
3839
const organizationsQuery = useQuery(myOrganizations());
3940

4041
const multiOrgExperimentEnabled = experiments.includes("multi-organization");
4142

43+
const inOrganizationSettings =
44+
location.pathname.startsWith("/organizations") &&
45+
location.pathname !== "/organizations/new";
46+
4247
if (!multiOrgExperimentEnabled) {
4348
return <NotFoundPage />;
4449
}
@@ -50,10 +55,13 @@ export const OrganizationSettingsLayout: FC = () => {
5055
{organizationsQuery.data ? (
5156
<OrganizationSettingsContext.Provider
5257
value={{
53-
currentOrganizationId:
54-
organizationsQuery.data.find(
55-
(org) => org.name === organization,
56-
)?.id ?? organizationIds[0],
58+
currentOrganizationId: !inOrganizationSettings
59+
? undefined
60+
: !organization
61+
? organizationsQuery.data[0]?.id
62+
: organizationsQuery.data.find(
63+
(org) => org.name === organization,
64+
)?.id,
5765
organizations: organizationsQuery.data,
5866
}}
5967
>

site/src/pages/OrganizationSettingsPage/OrganizationSettingsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ const OrganizationSettingsPage: FC = () => {
140140
css={styles.dangerButton}
141141
variant="contained"
142142
onClick={() =>
143-
deleteOrganizationMutation.mutate(currentOrganizationId)
143+
deleteOrganizationMutation.mutate(currentOrganizationId!)
144144
}
145145
>
146146
Delete this organization

0 commit comments

Comments
 (0)