mirror of https://github.com/coder/coder.git
refactor: Refactor audit logs count to support filtering (#4113)
This commit is contained in:
parent
6f82ad09c8
commit
adad347902
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue