Skip to content

feat(site): add download logs option #13466

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 27 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
051217a
Download agent logs
BrunoQuaresma Jun 3, 2024
c9af899
Download workspace logs
BrunoQuaresma Jun 3, 2024
e479cf2
Use Logs instead of Agent Logs
BrunoQuaresma Jun 3, 2024
9ccc9e1
Centralize items list
BrunoQuaresma Jun 3, 2024
128f938
Init files with build logs
BrunoQuaresma Jun 3, 2024
4d5c9cb
Add tests for the download logs dialog
BrunoQuaresma Jun 4, 2024
11b528a
Add test to verify if download dialog is opening
BrunoQuaresma Jun 4, 2024
efcee2b
Add tests to download agent logs
BrunoQuaresma Jun 4, 2024
895e942
Fix package.json
BrunoQuaresma Jun 4, 2024
9ee7fd4
Commit Asher suggestions
BrunoQuaresma Jun 4, 2024
a71529a
Fix open download logs story
BrunoQuaresma Jun 5, 2024
f82a4e4
Fix dropdown arrow on light theme
BrunoQuaresma Jun 5, 2024
967fc0b
Enable agent logs only when showing
BrunoQuaresma Jun 5, 2024
73af195
Use web socket decorator and remove specific props for storybook
BrunoQuaresma Jun 5, 2024
f145ead
Refactor useAgentLogs to optimize loading
BrunoQuaresma Jun 5, 2024
5baa052
Add prefetch back
BrunoQuaresma Jun 5, 2024
40d2a65
Only fetch logs when clicking on download
BrunoQuaresma Jun 5, 2024
35368ee
Add useAgentLogs test
BrunoQuaresma Jun 5, 2024
10b4b71
Use Michael refactoring of useAgentLogs
BrunoQuaresma Jun 6, 2024
8da2d28
Improve naming
BrunoQuaresma Jun 6, 2024
b54c72c
Improve naming
BrunoQuaresma Jun 6, 2024
83f60dd
Fix hook usage
BrunoQuaresma Jun 6, 2024
6f85490
allow pkg
sreya Jun 6, 2024
270e04e
Rollback useAgentLogs implementation
BrunoQuaresma Jun 6, 2024
a6ab9fd
Add decorators to fix storybook
BrunoQuaresma Jun 6, 2024
c1f281c
Merge branch 'bq/download-logs' of https://github.com/coder/coder int…
BrunoQuaresma Jun 6, 2024
fab7d56
Apply Asher suggestions
BrunoQuaresma Jun 7, 2024
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
Prev Previous commit
Next Next commit
Add useAgentLogs test
  • Loading branch information
BrunoQuaresma committed Jun 5, 2024
commit 35368ee06de19798e25bca43bb06bb9b5fd2c01a
2 changes: 1 addition & 1 deletion site/src/api/queries/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UseQueryOptions, QueryKey } from "react-query";
import type { MetadataState, MetadataValue } from "hooks/useEmbeddedMetadata";

const disabledFetchOptions = {
export const disabledFetchOptions = {
cacheTime: Infinity,
staleTime: Infinity,
refetchOnMount: false,
Expand Down
2 changes: 2 additions & 0 deletions site/src/api/queries/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
WorkspacesRequest,
WorkspacesResponse,
} from "api/typesGenerated";
import { disabledFetchOptions } from "./util";
import { workspaceBuildsKey } from "./workspaceBuilds";

export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
Expand Down Expand Up @@ -309,5 +310,6 @@ export const agentLogs = (workspaceId: string, agentId: string) => {
return {
queryKey: agentLogsKey(workspaceId, agentId),
queryFn: () => API.getWorkspaceAgentLogs(agentId),
...disabledFetchOptions,
};
};
137 changes: 137 additions & 0 deletions site/src/modules/resources/AgentLogs/useAgentLogs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { act, renderHook, waitFor } from "@testing-library/react";
import WS from "jest-websocket-mock";
import { type QueryClient, QueryClientProvider } from "react-query";
import { API } from "api/api";
import * as APIModule from "api/api";
import { agentLogsKey } from "api/queries/workspaces";
import type {
WorkspaceAgentLifecycle,
WorkspaceAgentLog,
} from "api/typesGenerated";
import { MockWorkspace, MockWorkspaceAgent } from "testHelpers/entities";
import { createTestQueryClient } from "testHelpers/renderHelpers";
import { type UseAgentLogsOptions, useAgentLogs } from "./useAgentLogs";

afterEach(() => {
WS.clean();
});

describe("useAgentLogs", () => {
it("should not fetch logs if disabled", async () => {
const queryClient = createTestQueryClient();
const fetchSpy = jest.spyOn(API, "getWorkspaceAgentLogs");
const wsSpy = jest.spyOn(APIModule, "watchWorkspaceAgentLogs");
renderUseAgentLogs(queryClient, "ready", { enabled: false });
expect(fetchSpy).not.toHaveBeenCalled();
expect(wsSpy).not.toHaveBeenCalled();
});

it("should return existing logs without network calls", async () => {
const queryClient = createTestQueryClient();
queryClient.setQueryData(
agentLogsKey(MockWorkspace.id, MockWorkspaceAgent.id),
generateLogs(5),
);
const fetchSpy = jest.spyOn(API, "getWorkspaceAgentLogs");
const wsSpy = jest.spyOn(APIModule, "watchWorkspaceAgentLogs");
const { result } = renderUseAgentLogs(queryClient, "ready");
await waitFor(() => {
expect(result.current).toHaveLength(5);
});
expect(fetchSpy).not.toHaveBeenCalled();
expect(wsSpy).not.toHaveBeenCalled();
});

it("should fetch logs when empty", async () => {
const queryClient = createTestQueryClient();
const fetchSpy = jest
.spyOn(API, "getWorkspaceAgentLogs")
.mockResolvedValueOnce(generateLogs(5));
const wsSpy = jest.spyOn(APIModule, "watchWorkspaceAgentLogs");
const { result } = renderUseAgentLogs(queryClient, "ready");
await waitFor(() => {
expect(result.current).toHaveLength(5);
});
expect(fetchSpy).toHaveBeenCalledWith(MockWorkspaceAgent.id);
expect(wsSpy).not.toHaveBeenCalled();
});

it("should fetch logs and connect to websocket when agent is starting", async () => {
const queryClient = createTestQueryClient();
const logs = generateLogs(5);
const fetchSpy = jest
.spyOn(API, "getWorkspaceAgentLogs")
.mockResolvedValueOnce(logs);
const wsSpy = jest.spyOn(APIModule, "watchWorkspaceAgentLogs");
new WS(
`ws://localhost/api/v2/workspaceagents/${
MockWorkspaceAgent.id
}/logs?follow&after=${logs[logs.length - 1].id}`,
);
const { result } = renderUseAgentLogs(queryClient, "starting");
await waitFor(() => {
expect(result.current).toHaveLength(5);
});
expect(fetchSpy).toHaveBeenCalledWith(MockWorkspaceAgent.id);
expect(wsSpy).toHaveBeenCalledWith(MockWorkspaceAgent.id, {
after: logs[logs.length - 1].id,
onMessage: expect.any(Function),
onError: expect.any(Function),
});
});

it("update logs from websocket messages", async () => {
const queryClient = createTestQueryClient();
const logs = generateLogs(5);
jest.spyOn(API, "getWorkspaceAgentLogs").mockResolvedValueOnce(logs);
const server = new WS(
`ws://localhost/api/v2/workspaceagents/${
MockWorkspaceAgent.id
}/logs?follow&after=${logs[logs.length - 1].id}`,
);
const { result } = renderUseAgentLogs(queryClient, "starting");
await waitFor(() => {
expect(result.current).toHaveLength(5);
});
await server.connected;
act(() => {
server.send(JSON.stringify(generateLogs(3)));
});
await waitFor(() => {
expect(result.current).toHaveLength(8);
});
});
});

function renderUseAgentLogs(
queryClient: QueryClient,
lifeCycleState: WorkspaceAgentLifecycle,
options?: UseAgentLogsOptions,
) {
return renderHook(
() =>
useAgentLogs(
MockWorkspace.id,
MockWorkspaceAgent.id,
lifeCycleState,
options,
),
{
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
},
);
}

function generateLogs(count: number): WorkspaceAgentLog[] {
return Array.from({ length: count }, (_, i) => ({
id: i,
created_at: new Date().toISOString(),
level: "info",
output: `Log ${i}`,
source_id: "",
}));
}
6 changes: 5 additions & 1 deletion site/src/modules/resources/AgentLogs/useAgentLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import type {
} from "api/typesGenerated";
import { useEffectEvent } from "hooks/hookPolyfills";

export type UseAgentLogsOptions = {
enabled?: boolean;
};

export const useAgentLogs = (
workspaceId: string,
agentId: string,
agentLifeCycleState: WorkspaceAgentLifecycle,
options?: { enabled?: boolean },
options?: UseAgentLogsOptions,
) => {
const queryClient = useQueryClient();
const queryOptions = agentLogs(workspaceId, agentId);
Expand Down
Loading