refactor: Refactor audit logs count to support filtering (#4113)

This commit is contained in:
Bruno Quaresma 2022-09-19 14:08:25 -03:00 committed by GitHub
parent 6f82ad09c8
commit adad347902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 164 additions and 13 deletions

View File

@ -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

View File

@ -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{
@ -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()
@ -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")
})
}
})
}

View File

@ -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) {

View File

@ -27,7 +27,7 @@ type querier interface {
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
GetActiveUserCount(ctx context.Context) (int64, error)
GetAuditLogCount(ctx context.Context) (int64, error)
GetAuditLogCount(ctx context.Context, arg GetAuditLogCountParams) (int64, error)
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
// ID.
GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOffsetParams) ([]GetAuditLogsOffsetRow, error)

View File

@ -292,10 +292,28 @@ SELECT
COUNT(*) as count
FROM
audit_logs
WHERE
-- Filter resource_type
CASE
WHEN $1 :: text != '' THEN
resource_type = $1 :: resource_type
ELSE true
END
-- Filter action
AND CASE
WHEN $2 :: text != '' THEN
action = $2 :: audit_action
ELSE true
END
`
func (q *sqlQuerier) GetAuditLogCount(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(ctx, getAuditLogCount)
type GetAuditLogCountParams struct {
ResourceType string `db:"resource_type" json:"resource_type"`
Action string `db:"action" json:"action"`
}
func (q *sqlQuerier) GetAuditLogCount(ctx context.Context, arg GetAuditLogCountParams) (int64, error) {
row := q.db.QueryRowContext(ctx, getAuditLogCount, arg.ResourceType, arg.Action)
var count int64
err := row.Scan(&count)
return count, err

View File

@ -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

View File

@ -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"`
}
@ -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
}

View File

@ -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
}

View File

@ -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