Skip to content

refactor: Refactor audit logs count to support filtering #4113

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 3 commits into from
Sep 19, 2022
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
15 changes: 14 additions & 1 deletion coderd/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,20 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) {
return
}

count, err := api.Database.GetAuditLogCount(ctx)
queryStr := r.URL.Query().Get("q")
filter, errs := auditSearchQuery(queryStr)
if len(errs) > 0 {
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid audit search query.",
Validations: errs,
})
return
}

count, err := api.Database.GetAuditLogCount(ctx, database.GetAuditLogCountParams{
ResourceType: filter.ResourceType,
Action: filter.Action,
})
if err != nil {
httpapi.InternalServerError(rw, err)
return
Expand Down
74 changes: 72 additions & 2 deletions coderd/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestAuditLogs(t *testing.T) {
err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{})
require.NoError(t, err)

count, err := client.AuditLogCount(ctx)
count, err := client.AuditLogCount(ctx, codersdk.AuditLogCountRequest{})
require.NoError(t, err)

alogs, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{
Expand All @@ -41,7 +41,7 @@ func TestAuditLogs(t *testing.T) {
func TestAuditLogsFilter(t *testing.T) {
t.Parallel()

t.Run("FilterByResourceType", func(t *testing.T) {
t.Run("Filter", func(t *testing.T) {
t.Parallel()

ctx := context.Background()
Expand Down Expand Up @@ -110,3 +110,73 @@ func TestAuditLogsFilter(t *testing.T) {
}
})
}

func TestAuditLogCountFilter(t *testing.T) {
t.Parallel()

t.Run("Filter", func(t *testing.T) {
t.Parallel()

ctx := context.Background()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)

// Create two logs with "Create"
err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
Action: codersdk.AuditActionCreate,
ResourceType: codersdk.ResourceTypeTemplate,
})
require.NoError(t, err)
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
Action: codersdk.AuditActionCreate,
ResourceType: codersdk.ResourceTypeUser,
})
require.NoError(t, err)

// Create one log with "Delete"
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
Action: codersdk.AuditActionDelete,
ResourceType: codersdk.ResourceTypeUser,
})
require.NoError(t, err)

// Test cases
testCases := []struct {
Name string
SearchQuery string
ExpectedResult int64
}{
{
Name: "FilterByCreateAction",
SearchQuery: "action:create",
ExpectedResult: 2,
},
{
Name: "FilterByDeleteAction",
SearchQuery: "action:delete",
ExpectedResult: 1,
},
{
Name: "FilterByUserResourceType",
SearchQuery: "resource_type:user",
ExpectedResult: 2,
},
{
Name: "FilterByTemplateResourceType",
SearchQuery: "resource_type:template",
ExpectedResult: 1,
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
t.Parallel()
response, err := client.AuditLogCount(ctx, codersdk.AuditLogCountRequest{
SearchQuery: testCase.SearchQuery,
})
require.NoError(t, err, "fetch audit logs count")
require.Equal(t, response.Count, testCase.ExpectedResult, "expected audit logs count returned")
})
}
})
}
18 changes: 16 additions & 2 deletions coderd/database/databasefake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -2402,11 +2402,25 @@ func (q *fakeQuerier) GetAuditLogsOffset(ctx context.Context, arg database.GetAu
return logs, nil
}

func (q *fakeQuerier) GetAuditLogCount(_ context.Context) (int64, error) {
func (q *fakeQuerier) GetAuditLogCount(_ context.Context, arg database.GetAuditLogCountParams) (int64, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()

return int64(len(q.auditLogs)), nil
logs := make([]database.AuditLog, 0)

for _, alog := range q.auditLogs {
if arg.Action != "" && !strings.Contains(string(alog.Action), arg.Action) {
continue
}

if arg.ResourceType != "" && !strings.Contains(string(alog.ResourceType), arg.ResourceType) {
continue
}

logs = append(logs, alog)
}

return int64(len(logs)), nil
}

func (q *fakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAuditLogParams) (database.AuditLog, error) {
Expand Down
2 changes: 1 addition & 1 deletion coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 20 additions & 2 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion coderd/database/queries/auditlogs.sql
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,20 @@ OFFSET
SELECT
COUNT(*) as count
FROM
audit_logs;
audit_logs
WHERE
-- Filter resource_type
CASE
WHEN @resource_type :: text != '' THEN
resource_type = @resource_type :: resource_type
ELSE true
END
-- Filter action
AND CASE
WHEN @action :: text != '' THEN
action = @action :: audit_action
ELSE true
END;

-- name: InsertAuditLog :one
INSERT INTO
Expand Down
16 changes: 14 additions & 2 deletions codersdk/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ type AuditLogResponse struct {
AuditLogs []AuditLog `json:"audit_logs"`
}

type AuditLogCountRequest struct {
SearchQuery string `json:"q,omitempty"`
}

type AuditLogCountResponse struct {
Count int64 `json:"count"`
}
Expand Down Expand Up @@ -142,8 +146,16 @@ func (c *Client) AuditLogs(ctx context.Context, req AuditLogsRequest) (AuditLogR
}

// AuditLogCount returns the count of all audit logs in the product.
func (c *Client) AuditLogCount(ctx context.Context) (AuditLogCountResponse, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/audit/count", nil)
func (c *Client) AuditLogCount(ctx context.Context, req AuditLogCountRequest) (AuditLogCountResponse, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/audit/count", nil, func(r *http.Request) {
q := r.URL.Query()
var params []string
if req.SearchQuery != "" {
params = append(params, req.SearchQuery)
}
q.Set("q", strings.Join(params, " "))
r.URL.RawQuery = q.Encode()
})
if err != nil {
return AuditLogCountResponse{}, err
}
Expand Down
10 changes: 8 additions & 2 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,14 @@ export const getAuditLogs = async (
return response.data
}

export const getAuditLogsCount = async (): Promise<TypesGen.AuditLogCountResponse> => {
const response = await axios.get(`/api/v2/audit/count`)
export const getAuditLogsCount = async (
options: TypesGen.AuditLogCountRequest = {},
): Promise<TypesGen.AuditLogCountResponse> => {
const searchParams = new URLSearchParams()
if (options.q) {
searchParams.set("q", options.q)
}
const response = await axios.get(`/api/v2/audit/count?${searchParams.toString()}`)
return response.data
}

Expand Down
5 changes: 5 additions & 0 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ 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
Expand Down