Skip to content
Closed
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
2 changes: 2 additions & 0 deletions api/handler/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ func (h *UserHandler) GetFinetuneInstances(ctx *gin.Context) {
// @Param per query int false "per" default(50)
// @Param page query int false "page index" default(1)
// @Param current_user query string false "current user"
// @Param search query string false "search by path or deployname"
// @Success 200 {object} types.ResponseWithTotal{data=[]types.DeployRepo,total=int} "OK"
// @Failure 400 {object} types.APIBadRequest "Bad request"
// @Failure 500 {object} types.APIInternalServerError "Internal server error"
Expand All @@ -722,6 +723,7 @@ func (h *UserHandler) GetRunServerless(ctx *gin.Context) {
req.PageSize = per
req.RepoType = types.ModelRepo
req.DeployType = types.ServerlessType
req.Query = ctx.Query("search")
ds, total, err := h.user.ListServerless(ctx.Request.Context(), req)
if err != nil {
slog.ErrorContext(ctx.Request.Context(), "Failed to get serverless list", slog.Any("error", err), slog.Any("req", req))
Expand Down
15 changes: 12 additions & 3 deletions builder/store/database/deploy_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"log/slog"
"strings"
"time"

"github.com/uptrace/bun"
Expand Down Expand Up @@ -368,13 +369,21 @@ func (s *deployTaskStoreImpl) ListServerless(ctx context.Context, req types.Depl
var result []Deploy
query := s.db.Operator.Core.NewSelect().Model(&result).Where("type = ?", req.DeployType)
query = query.Where("status != ?", common.Deleted)
query = query.Limit(req.PageSize).Offset((req.Page - 1) * req.PageSize)
_, err := query.Exec(ctx, &result)

searchQuery := strings.TrimSpace(req.Query)
if searchQuery != "" {
searchPattern := "%" + strings.ToLower(searchQuery) + "%"
query = query.Where("LOWER(deploy_name) LIKE ? OR LOWER(git_path) LIKE ?", searchPattern, searchPattern)
}

total, err := query.Count(ctx)
if err != nil {
err = errorx.HandleDBError(err, nil)
return nil, 0, err
}
total, err := query.Count(ctx)

query = query.Limit(req.PageSize).Offset((req.Page - 1) * req.PageSize)
_, err = query.Exec(ctx, &result)
if err != nil {
err = errorx.HandleDBError(err, nil)
return nil, 0, err
Expand Down
303 changes: 303 additions & 0 deletions builder/store/database/deploy_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,3 +653,306 @@
err = store.DeleteDeployByID(ctx, 100, 999999)
require.NotNil(t, err)
}

func TestDeployTaskStore_GetLatestDeploysBySpaceIDs(t *testing.T) {
db := tests.InitTestDB()
defer db.Close()
ctx := context.TODO()
store := database.NewDeployTaskStoreWithDB(db)

// Test with empty spaceIDs
result, err := store.GetLatestDeploysBySpaceIDs(ctx, []int64{})

Check failure on line 664 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / test (1.24)

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 664 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / lint

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 664 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / lint

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 664 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / test (1.24)

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)
require.Nil(t, err)
require.NotNil(t, result)
require.Equal(t, 0, len(result))

// Create test data: multiple deploys for different space IDs
// Space 100: 3 deploys (should return the latest)
// Space 200: 2 deploys (should return the latest)
// Space 300: 1 deploy (should return that one)
// Space 400: no deploys (should not appear in result)

now := time.Now().UTC()
space100Deploys := []database.Deploy{
{SpaceID: 100, DeployName: "space100-old", SvcName: "svc100-1", UserID: 1, RepoID: 1, GitPath: "test", GitBranch: "main", Template: "test", Hardware: "test"},
{SpaceID: 100, DeployName: "space100-middle", SvcName: "svc100-2", UserID: 1, RepoID: 1, GitPath: "test", GitBranch: "main", Template: "test", Hardware: "test"},
{SpaceID: 100, DeployName: "space100-latest", SvcName: "svc100-3", UserID: 1, RepoID: 1, GitPath: "test", GitBranch: "main", Template: "test", Hardware: "test"},
}

space200Deploys := []database.Deploy{
{SpaceID: 200, DeployName: "space200-old", SvcName: "svc200-1", UserID: 1, RepoID: 2, GitPath: "test", GitBranch: "main", Template: "test", Hardware: "test"},
{SpaceID: 200, DeployName: "space200-latest", SvcName: "svc200-2", UserID: 1, RepoID: 2, GitPath: "test", GitBranch: "main", Template: "test", Hardware: "test"},
}

space300Deploy := database.Deploy{
SpaceID: 300, DeployName: "space300-single", SvcName: "svc300-1", UserID: 1, RepoID: 3, GitPath: "test", GitBranch: "main", Template: "test", Hardware: "test",
}

// Create deploys with different timestamps
for i, dp := range space100Deploys {
err := store.CreateDeploy(ctx, &dp)
require.Nil(t, err)
// Set created_at to different times (oldest first)
_, err = db.BunDB.ExecContext(ctx, "UPDATE deploys SET created_at = ?, updated_at = ? WHERE id = ?",
now.Add(-time.Duration(3-i)*time.Hour), now.Add(-time.Duration(3-i)*time.Hour), dp.ID)
require.NoError(t, err)
}

for i, dp := range space200Deploys {
err := store.CreateDeploy(ctx, &dp)
require.Nil(t, err)
// Set created_at to different times (oldest first)
_, err = db.BunDB.ExecContext(ctx, "UPDATE deploys SET created_at = ?, updated_at = ? WHERE id = ?",
now.Add(-time.Duration(2-i)*time.Hour), now.Add(-time.Duration(2-i)*time.Hour), dp.ID)
require.NoError(t, err)
}

err = store.CreateDeploy(ctx, &space300Deploy)
require.Nil(t, err)

// Test: Get latest deploys for space 100, 200, 300, 400
spaceIDs := []int64{100, 200, 300, 400}
result, err = store.GetLatestDeploysBySpaceIDs(ctx, spaceIDs)

Check failure on line 715 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / test (1.24)

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 715 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / lint

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 715 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / lint

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 715 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / test (1.24)

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)
require.Nil(t, err)
require.NotNil(t, result)

// Should have 3 results (space 400 has no deploys, so won't appear)
require.Equal(t, 3, len(result))

// Verify space 100 has the latest deploy
deploy100, exists := result[100]
require.True(t, exists)
require.NotNil(t, deploy100)
require.Equal(t, "space100-latest", deploy100.DeployName)
require.Equal(t, "svc100-3", deploy100.SvcName)

// Verify space 200 has the latest deploy
deploy200, exists := result[200]
require.True(t, exists)
require.NotNil(t, deploy200)
require.Equal(t, "space200-latest", deploy200.DeployName)
require.Equal(t, "svc200-2", deploy200.SvcName)

// Verify space 300 has its deploy
deploy300, exists := result[300]
require.True(t, exists)
require.NotNil(t, deploy300)
require.Equal(t, "space300-single", deploy300.DeployName)
require.Equal(t, "svc300-1", deploy300.SvcName)

// Verify space 400 is not in the result (no deploys)
_, exists = result[400]
require.False(t, exists)

// Test with only space IDs that don't exist
result, err = store.GetLatestDeploysBySpaceIDs(ctx, []int64{999, 998})

Check failure on line 748 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / test (1.24)

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)

Check failure on line 748 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / lint

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs) (typecheck)

Check failure on line 748 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / lint

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs) (typecheck)

