mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: refactor audit page to use window function for count (#5133)
* Move count query to window function * Unpack count and update types * Remove count endpoint * Update tests, wip * Fix tests * Update frontend, wip * Remove space * Fix frontend test * Don't hang on error * Handle no results * Don't omit count * Fix frontend tests
This commit is contained in:
@@ -556,14 +556,6 @@ export const getAuditLogs = async (
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getAuditLogsCount = async (
|
||||
options: TypesGen.AuditLogCountRequest = {},
|
||||
): Promise<TypesGen.AuditLogCountResponse> => {
|
||||
const url = getURLWithSearchParams("/api/v2/audit/count", options)
|
||||
const response = await axios.get(url)
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getTemplateDAUs = async (
|
||||
templateId: string,
|
||||
): Promise<TypesGen.TemplateDAUsResponse> => {
|
||||
|
||||
@@ -70,19 +70,10 @@ export interface AuditLog {
|
||||
readonly user?: User
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
export interface AuditLogCountRequest {
|
||||
readonly q?: string
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
export interface AuditLogCountResponse {
|
||||
readonly count: number
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
export interface AuditLogResponse {
|
||||
readonly audit_logs: AuditLog[]
|
||||
readonly count: number
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("readableActionMessage()", () => {
|
||||
|
||||
// Then
|
||||
expect(friendlyString).toBe(
|
||||
"<strong>TestUser</strong> updated workspace <strong>bruno-dev</strong>",
|
||||
"<strong>TestUser</strong> created workspace <strong>bruno-dev</strong>",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,33 +29,22 @@ describe("AuditPage", () => {
|
||||
|
||||
describe("Filtering", () => {
|
||||
it("filters by typing", async () => {
|
||||
const getAuditLogsSpy = jest
|
||||
.spyOn(API, "getAuditLogs")
|
||||
.mockResolvedValue({ audit_logs: [MockAuditLog] })
|
||||
|
||||
render(<AuditPage />)
|
||||
await waitForLoaderToBeRemoved()
|
||||
|
||||
// Reset spy so we can focus on the call with the filter
|
||||
getAuditLogsSpy.mockReset()
|
||||
await screen.findByText("updated", { exact: false })
|
||||
|
||||
const filterField = screen.getByLabelText("Filter")
|
||||
const query = "resource_type:workspace action:create"
|
||||
await userEvent.type(filterField, query)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getAuditLogsSpy).toBeCalledWith({
|
||||
limit: 25,
|
||||
offset: 0,
|
||||
q: query,
|
||||
}),
|
||||
)
|
||||
await screen.findByText("created", { exact: false })
|
||||
const editWorkspace = screen.queryByText("updated", { exact: false })
|
||||
expect(editWorkspace).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("filters by URL", async () => {
|
||||
const getAuditLogsSpy = jest
|
||||
.spyOn(API, "getAuditLogs")
|
||||
.mockResolvedValue({ audit_logs: [MockAuditLog] })
|
||||
.mockResolvedValue({ audit_logs: [MockAuditLog], count: 1 })
|
||||
|
||||
const query = "resource_type:workspace action:create"
|
||||
history.push(`/audit?filter=${encodeURIComponent(query)}`)
|
||||
@@ -67,15 +56,11 @@ describe("AuditPage", () => {
|
||||
})
|
||||
|
||||
it("resets page to 1 when filter is changed", async () => {
|
||||
const getAuditLogsSpy = jest
|
||||
.spyOn(API, "getAuditLogs")
|
||||
.mockResolvedValue({ audit_logs: [MockAuditLog] })
|
||||
|
||||
history.push(`/audit?page=2`)
|
||||
render(<AuditPage />)
|
||||
|
||||
await waitForLoaderToBeRemoved()
|
||||
getAuditLogsSpy.mockReset()
|
||||
const getAuditLogsSpy = jest.spyOn(API, "getAuditLogs")
|
||||
|
||||
const filterField = screen.getByLabelText("Filter")
|
||||
const query = "resource_type:workspace action:create"
|
||||
|
||||
@@ -959,7 +959,7 @@ export const MockAuditLog: TypesGen.AuditLog = {
|
||||
},
|
||||
status_code: 200,
|
||||
additional_fields: {},
|
||||
description: "{user} updated workspace {target}",
|
||||
description: "{user} created workspace {target}",
|
||||
user: MockUser,
|
||||
}
|
||||
|
||||
@@ -967,6 +967,8 @@ export const MockAuditLog2: TypesGen.AuditLog = {
|
||||
...MockAuditLog,
|
||||
id: "53bded77-7b9d-4e82-8771-991a34d759f9",
|
||||
action: "write",
|
||||
time: "2022-05-20T16:45:57.122Z",
|
||||
description: "{user} updated workspace {target}",
|
||||
diff: {
|
||||
workspace_name: {
|
||||
old: "old-workspace-name",
|
||||
|
||||
@@ -212,18 +212,20 @@ export const handlers = [
|
||||
|
||||
// Audit
|
||||
rest.get("/api/v2/audit", (req, res, ctx) => {
|
||||
const filter = req.url.searchParams.get("q") as string
|
||||
const logs =
|
||||
filter === "resource_type:workspace action:create"
|
||||
? [M.MockAuditLog]
|
||||
: [M.MockAuditLog, M.MockAuditLog2]
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
audit_logs: [M.MockAuditLog, M.MockAuditLog2],
|
||||
audit_logs: logs,
|
||||
count: logs.length,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
||||
rest.get("/api/v2/audit/count", (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json({ count: 1000 }))
|
||||
}),
|
||||
|
||||
// Applications host
|
||||
rest.get("/api/v2/applications/host", (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json({ host: "*.dev.coder.com" }))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getAuditLogs, getAuditLogsCount } from "api/api"
|
||||
import { getAuditLogs } from "api/api"
|
||||
import { getErrorMessage } from "api/errors"
|
||||
import { AuditLog } from "api/typesGenerated"
|
||||
import { AuditLog, AuditLogResponse } from "api/typesGenerated"
|
||||
import { displayError } from "components/GlobalSnackbar/utils"
|
||||
import { getPaginationData } from "components/PaginationWidget/utils"
|
||||
import {
|
||||
@@ -29,10 +29,7 @@ export const auditMachine = createMachine(
|
||||
context: {} as AuditContext,
|
||||
services: {} as {
|
||||
loadAuditLogsAndCount: {
|
||||
data: {
|
||||
auditLogs: AuditLog[]
|
||||
count: number
|
||||
}
|
||||
data: AuditLogResponse
|
||||
}
|
||||
},
|
||||
events: {} as
|
||||
@@ -59,17 +56,17 @@ export const auditMachine = createMachine(
|
||||
invoke: {
|
||||
src: "loadAuditLogsAndCount",
|
||||
onDone: {
|
||||
target: "success",
|
||||
target: "idle",
|
||||
actions: ["assignAuditLogsAndCount"],
|
||||
},
|
||||
onError: {
|
||||
target: "error",
|
||||
target: "idle",
|
||||
actions: ["displayApiError"],
|
||||
},
|
||||
},
|
||||
onDone: "success",
|
||||
onDone: "idle",
|
||||
},
|
||||
success: {
|
||||
idle: {
|
||||
on: {
|
||||
UPDATE_PAGE: {
|
||||
actions: ["updateURL"],
|
||||
@@ -80,9 +77,6 @@ export const auditMachine = createMachine(
|
||||
},
|
||||
},
|
||||
},
|
||||
error: {
|
||||
type: "final",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -91,7 +85,7 @@ export const auditMachine = createMachine(
|
||||
auditLogs: (_) => undefined,
|
||||
}),
|
||||
assignAuditLogsAndCount: assign({
|
||||
auditLogs: (_, event) => event.data.auditLogs,
|
||||
auditLogs: (_, event) => event.data.audit_logs,
|
||||
count: (_, event) => event.data.count,
|
||||
}),
|
||||
assignPaginationRef: assign({
|
||||
@@ -117,21 +111,11 @@ export const auditMachine = createMachine(
|
||||
loadAuditLogsAndCount: async (context) => {
|
||||
if (context.paginationRef) {
|
||||
const { offset, limit } = getPaginationData(context.paginationRef)
|
||||
const [auditLogs, count] = await Promise.all([
|
||||
getAuditLogs({
|
||||
offset,
|
||||
limit,
|
||||
q: context.filter,
|
||||
}).then((data) => data.audit_logs),
|
||||
getAuditLogsCount({
|
||||
q: context.filter,
|
||||
}).then((data) => data.count),
|
||||
])
|
||||
|
||||
return {
|
||||
auditLogs,
|
||||
count,
|
||||
}
|
||||
return getAuditLogs({
|
||||
offset,
|
||||
limit,
|
||||
q: context.filter,
|
||||
})
|
||||
} else {
|
||||
throw new Error("Cannot get audit logs without pagination data")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user