From cf22473a394544a7c22d01618d38cb765a56ffa3 Mon Sep 17 00:00:00 2001 From: duytran Date: Fri, 6 Jun 2025 11:41:58 +0700 Subject: [PATCH 1/2] testing add comment - suggestion code --- .dagger/debugger.go | 59 ++++++++++------------ .dagger/frontend/.gitattributes | 4 ++ .dagger/frontend/.gitignore | 4 ++ .dagger/frontend/dagger.json | 7 +++ .dagger/frontend/go.mod | 50 +++++++++++++++++++ .dagger/frontend/go.sum | 85 ++++++++++++++++++++++++++++++++ .dagger/frontend/main.go | 40 +++++++++++++++ .dagger/main.go | 27 +++++----- .dagger/sugesstions.go | 75 +++++++++++----------------- .dagger/workspace/main.go | 21 +++++--- .github/workflows/3_wf_test.yaml | 17 ++++--- dagger.json | 4 ++ website/src/App.test.js | 2 +- 13 files changed, 290 insertions(+), 105 deletions(-) create mode 100644 .dagger/frontend/.gitattributes create mode 100644 .dagger/frontend/.gitignore create mode 100644 .dagger/frontend/dagger.json create mode 100644 .dagger/frontend/go.mod create mode 100644 .dagger/frontend/go.sum create mode 100644 .dagger/frontend/main.go diff --git a/.dagger/debugger.go b/.dagger/debugger.go index 5717870..b114e3d 100644 --- a/.dagger/debugger.go +++ b/.dagger/debugger.go @@ -18,21 +18,24 @@ func (p *PipelineDagger) DebugUT( // Detailed prompt stored in markdown file prompt := dag.CurrentModule().Source().File("prompts/fix_test.md") + ws := dag.Workspace( + p.Frontend.Source(), + p.Frontend.AsWorkspaceCheckable(), + ) + // Environment with agent inputs and outputs environment := dag.Env(). - WithWorkspaceInput("workspace", dag.Workspace(p.Source), "workspace to read, write, and test code"). + WithWorkspaceInput("workspace", ws, "workspace to read, write, and test code"). WithWorkspaceOutput("fixed", "workspace with fixed tests") // Put it all together to form the agent (LLM agent that fixes the tests) - work := dag.LLM(dagger.LLMOpts{Model: model}). + return dag.LLM(dagger.LLMOpts{Model: model}). WithEnv(environment). - WithPromptFile(prompt) - - // Bind the LLM's output to the workspace output - environment = work.Env() - - // Get output from the agent and return the diff - return environment.Output("fixed").AsWorkspace().Diff(ctx) + WithPromptFile(prompt). + Env(). // Bind the LLM's output to the workspace output + Output("fixed"). + AsWorkspace(). + Diff(ctx) // Get output from the agent and return the diff } // Suggest fixes to a Issues @@ -49,57 +52,47 @@ func (p *PipelineDagger) DebugUTIssues( ) error { gh := dag.GithubIssue(dagger.GithubIssueOpts{Token: githubToken}) + // Determine PR head + gitRef := dag.Git(p.RepoFE).Commit(commit) + gitSource := gitRef.Tree() pr, err := gh.GetPrForCommit(ctx, p.RepoFE, commit) if err != nil { return fmt.Errorf("failed to get PR for commit: %w", err) } + // Set source to PR head + p = New(gitSource, p.RepoFE) + // Suggest fix suggestionDiff, err := p.DebugUT(ctx, model) if err != nil { - return fmt.Errorf("failed to debug UT: %w", err) + return fmt.Errorf("debug UT failed: %w", err) } - if suggestionDiff == "" { + if strings.TrimSpace(suggestionDiff) == "" { return fmt.Errorf("no suggestions found") } fmt.Printf("Raw diff content:\n%s\n", suggestionDiff) // Convert the diff to CodeSuggestions - codeSuggestions, err := parseDiff(suggestionDiff) - if err != nil { - return fmt.Errorf("failed to parse diff: %w", err) - } + codeSuggestions := parseDiff(suggestionDiff) fmt.Printf("Number of suggestions: %d\n", len(codeSuggestions)) // For each suggestion, comment on PR - for i, suggestion := range codeSuggestions { - // Ensure we have a valid file path (remove any 'b/' prefix that git diff adds) - filePath := strings.TrimPrefix(suggestion.File, "b/") - - fmt.Printf("\nSuggestion %d:\n", i+1) - fmt.Printf("File: %s\n", filePath) - fmt.Printf("Line: %d\n", suggestion.Line) - fmt.Printf("DiffHunk:\n%s\n", suggestion.DiffHunk) - fmt.Printf("Suggestion:\n%s\n", strings.Join(suggestion.Suggestion, "\n")) - - // Create the comment with the required diff_hunk - comment := fmt.Sprintf("```diff\n%s\n```\n\n```suggestion\n%s\n```", - suggestion.DiffHunk, - strings.Join(suggestion.Suggestion, "\n")) - + for _, suggestion := range codeSuggestions { + markupSuggestion := "```suggestion\n" + strings.Join(suggestion.Suggestion, "\n") + "\n```" err := gh.WritePullRequestCodeComment( ctx, p.RepoFE, pr, commit, - comment, - filePath, + markupSuggestion, + suggestion.File, "RIGHT", suggestion.Line) if err != nil { - return fmt.Errorf("failed to write PR comment for file %s at line %d: %w", filePath, suggestion.Line, err) + return err } } return nil diff --git a/.dagger/frontend/.gitattributes b/.dagger/frontend/.gitattributes new file mode 100644 index 0000000..3a45493 --- /dev/null +++ b/.dagger/frontend/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/.dagger/frontend/.gitignore b/.dagger/frontend/.gitignore new file mode 100644 index 0000000..7ebabcc --- /dev/null +++ b/.dagger/frontend/.gitignore @@ -0,0 +1,4 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder +/internal/telemetry diff --git a/.dagger/frontend/dagger.json b/.dagger/frontend/dagger.json new file mode 100644 index 0000000..e2fa916 --- /dev/null +++ b/.dagger/frontend/dagger.json @@ -0,0 +1,7 @@ +{ + "name": "frontend", + "engineVersion": "v0.18.9", + "sdk": { + "source": "go" + } +} diff --git a/.dagger/frontend/go.mod b/.dagger/frontend/go.mod new file mode 100644 index 0000000..05a38f6 --- /dev/null +++ b/.dagger/frontend/go.mod @@ -0,0 +1,50 @@ +module dagger/frontend + +go 1.24.3 + +require ( + github.com/99designs/gqlgen v0.17.73 + github.com/Khan/genqlient v0.8.1 + github.com/vektah/gqlparser/v2 v2.5.27 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 + go.opentelemetry.io/otel/log v0.8.0 + go.opentelemetry.io/otel/metric v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/sdk/log v0.8.0 + go.opentelemetry.io/otel/sdk/metric v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 + go.opentelemetry.io/proto/otlp v1.3.1 + golang.org/x/sync v0.14.0 + google.golang.org/grpc v1.72.1 +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/protobuf v1.36.6 // indirect +) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 diff --git a/.dagger/frontend/go.sum b/.dagger/frontend/go.sum new file mode 100644 index 0000000..73a2898 --- /dev/null +++ b/.dagger/frontend/go.sum @@ -0,0 +1,85 @@ +github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gEsrg= +github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= +github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.dagger/frontend/main.go b/.dagger/frontend/main.go new file mode 100644 index 0000000..6e160df --- /dev/null +++ b/.dagger/frontend/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "dagger/frontend/internal/dagger" +) + +type Frontend struct { + Source *dagger.Directory +} + +func New(source *dagger.Directory) *Frontend { + return &Frontend{ + Source: source, + } +} + +// Run the unit tests +func (f *Frontend) UnitTest(ctx context.Context) (string, error) { + return dag.Container().Build(f.Source, dagger.ContainerBuildOpts{ + Target: "build", + }).WithEnvVariable("CI", "true").WithExec([]string{"npm", "test"}).Stdout(ctx) +} + +// BuildImage builds the Docker image using the existing Dockerfile +func (f *Frontend) Build( + ctx context.Context, +) *dagger.Container { + return dag.Container().Build(f.Source) +} + +// Stateless checker +func (f *Frontend) CheckDirectory( + ctx context.Context, + // Directory to run checks on + source *dagger.Directory, +) (string, error) { + f.Source = source + return f.UnitTest(ctx) +} diff --git a/.dagger/main.go b/.dagger/main.go index 966cb79..7e3d34c 100644 --- a/.dagger/main.go +++ b/.dagger/main.go @@ -8,32 +8,36 @@ import ( ) type PipelineDagger struct { - Source *dagger.Directory // Shared Dagger directory - RepoFE string - RepoInfra string + // +private + Source *dagger.Directory + // +private + RepoFE string + // +private + Frontend *dagger.Frontend } // New initializes the pipeline with a Dagger client func New( // +optional - // +defaultPath="/website" + // +defaultPath="/" // +ignore=[".git", "**/node_modules"] source *dagger.Directory, // +optional // +default="github.com/OnlyLight/dev-ui" repoFE string, - // +optional - // +default="github.com/OnlyLight/dev-infra" - repoInfra string, ) *PipelineDagger { - return &PipelineDagger{Source: source, RepoFE: repoFE, RepoInfra: repoInfra} + return &PipelineDagger{ + Source: source, + RepoFE: repoFE, + Frontend: dag.Frontend(source.Directory("/website")), + } } // BuildImage builds the Docker image using the existing Dockerfile func (p *PipelineDagger) Build( ctx context.Context, ) *dagger.Container { - return dag.Container().Build(p.Source) + return p.Frontend.Build() } // Run and debug the unit tests @@ -48,8 +52,9 @@ func (p *PipelineDagger) Check( // The model to use to debug debug tests // +optional model string, -) (string, error) { - return p.DebugUT(ctx, model) +) error { + err := p.DebugUTIssues(ctx, githubToken, commit, model) + return fmt.Errorf("Unit tests failed, attempting to debug %v", err) } // Publish the built image to a container registry diff --git a/.dagger/sugesstions.go b/.dagger/sugesstions.go index 6f11ce2..622692f 100644 --- a/.dagger/sugesstions.go +++ b/.dagger/sugesstions.go @@ -11,20 +11,18 @@ type CodeSuggestion struct { File string Line int Suggestion []string - DiffHunk string // Add diff hunk for GitHub API } -func parseDiff(diffText string) ([]CodeSuggestion, error) { +func parseDiff(diffText string) []CodeSuggestion { var suggestions []CodeSuggestion var currentFile string var currentLine int var newCode []string - var currentHunk []string - var inHunk bool + removalReached := false - // Regular expressions + // Regular expressions for file detection and line number parsing fileRegex := regexp.MustCompile(`^\+\+\+ b/(.+)`) - lineRegex := regexp.MustCompile(`^@@ -(\d+),\d+ \+(\d+),\d+ @@`) + lineRegex := regexp.MustCompile(`^@@ .* \+(\d+),?`) scanner := bufio.NewScanner(strings.NewReader(diffText)) for scanner.Scan() { @@ -32,69 +30,56 @@ func parseDiff(diffText string) ([]CodeSuggestion, error) { // Detect file name if matches := fileRegex.FindStringSubmatch(line); matches != nil { - if inHunk && len(newCode) > 0 { - suggestions = append(suggestions, CodeSuggestion{ - File: currentFile, - Line: currentLine, - Suggestion: newCode, - DiffHunk: strings.Join(currentHunk, "\n"), - }) - newCode = []string{} - currentHunk = []string{} - } currentFile = matches[1] - inHunk = false continue } - // Detect hunk + // Detect modified line number in the new file if matches := lineRegex.FindStringSubmatch(line); matches != nil { - if inHunk && len(newCode) > 0 { + currentLine = atoi(matches[1]) - 1 // Convert to 0-based index for tracking + newCode = []string{} // Reset new code buffer + removalReached = false + continue + } + + // Extract new code (ignoring metadata lines) + if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") { + newCode = append(newCode, line[1:]) // Remove `+` + continue + } + + if !removalReached { + currentLine++ // Track line modifications + } + + // If a removed line (`-`) appears after `+` lines, store the suggestion + if strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "---") { + if len(newCode) > 0 && currentFile != "" { suggestions = append(suggestions, CodeSuggestion{ File: currentFile, Line: currentLine, Suggestion: newCode, - DiffHunk: strings.Join(currentHunk, "\n"), }) - newCode = []string{} - currentHunk = []string{} + newCode = []string{} // Reset new code buffer } - currentLine = atoi(matches[2]) // 1-based line number - inHunk = true - currentHunk = append(currentHunk, line) - continue + removalReached = true } - if inHunk { - currentHunk = append(currentHunk, line) - if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") { - newCode = append(newCode, strings.TrimPrefix(line, "+")) - currentLine++ - } else if strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "---") { - // Do not increment for removed lines - } else if strings.HasPrefix(line, " ") { - currentLine++ // Context lines - } - } } - // Add final suggestion - if inHunk && len(newCode) > 0 && currentFile != "" { + // If there's a pending multi-line suggestion, add it + if len(newCode) > 0 && currentFile != "" { suggestions = append(suggestions, CodeSuggestion{ File: currentFile, Line: currentLine, Suggestion: newCode, - DiffHunk: strings.Join(currentHunk, "\n"), }) } - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("failed to scan diff: %w", err) - } - - return suggestions, nil + return suggestions } +// Helper function to convert string to int safely func atoi(s string) int { var i int fmt.Sscanf(s, "%d", &i) diff --git a/.dagger/workspace/main.go b/.dagger/workspace/main.go index 24a5dfb..dc6928c 100644 --- a/.dagger/workspace/main.go +++ b/.dagger/workspace/main.go @@ -5,17 +5,27 @@ import ( "dagger/workspace/internal/dagger" ) +// Interface for something that can be checked +type Checkable interface { + dagger.DaggerObject + CheckDirectory(ctx context.Context, source *dagger.Directory) (string, error) +} + type Workspace struct { Work *dagger.Directory // +private Start *dagger.Directory + // +private + Checker Checkable } func New( // The source directory source *dagger.Directory, + // Checker to use for testing + checker Checkable, ) *Workspace { - return &Workspace{Work: source, Start: source} + return &Workspace{Work: source, Start: source, Checker: checker} } // Read a file in the Workspace @@ -61,10 +71,7 @@ func (w *Workspace) Diff(ctx context.Context) (string, error) { Stdout(ctx) } -// Run the unit tests -func (w *Workspace) Test(ctx context.Context) (string, error) { - return dag.Container().Build(w.Work, dagger.ContainerBuildOpts{ - // Dockerfile: "./website/Dockerfile", - Target: "build", - }).WithEnvVariable("CI", "true").WithExec([]string{"npm", "test"}).Stdout(ctx) +// Run the tests in the workspace +func (w *Workspace) Check(ctx context.Context) (string, error) { + return w.Checker.CheckDirectory(ctx, w.Work) } diff --git a/.github/workflows/3_wf_test.yaml b/.github/workflows/3_wf_test.yaml index 9922d63..f4b0820 100644 --- a/.github/workflows/3_wf_test.yaml +++ b/.github/workflows/3_wf_test.yaml @@ -1,17 +1,14 @@ name: wf_dagger_test on: - push: - branches: ["**", "!main"] + pull_request: + branches: + - main jobs: build: name: Debug Unit Tests runs-on: ubuntu-22.04 - permissions: - contents: write - issues: read - pull-requests: write timeout-minutes: 20 steps: - name: Check out code @@ -23,8 +20,12 @@ jobs: - name: Run dagger Debug run: | - dagger call check --github-token GITHUB_TOKEN --model gemini-2.0-flash + if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) + fi + + dagger call check --github-token GH_TOKEN --commit $GITHUB_SHA env: DAGGER_CLOUD_TOKEN: ${{ secrets.DAGGER_CLOUD_TOKEN }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }} + GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }} diff --git a/dagger.json b/dagger.json index c70fd23..45feaf3 100644 --- a/dagger.json +++ b/dagger.json @@ -5,6 +5,10 @@ "source": "go" }, "dependencies": [ + { + "name": "frontend", + "source": ".dagger/frontend" + }, { "name": "github-issue", "source": "github.com/kpenfound/dag/github-issue@github-issue/v2.0.0", diff --git a/website/src/App.test.js b/website/src/App.test.js index ff21530..b0b88e4 100644 --- a/website/src/App.test.js +++ b/website/src/App.test.js @@ -4,7 +4,7 @@ import App from "./App"; test("renders Crawler System Testing header", () => { render(); const headerElement = screen.getByRole("heading", { - name: /Crawler System Testing 2/i, + name: /Crawler System Testing 1/i, }); expect(headerElement).toBeInTheDocument(); }); From 4c9412e696cab7e3e4f4a4106d0975e9583d6fc6 Mon Sep 17 00:00:00 2001 From: duytran Date: Fri, 6 Jun 2025 19:05:09 +0700 Subject: [PATCH 2/2] Fix diffhunk send comment suggestion --- .dagger/debugger.go | 13 +++++++++++-- .dagger/prompts/fix_test.md | 9 +++------ .dagger/sugesstions.go | 6 +++++- .github/workflows/3_wf_test.yaml | 10 +++++++++- website/src/App.test.js | 2 +- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.dagger/debugger.go b/.dagger/debugger.go index b114e3d..33a66f7 100644 --- a/.dagger/debugger.go +++ b/.dagger/debugger.go @@ -80,7 +80,15 @@ func (p *PipelineDagger) DebugUTIssues( fmt.Printf("Number of suggestions: %d\n", len(codeSuggestions)) // For each suggestion, comment on PR - for _, suggestion := range codeSuggestions { + for i, suggestion := range codeSuggestions { + fmt.Printf("Suggestion %d: File=%s, Line=%d\n", i+1, suggestion.File, suggestion.Line) + if suggestion.File == "" { + return fmt.Errorf("invalid suggestion: empty file path") + } + if suggestion.Line < 1 { + return fmt.Errorf("invalid suggestion: line %d in %s", suggestion.Line, suggestion.File) + } + markupSuggestion := "```suggestion\n" + strings.Join(suggestion.Suggestion, "\n") + "\n```" err := gh.WritePullRequestCodeComment( ctx, @@ -90,7 +98,8 @@ func (p *PipelineDagger) DebugUTIssues( markupSuggestion, suggestion.File, "RIGHT", - suggestion.Line) + suggestion.Line, + ) if err != nil { return err } diff --git a/.dagger/prompts/fix_test.md b/.dagger/prompts/fix_test.md index 519120c..d461702 100644 --- a/.dagger/prompts/fix_test.md +++ b/.dagger/prompts/fix_test.md @@ -1,11 +1,8 @@ -- Please clean cache before run - You are a programmer - You have access to a workspace with the code and the tests - The workspace has tools to let you read and write the code as well as run the tests -- In your workspace, run test and verify the tests. -- If the test fail, you fix the issues so that the tests pass go to next step. If the test pass, notify the workspace with successful tests -- Before writing code, analyze the Workspace to understand the project. -- Be sure to always write your changes to the workspace (Do not make unneccessary changes) +- In your workspace, run and verify the tests. +- Be sure to always write your changes to the workspace - Always run test after writing changes to the workspace - If the test fails, run reset so you can try again with a clean workspace -- Do not done until the unit test pass. \ No newline at end of file +- Do not done until the unit test pass. diff --git a/.dagger/sugesstions.go b/.dagger/sugesstions.go index 622692f..3b10f8e 100644 --- a/.dagger/sugesstions.go +++ b/.dagger/sugesstions.go @@ -30,7 +30,11 @@ func parseDiff(diffText string) []CodeSuggestion { // Detect file name if matches := fileRegex.FindStringSubmatch(line); matches != nil { - currentFile = matches[1] + path := matches[1] + if !strings.HasPrefix(path, "website/") { + path = "website/" + path + } + currentFile = path continue } diff --git a/.github/workflows/3_wf_test.yaml b/.github/workflows/3_wf_test.yaml index f4b0820..59ee0ac 100644 --- a/.github/workflows/3_wf_test.yaml +++ b/.github/workflows/3_wf_test.yaml @@ -11,6 +11,13 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 20 steps: + - name: Generate an agent token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + - name: Check out code uses: actions/checkout@v4 with: @@ -20,6 +27,7 @@ jobs: - name: Run dagger Debug run: | + # the GITHUB_SHA is the last merge commit on the GITHUB_REF branch. This GITHUB_SHA does not exist in any current branch in repo. if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) fi @@ -28,4 +36,4 @@ jobs: env: DAGGER_CLOUD_TOKEN: ${{ secrets.DAGGER_CLOUD_TOKEN }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - GH_TOKEN: ${{ secrets.PERSONAL_TOKEN }} + GH_TOKEN: ${{ steps.generate-token.outputs.token }} diff --git a/website/src/App.test.js b/website/src/App.test.js index b0b88e4..f68608e 100644 --- a/website/src/App.test.js +++ b/website/src/App.test.js @@ -4,7 +4,7 @@ import App from "./App"; test("renders Crawler System Testing header", () => { render(); const headerElement = screen.getByRole("heading", { - name: /Crawler System Testing 1/i, + name: /Crawler System Testing 3/i, }); expect(headerElement).toBeInTheDocument(); });