Check failure on line 748 in builder/store/database/deploy_task_test.go

View workflow job for this annotation

GitHub Actions / test (1.24)

store.GetLatestDeploysBySpaceIDs undefined (type "opencsg.com/csghub-server/builder/store/database".DeployTaskStore has no field or method GetLatestDeploysBySpaceIDs)
require.Nil(t, err)
require.NotNil(t, result)
require.Equal(t, 0, len(result))
}

func TestDeployTaskStore_ListServerless_Search(t *testing.T) {
db := tests.InitTestDB()
defer db.Close()
ctx := context.TODO()

store := database.NewDeployTaskStoreWithDB(db)

// Create test serverless deploys with different deploy names and git paths
deploys := []database.Deploy{
{
DeployName: "qwen-model-deploy",
GitPath: "models_namespace1/qwen-model",
GitBranch: "main",
Template: "test",
Hardware: "test",
Type: types.ServerlessType,
Status: common.Running,
RepoID: 1,
UserID: 1,
SpaceID: 0,
SvcName: "svc1",
},
{
DeployName: "test-deploy",
GitPath: "models_namespace2/test-model",
GitBranch: "main",
Template: "test",
Hardware: "test",
Type: types.ServerlessType,
Status: common.Running,
RepoID: 2,
UserID: 1,
SpaceID: 0,
SvcName: "svc2",
},
{
DeployName: "QWEN-Deploy-Upper",
GitPath: "models_namespace3/another-model",
GitBranch: "main",
Template: "test",
Hardware: "test",
Type: types.ServerlessType,
Status: common.Running,
RepoID: 3,
UserID: 1,
SpaceID: 0,
SvcName: "svc3",
},
{
DeployName: "other-deploy",
GitPath: "models_namespace4/qwen-other",
GitBranch: "main",
Template: "test",
Hardware: "test",
Type: types.ServerlessType,
Status: common.Running,
RepoID: 4,
UserID: 1,
SpaceID: 0,
SvcName: "svc4",
},
{
DeployName: "deleted-deploy",
GitPath: "models_namespace5/qwen-deleted",
GitBranch: "main",
Template: "test",
Hardware: "test",
Type: types.ServerlessType,
Status: common.Deleted,
RepoID: 5,
UserID: 1,
SpaceID: 0,
SvcName: "svc5",
},
{
DeployName: "non-serverless",
GitPath: "models_namespace6/qwen-non-serverless",
GitBranch: "main",
Template: "test",
Hardware: "test",
Type: types.InferenceType,
Status: common.Running,
RepoID: 6,
UserID: 1,
SpaceID: 0,
SvcName: "svc6",
},
}

for _, dp := range deploys {
err := store.CreateDeploy(ctx, &dp)
require.Nil(t, err)
}

// Test 1: List all serverless (no search)
dps, total, err := store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 4, total) // 4 serverless deploys (excluding deleted and non-serverless)
require.Equal(t, 4, len(dps))

// Test 2: Search by deploy_name (case-insensitive)
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: "qwen",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 3, total) // qwen-model-deploy, QWEN-Deploy-Upper, qwen-other (in git_path)
require.Equal(t, 3, len(dps))
deployNames := []string{}
for _, dp := range dps {
deployNames = append(deployNames, dp.DeployName)
}
require.Contains(t, deployNames, "qwen-model-deploy")
require.Contains(t, deployNames, "QWEN-Deploy-Upper")
require.Contains(t, deployNames, "other-deploy") // matches git_path

// Test 3: Search by git_path
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: "namespace2",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 1, total)
require.Equal(t, 1, len(dps))
require.Equal(t, "test-deploy", dps[0].DeployName)

// Test 4: Search with uppercase (case-insensitive)
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: "QWEN",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 3, total) // Should match lowercase and uppercase
require.Equal(t, 3, len(dps))

// Test 5: Search with empty string (should return all)
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: "",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 4, total)
require.Equal(t, 4, len(dps))

// Test 6: Search with whitespace (should be trimmed and return all)
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: " ",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 4, total)
require.Equal(t, 4, len(dps))

// Test 7: Search with no matches
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: "nonexistent",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 10,
},
})
require.Nil(t, err)
require.Equal(t, 0, total)
require.Equal(t, 0, len(dps))

// Test 8: Search with pagination
dps, total, err = store.ListServerless(ctx, types.DeployReq{
DeployType: types.ServerlessType,
Query: "qwen",
PageOpts: types.PageOpts{
Page: 1,
PageSize: 2,
},
})
require.Nil(t, err)
require.Equal(t, 3, total) // Total should be 3
require.Equal(t, 2, len(dps)) // But only 2 per page
}
Loading