Compare commits
8 Commits
406aa46a5a
...
master
Author | SHA1 | Date | |
---|---|---|---|
3a7ff257b0 | |||
d68562b631 | |||
0c57663f5f | |||
48cf3bb342 | |||
eda0f87fe3 | |||
367f9f9e70 | |||
4d6459fae5 | |||
84087ffa62 |
25
go.mod
25
go.mod
@ -1,14 +1,14 @@
|
||||
module forge.cadoles.com/wpetit/clearcase
|
||||
|
||||
go 1.23.4
|
||||
go 1.24.1
|
||||
|
||||
toolchain go1.23.6
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.20.0
|
||||
github.com/a-h/templ v0.3.833
|
||||
github.com/bornholm/genai v0.0.0-20250227201654-4c93b20ee628
|
||||
github.com/caarlos0/env/v11 v11.2.2
|
||||
github.com/bornholm/genai v0.0.0-20250813093009-06e6c9e04c1c
|
||||
github.com/caarlos0/env/v11 v11.3.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.7
|
||||
github.com/google/go-github/v69 v69.2.0
|
||||
github.com/gorilla/sessions v1.1.1
|
||||
@ -34,23 +34,26 @@ require (
|
||||
github.com/gorilla/mux v1.6.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/openai/openai-go v0.1.0-alpha.59 // indirect
|
||||
github.com/revrost/go-openrouter v0.0.0-20250128091643-3d014d57014d // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/openai/openai-go v0.1.0-beta.10 // indirect
|
||||
github.com/revrost/go-openrouter v0.2.1 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/oauth2 v0.17.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
honnef.co/go/tools v0.3.1 // indirect
|
||||
)
|
||||
|
65
go.sum
65
go.sum
@ -12,12 +12,11 @@ github.com/RealAlexandreAI/json-repair v0.0.14 h1:4kTqotVonDVTio5n2yweRUELVcNe2x
|
||||
github.com/RealAlexandreAI/json-repair v0.0.14/go.mod h1:GKJi5borR78O8c7HCVbgqjhoiVibZ6hJldxbc6dGrAI=
|
||||
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
|
||||
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
|
||||
github.com/bornholm/genai v0.0.0-20250222092500-1076426da67c h1:bI0ebsgO1/7Jx6+ZQdDF/I6tTZxyB5hODYz7x/XxwK8=
|
||||
github.com/bornholm/genai v0.0.0-20250222092500-1076426da67c/go.mod h1:MnuvwSsBEWv/joeK/WgUyfZfOLcLTpd81NJdWoRpRfI=
|
||||
github.com/bornholm/genai v0.0.0-20250227201654-4c93b20ee628 h1:YsrF9+NUdwYPLfpJUUfD0h/yH0jvpnaMxtM/sPsFsPg=
|
||||
github.com/bornholm/genai v0.0.0-20250227201654-4c93b20ee628/go.mod h1:kgZb50LiE3cLjyGdUzNwDtpxL5QRllZIsWT2Ub24fIM=
|
||||
github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
|
||||
github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
|
||||
github.com/bornholm/genai v0.0.0-20250813093009-06e6c9e04c1c h1:+w9HM44ytNG1F7yoMaPBlLHs+W8EX8uONfJGfRDUyvE=
|
||||
github.com/bornholm/genai v0.0.0-20250813093009-06e6c9e04c1c/go.mod h1:J4c2UCGjTSGqtvpc43er2Eo0AR6UxDbiJh8oI42A7sw=
|
||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
@ -26,14 +25,15 @@ github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW
|
||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
|
||||
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
@ -52,23 +52,33 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8=
|
||||
github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/num30/go-cache v1.0.0 h1:uvXf3X4xEm+AB8b2MtMouFpgevR/sFCOOxXKaofzReg=
|
||||
github.com/num30/go-cache v1.0.0/go.mod h1:YxYzvWbR26wa2xojzZ8W3c+NSSwBzYtrorHSkeRaJWw=
|
||||
github.com/openai/openai-go v0.1.0-alpha.59 h1:T3IYwKSCezfIlL9Oi+CGvU03fq0RoH33775S78Ti48Y=
|
||||
github.com/openai/openai-go v0.1.0-alpha.59/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
|
||||
github.com/openai/openai-go v0.1.0-beta.10 h1:CknhGXe8aXQMRuqg255PFnWzgRY9nEryMxoNIBBM9tU=
|
||||
github.com/openai/openai-go v0.1.0-beta.10/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/revrost/go-openrouter v0.0.0-20250128091643-3d014d57014d h1:3PCl9WWy1S3YpCzlXrDoBJqPJ59IsiqKSf8d3tHDYn0=
|
||||
github.com/revrost/go-openrouter v0.0.0-20250128091643-3d014d57014d/go.mod h1:UIrJIZBygNz1DygZPImp56zCjv5IJNNkdp2hNUgn9H4=
|
||||
github.com/revrost/go-openrouter v0.2.1 h1:4BMQ6pgYeEJq9pLl7pFbwnBabmqgUa35hGRnVHqjpA4=
|
||||
github.com/revrost/go-openrouter v0.2.1/go.mod h1:ZH/UdpnDEdMmJwq8tbSTX1S5I07ee8KMlEYN4jmegU0=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/slog-http v1.4.4 h1:NuENLy39Lk6b7wfj9cG9R5C/JLZR4t6pb9cwlyroybI=
|
||||
github.com/samber/slog-http v1.4.4/go.mod h1:PAcQQrYFo5KM7Qbk50gNNwKEAMGCyfsw6GN5dI0iv9g=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@ -87,8 +97,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@ -98,8 +108,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
|
||||
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -112,12 +122,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@ -133,8 +146,8 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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=
|
||||
honnef.co/go/tools v0.3.1 h1:1kJlrWJLkaGXgcaeosRXViwviqjI7nkBvU2+sZW0AYc=
|
||||
|
@ -16,6 +16,128 @@ type Forge struct {
|
||||
client *gitea.Client
|
||||
}
|
||||
|
||||
// UpdatePullRequest implements port.Forge.
|
||||
func (f *Forge) UpdatePullRequest(ctx context.Context, rawProjectID string, rawPullRequestID string, title string, body string) (string, error) {
|
||||
project, err := f.getProject(rawProjectID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequestID, err := strconv.ParseInt(rawPullRequestID, 10, 64)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not parse pull request id '%v'", rawPullRequestID)
|
||||
}
|
||||
|
||||
pr, _, err := f.client.EditPullRequest(project.Owner.UserName, project.Name, pullRequestID, gitea.EditPullRequestOption{
|
||||
Title: title,
|
||||
Body: body,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return pr.HTMLURL, nil
|
||||
}
|
||||
|
||||
// GetPullRequestTemplate implements port.Forge.
|
||||
func (f *Forge) GetPullRequestTemplate(ctx context.Context, rawProjectID string) (string, error) {
|
||||
data, err := f.GetFile(ctx, rawProjectID, ".gitea/pull_request_template.md")
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// GetPullRequests implements port.Forge.
|
||||
func (f *Forge) GetPullRequests(ctx context.Context, rawProjectID string, pullRequestIDs ...string) ([]*model.PullRequest, error) {
|
||||
project, err := f.getProject(rawProjectID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests := make([]*model.PullRequest, 0)
|
||||
for _, rawID := range pullRequestIDs {
|
||||
id, err := strconv.ParseInt(rawID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse pull request id '%v'", rawID)
|
||||
}
|
||||
|
||||
pr, _, err := f.client.GetPullRequest(project.Owner.UserName, project.Name, id)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests = append(pullRequests, &model.PullRequest{
|
||||
ID: strconv.FormatInt(pr.ID, 10),
|
||||
Title: pr.Title,
|
||||
Body: pr.Body,
|
||||
})
|
||||
}
|
||||
|
||||
return pullRequests, nil
|
||||
}
|
||||
|
||||
// GetPullRequestDiff implements port.Forge.
|
||||
func (f *Forge) GetPullRequestDiff(ctx context.Context, rawProjectID string, rawPullRequestID string) (string, error) {
|
||||
project, err := f.getProject(rawProjectID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequestID, err := strconv.ParseInt(rawPullRequestID, 10, 64)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not parse pull request id '%v'", rawPullRequestID)
|
||||
}
|
||||
|
||||
diff, _, err := f.client.GetPullRequestDiff(project.Owner.UserName, project.Name, pullRequestID, gitea.PullRequestDiffOptions{
|
||||
Binary: false,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return string(diff), nil
|
||||
}
|
||||
|
||||
// ListOpenedPullRequests implements port.Forge.
|
||||
func (f *Forge) ListOpenedPullRequests(ctx context.Context, rawProjectID string) ([]*model.PullRequest, error) {
|
||||
project, err := f.getProject(rawProjectID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests := make([]*model.PullRequest, 0)
|
||||
|
||||
page := 1
|
||||
for {
|
||||
repoPullRequests, res, err := f.client.ListRepoPullRequests(project.Owner.UserName, project.Name, gitea.ListPullRequestsOptions{
|
||||
State: gitea.StateOpen,
|
||||
ListOptions: gitea.ListOptions{
|
||||
Page: page,
|
||||
PageSize: 100,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, pr := range repoPullRequests {
|
||||
pullRequests = append(pullRequests, &model.PullRequest{
|
||||
ID: strconv.FormatInt(pr.Index, 10),
|
||||
Title: pr.Title,
|
||||
Body: pr.Body,
|
||||
})
|
||||
}
|
||||
|
||||
if res.NextPage == 0 {
|
||||
return pullRequests, nil
|
||||
}
|
||||
|
||||
page = res.NextPage
|
||||
}
|
||||
}
|
||||
|
||||
// GetFile implements port.Forge.
|
||||
func (f *Forge) GetFile(ctx context.Context, rawProjectID string, path string) ([]byte, error) {
|
||||
project, err := f.getProject(rawProjectID)
|
||||
@ -132,8 +254,8 @@ func (f *Forge) GetIssues(ctx context.Context, rawProjectID string, issueIDs ...
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
// GetAllProjects implements port.Forge.
|
||||
func (f *Forge) GetAllProjects(ctx context.Context) ([]*model.Project, error) {
|
||||
// ListProjects implements port.Forge.
|
||||
func (f *Forge) ListProjects(ctx context.Context) ([]*model.Project, error) {
|
||||
projects := make([]*model.Project, 0)
|
||||
|
||||
page := 1
|
||||
@ -148,18 +270,18 @@ func (f *Forge) GetAllProjects(ctx context.Context) ([]*model.Project, error) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
page = res.NextPage
|
||||
|
||||
if res.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, r := range repositories {
|
||||
projects = append(projects, &model.Project{
|
||||
ID: strconv.FormatInt(r.ID, 10),
|
||||
Name: r.FullName,
|
||||
})
|
||||
}
|
||||
|
||||
if res.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
page = res.NextPage
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
|
@ -18,6 +18,168 @@ type Forge struct {
|
||||
client *github.Client
|
||||
}
|
||||
|
||||
// UpdatePullRequest implements port.Forge.
|
||||
func (f *Forge) UpdatePullRequest(ctx context.Context, rawProjectID string, rawPullRequestID string, title string, body string) (string, error) {
|
||||
repo, err := f.getRepository(ctx, rawProjectID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequestID, err := strconv.ParseInt(rawPullRequestID, 10, 64)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not parse pull request id '%v'", rawPullRequestID)
|
||||
}
|
||||
|
||||
pr, res, err := f.client.PullRequests.Edit(ctx, *repo.Owner.Login, *repo.Name, int(pullRequestID), &github.PullRequest{
|
||||
Title: &title,
|
||||
Body: &body,
|
||||
})
|
||||
if err != nil {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not edit pull request", slog.Any("error", errors.WithStack(err)))
|
||||
return "", errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return *pr.HTMLURL, nil
|
||||
}
|
||||
|
||||
// GetPullRequestTemplate implements port.Forge.
|
||||
func (f *Forge) GetPullRequestTemplate(ctx context.Context, projectID string) (string, error) {
|
||||
data, err := f.GetFile(ctx, projectID, ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md")
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// GetPullRequestDiff implements port.Forge.
|
||||
func (f *Forge) GetPullRequests(ctx context.Context, projectID string, pullRequestIDs ...string) ([]*model.PullRequest, error) {
|
||||
repo, err := f.getRepository(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests := make([]*model.PullRequest, 0)
|
||||
for _, rawID := range pullRequestIDs {
|
||||
id, err := strconv.ParseInt(rawID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse pull request id '%v'", rawID)
|
||||
}
|
||||
|
||||
pr, res, err := f.client.PullRequests.Get(ctx, *repo.Owner.Login, *repo.Name, int(id))
|
||||
if err != nil {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve repository pull requests", slog.Any("error", errors.WithStack(err)))
|
||||
return nil, errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
var body string
|
||||
if pr.Body != nil {
|
||||
body = *pr.Body
|
||||
}
|
||||
|
||||
var title string
|
||||
if pr.Title != nil {
|
||||
title = *pr.Title
|
||||
}
|
||||
|
||||
pullRequests = append(pullRequests, &model.PullRequest{
|
||||
ID: strconv.FormatInt(*pr.ID, 10),
|
||||
Title: title,
|
||||
Body: body,
|
||||
})
|
||||
}
|
||||
|
||||
return pullRequests, nil
|
||||
}
|
||||
|
||||
// GetPullRequestDiff implements port.Forge.
|
||||
func (f *Forge) GetPullRequestDiff(ctx context.Context, projectID string, rawPullRequestID string) (string, error) {
|
||||
repo, err := f.getRepository(ctx, projectID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequestID, err := strconv.ParseInt(rawPullRequestID, 10, 64)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not parse pull request id '%v'", pullRequestID)
|
||||
}
|
||||
|
||||
diff, res, err := f.client.PullRequests.GetRaw(ctx, *repo.Owner.Login, *repo.Name, int(pullRequestID), github.RawOptions{
|
||||
Type: github.Diff,
|
||||
})
|
||||
if err != nil {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve pull request diff", slog.Any("error", errors.WithStack(err)))
|
||||
return "", errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
// ListOpenedPullRequests implements port.Forge.
|
||||
func (f *Forge) ListOpenedPullRequests(ctx context.Context, projectID string) ([]*model.PullRequest, error) {
|
||||
repo, err := f.getRepository(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests := make([]*model.PullRequest, 0)
|
||||
|
||||
page := 1
|
||||
for {
|
||||
repoPullRequests, res, err := f.client.PullRequests.List(ctx, *repo.Owner.Login, *repo.Name, &github.PullRequestListOptions{
|
||||
State: "opened",
|
||||
ListOptions: github.ListOptions{
|
||||
Page: page,
|
||||
PerPage: 100,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve repository pull requests", slog.Any("error", errors.WithStack(err)))
|
||||
return nil, errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, pr := range repoPullRequests {
|
||||
var body string
|
||||
if pr.Body != nil {
|
||||
body = *pr.Body
|
||||
}
|
||||
|
||||
var title string
|
||||
if pr.Title != nil {
|
||||
title = *pr.Title
|
||||
}
|
||||
|
||||
pullRequests = append(pullRequests, &model.PullRequest{
|
||||
ID: strconv.FormatInt(*pr.ID, 10),
|
||||
Title: title,
|
||||
Body: body,
|
||||
})
|
||||
}
|
||||
|
||||
if res.NextPage == 0 {
|
||||
return pullRequests, nil
|
||||
}
|
||||
|
||||
page = res.NextPage
|
||||
}
|
||||
}
|
||||
|
||||
// CreateIssue implements port.Forge.
|
||||
func (f *Forge) CreateIssue(ctx context.Context, projectID string, title string, body string) (string, error) {
|
||||
repo, err := f.getRepository(ctx, projectID)
|
||||
@ -29,16 +191,20 @@ func (f *Forge) CreateIssue(ctx context.Context, projectID string, title string,
|
||||
Title: &title,
|
||||
Body: &body,
|
||||
})
|
||||
if res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized {
|
||||
if err != nil {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not create issue", slog.Any("error", errors.WithStack(err)))
|
||||
return "", errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return *issue.HTMLURL, nil
|
||||
}
|
||||
|
||||
// GetAllProjects implements port.Forge.
|
||||
func (f *Forge) GetAllProjects(ctx context.Context) ([]*model.Project, error) {
|
||||
// ListProjects implements port.Forge.
|
||||
func (f *Forge) ListProjects(ctx context.Context) ([]*model.Project, error) {
|
||||
projects := make([]*model.Project, 0)
|
||||
|
||||
page := 1
|
||||
@ -51,7 +217,7 @@ func (f *Forge) GetAllProjects(ctx context.Context) ([]*model.Project, error) {
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve user repositories", slog.Any("error", errors.WithStack(err)))
|
||||
return nil, errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
@ -94,7 +260,7 @@ func (f *Forge) GetFile(ctx context.Context, rawProjectID string, path string) (
|
||||
|
||||
fileContent, _, res, err := f.client.Repositories.GetContents(ctx, *repo.Owner.Login, *repo.Name, path, nil)
|
||||
if err != nil {
|
||||
if res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve file content", slog.Any("error", errors.WithStack(err)))
|
||||
return nil, errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
@ -185,7 +351,7 @@ func (f *Forge) GetProjectLanguages(ctx context.Context, rawProjectID string) ([
|
||||
|
||||
mappedLanguages, res, err := f.client.Repositories.ListLanguages(ctx, *repo.Owner.Login, *repo.Name)
|
||||
if err != nil {
|
||||
if res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve repository languages", slog.Any("error", errors.WithStack(err)))
|
||||
return nil, errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
@ -210,7 +376,7 @@ func (f *Forge) getRepository(ctx context.Context, rawProjectID string) (*github
|
||||
|
||||
repo, res, err := f.client.Repositories.GetByID(ctx, projectID)
|
||||
if err != nil {
|
||||
if res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized {
|
||||
if res != nil && (res.StatusCode == http.StatusForbidden || res.StatusCode == http.StatusUnauthorized) {
|
||||
slog.ErrorContext(ctx, "could not retrieve repository", slog.Any("error", errors.WithStack(err)))
|
||||
return nil, errors.WithStack(service.ErrForgeNotAvailable)
|
||||
}
|
||||
|
7
internal/core/model/pull_request.go
Normal file
7
internal/core/model/pull_request.go
Normal file
@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
type PullRequest struct {
|
||||
ID string
|
||||
Title string
|
||||
Body string
|
||||
}
|
@ -9,10 +9,16 @@ import (
|
||||
|
||||
var (
|
||||
ErrFileNotFound = errors.New("file not found")
|
||||
ErrPullRequestNotFound = errors.New("pull request not found")
|
||||
)
|
||||
|
||||
type Forge interface {
|
||||
GetAllProjects(ctx context.Context) ([]*model.Project, error)
|
||||
ListProjects(ctx context.Context) ([]*model.Project, error)
|
||||
ListOpenedPullRequests(ctx context.Context, projectID string) ([]*model.PullRequest, error)
|
||||
GetPullRequestDiff(ctx context.Context, projectID string, pullRequestID string) (string, error)
|
||||
GetPullRequestTemplate(ctx context.Context, projectID string) (string, error)
|
||||
GetPullRequests(ctx context.Context, projectID string, pullRequestIDs ...string) ([]*model.PullRequest, error)
|
||||
UpdatePullRequest(ctx context.Context, projectID string, pullRequestID string, title string, body string) (string, error)
|
||||
CreateIssue(ctx context.Context, projectID string, title string, body string) (string, error)
|
||||
GetIssues(ctx context.Context, projectID string, issueIDs ...string) ([]*model.Issue, error)
|
||||
GetIssueTemplate(ctx context.Context, projectID string) (string, error)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
@ -15,6 +16,7 @@ import (
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/port"
|
||||
"github.com/bornholm/genai/llm"
|
||||
"github.com/bornholm/genai/llm/prompt"
|
||||
"github.com/num30/go-cache"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -23,28 +25,51 @@ var (
|
||||
ErrForgeNotAvailable = errors.New("forge not available")
|
||||
)
|
||||
|
||||
//go:embed issue_system_prompt.gotmpl
|
||||
//go:embed prompts/issue_system_prompt.gotmpl
|
||||
var issueSystemPromptRawTemplate string
|
||||
|
||||
//go:embed issue_user_prompt.gotmpl
|
||||
//go:embed prompts/issue_user_prompt.gotmpl
|
||||
var issueUserPromptRawTemplate string
|
||||
|
||||
//go:embed issue_default_template.txt
|
||||
//go:embed prompts/issue_default_template.txt
|
||||
var issueDefaultTemplate string
|
||||
|
||||
//go:embed prompts/pull_request_system_prompt.gotmpl
|
||||
var pullRequestSystemPromptRawTemplate string
|
||||
|
||||
//go:embed prompts/pull_request_user_prompt.gotmpl
|
||||
var pullRequestUserPromptRawTemplate string
|
||||
|
||||
//go:embed prompts/pull_request_default_template.txt
|
||||
var pullRequestDefaultTemplate string
|
||||
|
||||
type ForgeFactory interface {
|
||||
Match(user *model.User) bool
|
||||
Create(ctx context.Context, user *model.User) (port.Forge, error)
|
||||
}
|
||||
|
||||
type IssueManager struct {
|
||||
type ForgeManager struct {
|
||||
forgeFactories []ForgeFactory
|
||||
llmClient llm.Client
|
||||
projectCache *cache.Cache[[]*model.Project]
|
||||
forgeCache *cache.Cache[port.Forge]
|
||||
}
|
||||
|
||||
func (m *IssueManager) CreateIssue(ctx context.Context, user *model.User, projectID string, title string, body string) (string, error) {
|
||||
func (m *ForgeManager) UpdatePullRequest(ctx context.Context, user *model.User, projectID string, pullRequestID string, title string, body string) (string, error) {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequestURL, err := forge.UpdatePullRequest(ctx, projectID, pullRequestID, title, body)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return pullRequestURL, nil
|
||||
}
|
||||
|
||||
func (m *ForgeManager) CreateIssue(ctx context.Context, user *model.User, projectID string, title string, body string) (string, error) {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
@ -58,7 +83,21 @@ func (m *IssueManager) CreateIssue(ctx context.Context, user *model.User, projec
|
||||
return issueURL, nil
|
||||
}
|
||||
|
||||
func (m *IssueManager) GetUserProjects(ctx context.Context, user *model.User) ([]*model.Project, error) {
|
||||
func (m *ForgeManager) GetUserProjectOpenedPullRequests(ctx context.Context, user *model.User, projectID string) ([]*model.PullRequest, error) {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests, err := forge.ListOpenedPullRequests(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return pullRequests, nil
|
||||
}
|
||||
|
||||
func (m *ForgeManager) GetUserProjects(ctx context.Context, user *model.User) ([]*model.Project, error) {
|
||||
cacheKey := fmt.Sprintf("%s/%s", user.Provider, user.ID)
|
||||
|
||||
projects, exists := m.projectCache.Get(cacheKey)
|
||||
@ -68,7 +107,7 @@ func (m *IssueManager) GetUserProjects(ctx context.Context, user *model.User) ([
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
refreshedProjects, err := forge.GetAllProjects(ctx)
|
||||
refreshedProjects, err := forge.ListProjects(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@ -85,12 +124,161 @@ func (m *IssueManager) GetUserProjects(ctx context.Context, user *model.User) ([
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
func (m *ForgeManager) GetUserProjectPullRequest(ctx context.Context, user *model.User, projectID string, pullRequestID string) (*model.PullRequest, error) {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pullRequests, err := forge.GetPullRequests(ctx, projectID, pullRequestID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(pullRequests) == 0 {
|
||||
return nil, errors.WithStack(port.ErrPullRequestNotFound)
|
||||
}
|
||||
|
||||
return pullRequests[0], nil
|
||||
}
|
||||
|
||||
type GeneratIssueOptions struct {
|
||||
IssueSummary string
|
||||
IssueTemplate string
|
||||
}
|
||||
|
||||
func (m *IssueManager) GenerateIssue(ctx context.Context, user *model.User, projectID string, issueSummary string, overwrittenIssueTemplate string) (string, string, string, error) {
|
||||
func (m *ForgeManager) GeneratePullRequest(ctx context.Context, user *model.User, projectID string, pullRequestID string, summary string, overwrittenTemplate string) (string, string, string, error) {
|
||||
systemPrompt, err := m.getPullRequestSystemPrompt(ctx, user, projectID, pullRequestID, overwrittenTemplate)
|
||||
if err != nil {
|
||||
return "", "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
slog.DebugContext(ctx, "using system prompt", slog.String("systemPrompt", systemPrompt))
|
||||
|
||||
userPrompt, err := m.getPullRequestUserPrompt(ctx, user, projectID, pullRequestID, summary)
|
||||
if err != nil {
|
||||
return "", "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
slog.DebugContext(ctx, "using user prompt", slog.String("userPrompt", userPrompt))
|
||||
|
||||
messages := []llm.Message{
|
||||
llm.NewMessage(llm.RoleSystem, systemPrompt),
|
||||
llm.NewMessage(llm.RoleUser, userPrompt),
|
||||
}
|
||||
|
||||
res, err := m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
|
||||
if err != nil {
|
||||
return "", "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
body := res.Message().Content()
|
||||
|
||||
body = strings.TrimSpace(body)
|
||||
body = strings.TrimPrefix(body, "```markdown")
|
||||
body = strings.TrimSuffix(body, "```")
|
||||
|
||||
messages = append(messages, res.Message())
|
||||
messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this pull request. Keep it descriptive, simple and short. Do not write anything else. Use the same language."))
|
||||
|
||||
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
|
||||
if err != nil {
|
||||
return "", "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
title := toTitle(res.Message().Content())
|
||||
|
||||
messages = append(messages, res.Message())
|
||||
messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings. Use the same language."))
|
||||
|
||||
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
|
||||
if err != nil {
|
||||
return "", "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
tips := res.Message().Content()
|
||||
|
||||
return title, body, tips, nil
|
||||
}
|
||||
|
||||
func (m *ForgeManager) getPullRequestSystemPrompt(ctx context.Context, user *model.User, projectID string, pullRequestID string, prTemplate string) (string, error) {
|
||||
if prTemplate == "" {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
repoPullRequestTemplate, err := forge.GetPullRequestTemplate(ctx, projectID)
|
||||
if err != nil && !errors.Is(err, port.ErrFileNotFound) {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
prTemplate = repoPullRequestTemplate
|
||||
}
|
||||
|
||||
if prTemplate == "" {
|
||||
prTemplate = pullRequestDefaultTemplate
|
||||
}
|
||||
|
||||
systemPrompt, err := prompt.Template(pullRequestSystemPromptRawTemplate, struct {
|
||||
PullRequestTemplate string
|
||||
}{
|
||||
PullRequestTemplate: prTemplate,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return systemPrompt, nil
|
||||
}
|
||||
|
||||
func (m *ForgeManager) getPullRequestUserPrompt(ctx context.Context, user *model.User, projectID string, pullRequestID string, summary string) (string, error) {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
project, err := forge.GetProject(ctx, projectID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
projectLanguages, err := forge.GetProjectLanguages(ctx, projectID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
diff, err := forge.GetPullRequestDiff(ctx, projectID, pullRequestID)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
resources, err := m.extractResources(ctx, forge, projectID, summary)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
userPrompt, err := prompt.Template(pullRequestUserPromptRawTemplate, struct {
|
||||
Summary string
|
||||
Project *model.Project
|
||||
ProjectLanguages []string
|
||||
Resources []*model.Resource
|
||||
Diff string
|
||||
}{
|
||||
Summary: summary,
|
||||
Project: project,
|
||||
ProjectLanguages: projectLanguages,
|
||||
Resources: resources,
|
||||
Diff: diff,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return userPrompt, nil
|
||||
}
|
||||
|
||||
func (m *ForgeManager) GenerateIssue(ctx context.Context, user *model.User, projectID string, issueSummary string, overwrittenIssueTemplate string) (string, string, string, error) {
|
||||
systemPrompt, err := m.getIssueSystemPrompt(ctx, user, projectID, overwrittenIssueTemplate)
|
||||
if err != nil {
|
||||
return "", "", "", errors.WithStack(err)
|
||||
@ -117,8 +305,12 @@ func (m *IssueManager) GenerateIssue(ctx context.Context, user *model.User, proj
|
||||
|
||||
body := res.Message().Content()
|
||||
|
||||
body = strings.TrimSpace(body)
|
||||
body = strings.TrimPrefix(body, "```markdown")
|
||||
body = strings.TrimSuffix(body, "```")
|
||||
|
||||
messages = append(messages, res.Message())
|
||||
messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this issue. Keep it descriptive, simple and short. Do not write anything else."))
|
||||
messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this issue. Keep it descriptive, simple and short. Do not write anything else. Use the same language."))
|
||||
|
||||
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
|
||||
if err != nil {
|
||||
@ -128,7 +320,7 @@ func (m *IssueManager) GenerateIssue(ctx context.Context, user *model.User, proj
|
||||
title := toTitle(res.Message().Content())
|
||||
|
||||
messages = append(messages, res.Message())
|
||||
messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings."))
|
||||
messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings. Use the same language."))
|
||||
|
||||
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
|
||||
if err != nil {
|
||||
@ -140,7 +332,7 @@ func (m *IssueManager) GenerateIssue(ctx context.Context, user *model.User, proj
|
||||
return title, body, tips, nil
|
||||
}
|
||||
|
||||
func (m *IssueManager) getIssueSystemPrompt(ctx context.Context, user *model.User, projectID string, issueTemplate string) (string, error) {
|
||||
func (m *ForgeManager) getIssueSystemPrompt(ctx context.Context, user *model.User, projectID string, issueTemplate string) (string, error) {
|
||||
if issueTemplate == "" {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
@ -159,7 +351,7 @@ func (m *IssueManager) getIssueSystemPrompt(ctx context.Context, user *model.Use
|
||||
issueTemplate = issueDefaultTemplate
|
||||
}
|
||||
|
||||
systemPrompt, err := llm.PromptTemplate(issueSystemPromptRawTemplate, struct {
|
||||
systemPrompt, err := prompt.Template(issueSystemPromptRawTemplate, struct {
|
||||
IssueTemplate string
|
||||
}{
|
||||
IssueTemplate: issueTemplate,
|
||||
@ -171,7 +363,7 @@ func (m *IssueManager) getIssueSystemPrompt(ctx context.Context, user *model.Use
|
||||
return systemPrompt, nil
|
||||
}
|
||||
|
||||
func (m *IssueManager) getIssueUserPrompt(ctx context.Context, user *model.User, projectID string, issueSummary string) (string, error) {
|
||||
func (m *ForgeManager) getIssueUserPrompt(ctx context.Context, user *model.User, projectID string, issueSummary string) (string, error) {
|
||||
forge, err := m.getUserForge(ctx, user)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
@ -192,7 +384,7 @@ func (m *IssueManager) getIssueUserPrompt(ctx context.Context, user *model.User,
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
userPrompt, err := llm.PromptTemplate(issueUserPromptRawTemplate, struct {
|
||||
userPrompt, err := prompt.Template(issueUserPromptRawTemplate, struct {
|
||||
Summary string
|
||||
Project *model.Project
|
||||
ProjectLanguages []string
|
||||
@ -210,7 +402,7 @@ func (m *IssueManager) getIssueUserPrompt(ctx context.Context, user *model.User,
|
||||
return userPrompt, nil
|
||||
}
|
||||
|
||||
func (m *IssueManager) getUserForge(ctx context.Context, user *model.User) (port.Forge, error) {
|
||||
func (m *ForgeManager) getUserForge(ctx context.Context, user *model.User) (port.Forge, error) {
|
||||
forge, exists := m.forgeCache.Get(user.AccessToken)
|
||||
if exists {
|
||||
return forge, nil
|
||||
@ -235,7 +427,7 @@ func (m *IssueManager) getUserForge(ctx context.Context, user *model.User) (port
|
||||
return nil, errors.New("no forge matching user found")
|
||||
}
|
||||
|
||||
func (m *IssueManager) extractResources(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
|
||||
func (m *ForgeManager) extractResources(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
|
||||
resources := make([]*model.Resource, 0)
|
||||
|
||||
issues, err := m.extractIssues(ctx, forge, projectID, issueSummary)
|
||||
@ -257,7 +449,7 @@ func (m *IssueManager) extractResources(ctx context.Context, forge port.Forge, p
|
||||
|
||||
var issueRefRegExp = regexp.MustCompile(`#([0-9]+)`)
|
||||
|
||||
func (m *IssueManager) extractIssues(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
|
||||
func (m *ForgeManager) extractIssues(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
|
||||
issueRefMatches := issueRefRegExp.FindAllStringSubmatch(issueSummary, -1)
|
||||
|
||||
issueIDs := make([]string, 0, len(issueRefMatches))
|
||||
@ -290,7 +482,7 @@ func (m *IssueManager) extractIssues(ctx context.Context, forge port.Forge, proj
|
||||
|
||||
var fileRefRegExp = regexp.MustCompile(`(?i)(?:\/[^\/]+)+\/?[^\s]+(?:\.[^\s]+)+|[^\s]+(?:\.[^\s]+)+`)
|
||||
|
||||
func (m *IssueManager) extractFiles(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
|
||||
func (m *ForgeManager) extractFiles(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
|
||||
fileRefMatches := fileRefRegExp.FindAllStringSubmatch(issueSummary, -1)
|
||||
|
||||
paths := make([]string, 0, len(fileRefMatches))
|
||||
@ -301,6 +493,10 @@ func (m *IssueManager) extractFiles(ctx context.Context, forge port.Forge, proje
|
||||
resources := make([]*model.Resource, 0)
|
||||
|
||||
for _, p := range paths {
|
||||
if isURL(p) {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := forge.GetFile(ctx, projectID, p)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "could not retrieve file", slog.Any("error", errors.WithStack(err)), slog.String("path", p))
|
||||
@ -318,8 +514,8 @@ func (m *IssueManager) extractFiles(ctx context.Context, forge port.Forge, proje
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func NewIssueManager(llmClient llm.Client, forgeFactories ...ForgeFactory) *IssueManager {
|
||||
return &IssueManager{
|
||||
func NewForgeManager(llmClient llm.Client, forgeFactories ...ForgeFactory) *ForgeManager {
|
||||
return &ForgeManager{
|
||||
llmClient: llmClient,
|
||||
forgeFactories: forgeFactories,
|
||||
projectCache: cache.New[[]*model.Project](time.Minute*5, (time.Minute*5)/2),
|
||||
@ -331,3 +527,8 @@ func toTitle(str string) string {
|
||||
str = strings.ToLower(str)
|
||||
return strings.ToUpper(string(str[0])) + str[1:]
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
_, err := url.ParseRequestURI(str)
|
||||
return err == nil
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
## Description
|
||||
|
||||
La description du besoin, écrite de manière claire et concise.
|
||||
|
||||
## Détails d'implémentation
|
||||
|
||||
Liste des actions envisagées pour réaliser une évolution ou un correctif pour remplir le besoin.
|
||||
|
||||
## Tests d'acceptance
|
||||
|
||||
Liste des critères d'évaluation pour la validation finale des développements réalisés dans le cadre de la demande.
|
7
internal/core/service/prompts/issue_default_template.txt
Normal file
7
internal/core/service/prompts/issue_default_template.txt
Normal file
@ -0,0 +1,7 @@
|
||||
## Description
|
||||
|
||||
La description du besoin, écrite de manière claire et concise.
|
||||
|
||||
## Tests d'acceptation
|
||||
|
||||
Liste des critères d'évaluation pour la validation finale des développements réalisés dans le cadre de la demande.
|
@ -1,4 +1,6 @@
|
||||
Write a formatted issue/request based on the following informations:
|
||||
Write a formatted issue/request based on the following informations.
|
||||
|
||||
Do not write any output other than the issue.
|
||||
|
||||
## Request Summary
|
||||
|
@ -0,0 +1,7 @@
|
||||
## Description
|
||||
|
||||
Description générale des objectifs liés à cette pull request.
|
||||
|
||||
## Changements
|
||||
|
||||
Liste des changements apportés.
|
@ -0,0 +1,29 @@
|
||||
You are an expert software developer with extensive experience in writing clear and comprehensive pull requests for software forges. Your task is to create well-structured pull request based on the provided contextual information, following a predefined Markdown layout.
|
||||
|
||||
**Instructions:**
|
||||
|
||||
1. **Pull Request Description**:
|
||||
- Provide a detailed description of the pull request, including:
|
||||
- Background information.
|
||||
- Steps to reproduce the issue.
|
||||
- Expected behavior.
|
||||
- Actual behavior.
|
||||
- Any relevant error messages or logs.
|
||||
- Always use the user prompt summary main language.
|
||||
|
||||
2. **Additional Context**:
|
||||
- Include any other relevant information that might help in understanding or resolving the pull request.
|
||||
|
||||
3. Keep resources references when available.
|
||||
|
||||
4. Do not include the raw diff in your response.
|
||||
|
||||
5. Do not include general informations about the project. Keep the description focused on the current changes.
|
||||
|
||||
6. Let think step by step.
|
||||
|
||||
**Markdown Layout:**
|
||||
|
||||
```markdown
|
||||
{{ .PullRequestTemplate }}
|
||||
```
|
@ -0,0 +1,40 @@
|
||||
Write a formatted pull request based on the following informations.
|
||||
|
||||
Do not write any output other than the pull request.
|
||||
|
||||
## Pull Request Summary
|
||||
|
||||
{{ .Summary }}
|
||||
|
||||
## General Project Informations
|
||||
|
||||
- Name: {{ .Project.Name }}
|
||||
- Description: {{ .Project.Description }}
|
||||
- Languages:
|
||||
{{- range .ProjectLanguages }}
|
||||
- {{ . -}}
|
||||
{{ end }}
|
||||
|
||||
## Changelog
|
||||
|
||||
```
|
||||
{{ .Diff }}
|
||||
```
|
||||
|
||||
{{ if gt (len .Resources) 0 }}
|
||||
## Additional Resources
|
||||
|
||||
{{ range .Resources }}
|
||||
### {{ .Name }}
|
||||
|
||||
**Type:**
|
||||
|
||||
{{ .Type }}
|
||||
|
||||
**Content:**
|
||||
|
||||
```{{ .Syntax }}
|
||||
{{ .Content }}
|
||||
```
|
||||
{{end}}
|
||||
{{end}}
|
@ -101,7 +101,7 @@ func (f *Form) Field(name string) *Field {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
panic(errors.Errorf("no form field named '%s'", name))
|
||||
}
|
||||
|
||||
func (f *Form) Error(name string) (ValidationError, bool) {
|
||||
|
51
internal/http/handler/webui/common/component/app_page.templ
Normal file
51
internal/http/handler/webui/common/component/app_page.templ
Normal file
@ -0,0 +1,51 @@
|
||||
package component
|
||||
|
||||
type AppPageOptions struct {
|
||||
PageOptions []PageOptionFunc
|
||||
}
|
||||
|
||||
type AppPageOptionFunc func(opts *AppPageOptions)
|
||||
|
||||
func WithPageOptions(funcs ...PageOptionFunc) AppPageOptionFunc {
|
||||
return func(opts *AppPageOptions) {
|
||||
opts.PageOptions = funcs
|
||||
}
|
||||
}
|
||||
|
||||
func NewAppPageOptions(funcs ...AppPageOptionFunc) *AppPageOptions {
|
||||
opts := &AppPageOptions{
|
||||
PageOptions: make([]PageOptionFunc, 0),
|
||||
}
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
templ AppPage(funcs ...AppPageOptionFunc) {
|
||||
{{ opts := NewAppPageOptions(funcs...) }}
|
||||
@Page(opts.PageOptions...) {
|
||||
<div class="container is-fluid">
|
||||
<section class="section">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<h1 class="title is-size-1"><span class="has-text-info-light">Clear</span><span>Case</span></h1>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="buttons is-right level-item">
|
||||
<a class="button is-medium" href={ BaseURL(ctx, WithPath("/auth/logout")) }><span class="icon"><i class="fa fa-sign-out-alt"></i></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs is-large is-fullwidth is-boxed">
|
||||
<ul>
|
||||
<li class={ templ.KV("is-active", MatchPath(ctx, "/issue/")) }><a href={ BaseURL(ctx, WithPath("/issue")) }><span class="icon"><i class="fa fa-plus"></i></span>Nouvelle demande</a></li>
|
||||
// <li><a href={ BaseURL(ctx, WithPath("/issue")) }><span class="icon"><i class="fa fa-edit"></i></span>Éditer une demande</a></li>
|
||||
<li class={ templ.KV("is-active", MatchPath(ctx, "/pullrequest/")) }><a href={ CurrentURL(ctx, WithPath("/pullrequest")) }><span class="icon"><i class="fas fa-code-branch"></i></span>Éditer une PR</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{ children... }
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
160
internal/http/handler/webui/common/component/app_page_templ.go
Normal file
160
internal/http/handler/webui/common/component/app_page_templ.go
Normal file
@ -0,0 +1,160 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.819
|
||||
package component
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
type AppPageOptions struct {
|
||||
PageOptions []PageOptionFunc
|
||||
}
|
||||
|
||||
type AppPageOptionFunc func(opts *AppPageOptions)
|
||||
|
||||
func WithPageOptions(funcs ...PageOptionFunc) AppPageOptionFunc {
|
||||
return func(opts *AppPageOptions) {
|
||||
opts.PageOptions = funcs
|
||||
}
|
||||
}
|
||||
|
||||
func NewAppPageOptions(funcs ...AppPageOptionFunc) *AppPageOptions {
|
||||
opts := &AppPageOptions{
|
||||
PageOptions: make([]PageOptionFunc, 0),
|
||||
}
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func AppPage(funcs ...AppPageOptionFunc) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
opts := NewAppPageOptions(funcs...)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container is-fluid\"><section class=\"section\"><div class=\"level is-mobile\"><div class=\"level-left\"><h1 class=\"title is-size-1\"><span class=\"has-text-info-light\">Clear</span><span>Case</span></h1></div><div class=\"level-right\"><div class=\"buttons is-right level-item\"><a class=\"button is-medium\" href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 templ.SafeURL = BaseURL(ctx, WithPath("/auth/logout"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><span class=\"icon\"><i class=\"fa fa-sign-out-alt\"></i></span></a></div></div></div><div class=\"tabs is-large is-fullwidth is-boxed\"><ul>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 = []any{templ.KV("is-active", MatchPath(ctx, "/issue/"))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<li class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/app_page.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 templ.SafeURL = BaseURL(ctx, WithPath("/issue"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><span class=\"icon\"><i class=\"fa fa-plus\"></i></span>Nouvelle demande</a></li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 = []any{templ.KV("is-active", MatchPath(ctx, "/pullrequest/"))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<li class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/app_page.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 templ.SafeURL = CurrentURL(ctx, WithPath("/pullrequest"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><span class=\"icon\"><i class=\"fas fa-code-branch\"></i></span>Éditer une PR</a></li></ul></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</section></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = Page(opts.PageOptions...).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -40,7 +40,7 @@ func NewIssueSummaryForm() *form.Form {
|
||||
form.NewField(
|
||||
"project",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champs ne doit pas être vide."),
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"summary",
|
||||
@ -85,19 +85,9 @@ func NewIssueForm() *form.Form {
|
||||
}
|
||||
|
||||
templ IssuePage(vmodel IssuePageVModel) {
|
||||
@common.Page(common.WithTitle("Nouvelle demande")) {
|
||||
<div class="container is-fluid">
|
||||
<section class="section">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<h1 class="title is-size-1"><span class="has-text-info-light">Clear</span><span>Case</span></h1>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="buttons is-right level-item">
|
||||
<a class="button is-medium" href={ common.BaseURL(ctx, common.WithPath("/auth/logout")) }><span class="icon"><i class="fa fa-sign-out-alt"></i></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Nouvelle demande"),
|
||||
)) {
|
||||
if vmodel.IssueURL != "" {
|
||||
<article class="message is-primary">
|
||||
<div class="message-header">
|
||||
@ -143,9 +133,6 @@ templ IssuePage(vmodel IssuePageVModel) {
|
||||
<summary class="is-clickable">Paramètres avancés</summary>
|
||||
@common.FormTextarea(
|
||||
vmodel.SummaryForm, "issue-template", "template", "Surcharger le modèle de demande",
|
||||
common.WithTextareaAttrs(
|
||||
"hx-on:change", "onIssueTemplateChange(event)",
|
||||
),
|
||||
)
|
||||
</details>
|
||||
<div class="buttons is-right">
|
||||
@ -163,6 +150,17 @@ templ IssuePage(vmodel IssuePageVModel) {
|
||||
<form action={ common.CurrentURL(ctx) } method="post" hx-disabled-elt="textarea, input, select, button" hx-indicator="#generation-progress">
|
||||
@common.FormField(vmodel.IssueForm, "issue-title", "title", "Titre")
|
||||
@common.FormTextarea(vmodel.IssueForm, "issue-body", "body", "Corps")
|
||||
if value, ok := vmodel.IssueForm.Field("body").Get("value"); ok && value != nil {
|
||||
<article class="message is-warning mt-5">
|
||||
<div class="message-header">
|
||||
<p><span class="icon"><i class="fa fa-exclamation-triangle"></i></span>Avertissement</p>
|
||||
</div>
|
||||
<div class="message-body content">
|
||||
<p><b>ClearCase n'est qu'un outil.</b> Il est peu probable que le ticket généré soit parfait dès le premier essai, et il nécessitera sans doute des ajustements de votre part.</p>
|
||||
<p>Assurez-vous de bien relire votre ticket et de supprimer les éléments superflus ou hors sujet avant de le créer.</p>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
<div class="buttons is-right">
|
||||
<button type="submit" class="button is-info is-large">
|
||||
<span class="icon">
|
||||
@ -192,8 +190,6 @@ templ IssuePage(vmodel IssuePageVModel) {
|
||||
</article>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
function onCloseMessage(closeElement) {
|
||||
closeElement.closest('.message').style.display = 'none';
|
||||
@ -205,12 +201,6 @@ templ IssuePage(vmodel IssuePageVModel) {
|
||||
sessionStorage.setItem(`summary-${projectId}`, summary);
|
||||
}
|
||||
|
||||
function onIssueTemplateChange(evt) {
|
||||
const issueTemplate = evt.currentTarget.value;
|
||||
const projectId = document.getElementById("issue-project").value;
|
||||
localStorage.setItem(`issue-template-${projectId}`, issueTemplate);
|
||||
}
|
||||
|
||||
function savePreferredProject() {
|
||||
const projectId = document.getElementById("issue-project").value;
|
||||
localStorage.setItem(`preferred-project`, projectId);
|
||||
@ -238,22 +228,9 @@ templ IssuePage(vmodel IssuePageVModel) {
|
||||
summaryTextarea.value = savedSummary;
|
||||
}
|
||||
|
||||
function restoreIssueTemplate() {
|
||||
const issueTemplateTextarea = document.getElementById("issue-template");
|
||||
if (!issueTemplateTextarea) return;
|
||||
const issueTemplate = issueTemplateTextarea.value;
|
||||
if (issueTemplate !== "") return;
|
||||
const projectId = document.getElementById("issue-project").value;
|
||||
if (!projectId) return;
|
||||
const savedIssueTemplate = localStorage.getItem(`issue-template-${projectId}`);
|
||||
if (!savedIssueTemplate) return;
|
||||
issueTemplateTextarea.value = savedIssueTemplate;
|
||||
}
|
||||
|
||||
htmx.onLoad(function(){
|
||||
restoreLastSummary();
|
||||
restorePreferredProject();
|
||||
restoreIssueTemplate();
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ func NewIssueSummaryForm() *form.Form {
|
||||
form.NewField(
|
||||
"project",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champs ne doit pas être vide."),
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"summary",
|
||||
@ -125,43 +125,30 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container is-fluid\"><section class=\"section\"><div class=\"level is-mobile\"><div class=\"level-left\"><h1 class=\"title is-size-1\"><span class=\"has-text-info-light\">Clear</span><span>Case</span></h1></div><div class=\"level-right\"><div class=\"buttons is-right level-item\"><a class=\"button is-medium\" href=\"")
|
||||
if vmodel.IssueURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<article class=\"message is-primary\"><div class=\"message-header\"><p>Demande créée !</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\">Votre demande a été créée et est disponible à l'adresse suivante: <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 templ.SafeURL = common.BaseURL(ctx, common.WithPath("/auth/logout"))
|
||||
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(vmodel.IssueURL)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><span class=\"icon\"><i class=\"fa fa-sign-out-alt\"></i></span></a></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" target=\"_blank\"><code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if vmodel.IssueURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<article class=\"message is-primary\"><div class=\"message-header\"><p>Demande créée !</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\">Votre demande a été créée et est disponible à l'adresse suivante: <a href=\"")
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.IssueURL)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/issue/component/issue_page.templ`, Line: 99, Col: 87}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(vmodel.IssueURL)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" target=\"_blank\"><code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.IssueURL)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/issue/component/issue_page.templ`, Line: 109, Col: 89}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</code></a>.</div></article><script type=\"text/javascript\">\n\t\t\t\t\t\tfunction clearSummary(projectId) {\n\t\t\t\t\t\t\tsessionStorage.removeItem(`summary-${projectId}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfunction openIssue(issueUrl) {\n\t\t\t\t\t\t\twindow.open(issueUrl, \"_blank\");\n\t\t\t\t\t\t}\n\t\t\t\t\t</script> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</code></a>.</div></article><script type=\"text/javascript\">\n\t\t\t\t\t\tfunction clearSummary(projectId) {\n\t\t\t\t\t\t\tsessionStorage.removeItem(`summary-${projectId}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfunction openIssue(issueUrl) {\n\t\t\t\t\t\t\twindow.open(issueUrl, \"_blank\");\n\t\t\t\t\t\t}\n\t\t\t\t\t</script> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -169,7 +156,7 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -178,16 +165,16 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"columns\"><div class=\"column is-4\"><form id=\"summary-form\" action=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " <div class=\"columns\"><div class=\"column is-4\"><form id=\"summary-form\" action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
|
||||
var templ_7745c5c3_Var5 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" method=\"put\" hx-disabled-elt=\"textarea, input, select, button\" hx-on:htmx:before-send=\"savePreferredProject()\" hx-indicator=\"#generation-progress\"><h2 class=\"title is-size-3\">Résumé de la demande</h2>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" method=\"put\" hx-disabled-elt=\"textarea, input, select, button\" hx-on:htmx:before-send=\"savePreferredProject()\" hx-indicator=\"#generation-progress\"><h2 class=\"title is-size-3\">Résumé de la demande</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -212,29 +199,26 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<details class=\"my-3\"><summary class=\"is-clickable\">Paramètres avancés</summary>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<details class=\"my-3\"><summary class=\"is-clickable\">Paramètres avancés</summary>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormTextarea(
|
||||
vmodel.SummaryForm, "issue-template", "template", "Surcharger le modèle de demande",
|
||||
common.WithTextareaAttrs(
|
||||
"hx-on:change", "onIssueTemplateChange(event)",
|
||||
),
|
||||
).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</details><div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-robot\"></i></span> <span>Générer</span></button></div></form></div><div class=\"column\"><h2 class=\"title is-size-3\">Votre demande</h2><form action=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</details><div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-robot\"></i></span> <span>Générer</span></button></div></form></div><div class=\"column\"><h2 class=\"title is-size-3\">Votre demande</h2><form action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
|
||||
var templ_7745c5c3_Var6 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" method=\"post\" hx-disabled-elt=\"textarea, input, select, button\" hx-indicator=\"#generation-progress\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" method=\"post\" hx-disabled-elt=\"textarea, input, select, button\" hx-indicator=\"#generation-progress\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -246,14 +230,20 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-rocket\"></i></span> <span>Créer</span></button></div></form></div></div><progress id=\"generation-progress\" class=\"htmx-indicator progress\"></progress> ")
|
||||
if value, ok := vmodel.IssueForm.Field("body").Get("value"); ok && value != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<article class=\"message is-warning mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-exclamation-triangle\"></i></span>Avertissement</p></div><div class=\"message-body content\"><p><b>ClearCase n'est qu'un outil.</b> Il est peu probable que le ticket généré soit parfait dès le premier essai, et il nécessitera sans doute des ajustements de votre part.</p><p>Assurez-vous de bien relire votre ticket et de supprimer les éléments superflus ou hors sujet avant de le créer.</p></div></article>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-rocket\"></i></span> <span>Créer</span></button></div></form></div></div><progress id=\"generation-progress\" class=\"htmx-indicator progress\"></progress> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if vmodel.IssueTips != "" {
|
||||
html := markdownToHTML(ctx, vmodel.IssueTips)
|
||||
if html != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<article class=\"message is-info mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-lightbulb\"></i></span>Questionnements</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\"><div class=\"content\"><p>Utilisez ces quelques questions pour réfléchir aux éléments d'informations nécessaire à la bonne rédaction de votre demande:</p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<article class=\"message is-info mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-lightbulb\"></i></span>Questionnements</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\"><div class=\"content\"><p>Utilisez ces quelques questions pour réfléchir aux éléments d'informations nécessaire à la bonne rédaction de votre demande:</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -261,19 +251,21 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div></article>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></div></article>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</section></div><script type=\"text/javascript\">\n\t\tfunction onCloseMessage(closeElement) {\n\t\t\tcloseElement.closest('.message').style.display = 'none';\n\t\t}\n\n\t\tfunction onSummaryChange(evt) {\n\t\t\tconst summary = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tsessionStorage.setItem(`summary-${projectId}`, summary);\n\t\t}\n\n\t\tfunction onIssueTemplateChange(evt) {\n\t\t\tconst issueTemplate = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tlocalStorage.setItem(`issue-template-${projectId}`, issueTemplate);\n\t\t}\n\n\t\tfunction savePreferredProject() {\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tlocalStorage.setItem(`preferred-project`, projectId);\n\t\t}\n\n\t\tfunction restorePreferredProject() {\n\t\t\tconst preferredProject = localStorage.getItem(`preferred-project`);\n\t\t\tif (!preferredProject) return;\n\t\t\tconst projectElement = document.getElementById(\"issue-project\");\n\t\t\tif (!projectElement) return;\n\t\t\tif (preferredProject === projectElement.value) return;\n\t\t\tprojectElement.value = preferredProject;\n\t\t\tprojectElement.dispatchEvent(new Event('change'));\n\t\t}\n\n\t\tfunction restoreLastSummary() {\n\t\t\tconst summaryTextarea = document.getElementById(\"issue-summary\");\n\t\t\tif (!summaryTextarea) return;\n const summary = summaryTextarea.value;\n\t\t\tif (summary !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedSummary = sessionStorage.getItem(`summary-${projectId}`);\n\t\t\tif (!savedSummary) return;\n\t\t\tsummaryTextarea.value = savedSummary;\n\t\t}\n\n\t\tfunction restoreIssueTemplate() {\n\t\t\tconst issueTemplateTextarea = document.getElementById(\"issue-template\");\n\t\t\tif (!issueTemplateTextarea) return;\n const issueTemplate = issueTemplateTextarea.value;\n\t\t\tif (issueTemplate !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedIssueTemplate = localStorage.getItem(`issue-template-${projectId}`);\n\t\t\tif (!savedIssueTemplate) return;\n\t\t\tissueTemplateTextarea.value = savedIssueTemplate;\n\t\t}\n\n\t\thtmx.onLoad(function(){\n\t\t\trestoreLastSummary();\n\t\t\trestorePreferredProject();\n\t\t\trestoreIssueTemplate();\n })\n\t\t</script>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " <script type=\"text/javascript\">\n\t\tfunction onCloseMessage(closeElement) {\n\t\t\tcloseElement.closest('.message').style.display = 'none';\n\t\t}\n\n\t\tfunction onSummaryChange(evt) {\n\t\t\tconst summary = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tsessionStorage.setItem(`summary-${projectId}`, summary);\n\t\t}\n\n\t\tfunction savePreferredProject() {\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tlocalStorage.setItem(`preferred-project`, projectId);\n\t\t}\n\n\t\tfunction restorePreferredProject() {\n\t\t\tconst preferredProject = localStorage.getItem(`preferred-project`);\n\t\t\tif (!preferredProject) return;\n\t\t\tconst projectElement = document.getElementById(\"issue-project\");\n\t\t\tif (!projectElement) return;\n\t\t\tif (preferredProject === projectElement.value) return;\n\t\t\tprojectElement.value = preferredProject;\n\t\t\tprojectElement.dispatchEvent(new Event('change'));\n\t\t}\n\n\t\tfunction restoreLastSummary() {\n\t\t\tconst summaryTextarea = document.getElementById(\"issue-summary\");\n\t\t\tif (!summaryTextarea) return;\n const summary = summaryTextarea.value;\n\t\t\tif (summary !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedSummary = sessionStorage.getItem(`summary-${projectId}`);\n\t\t\tif (!savedSummary) return;\n\t\t\tsummaryTextarea.value = savedSummary;\n\t\t}\n\n\t\thtmx.onLoad(function(){\n\t\t\trestoreLastSummary();\n\t\t\trestorePreferredProject();\n })\n\t\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.Page(common.WithTitle("Nouvelle demande")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Nouvelle demande"),
|
||||
)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
type Handler struct {
|
||||
mux *http.ServeMux
|
||||
issueManager *service.IssueManager
|
||||
forge *service.ForgeManager
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
@ -16,10 +16,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewHandler(issueManager *service.IssueManager) *Handler {
|
||||
func NewHandler(forge *service.ForgeManager) *Handler {
|
||||
h := &Handler{
|
||||
mux: http.NewServeMux(),
|
||||
issueManager: issueManager,
|
||||
forge: forge,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("GET /", h.getIssuePage)
|
||||
|
@ -47,7 +47,7 @@ func (h *Handler) fillIssuePageVModel(r *http.Request) (*component.IssuePageVMod
|
||||
func (h *Handler) fillIssuePageProjects(ctx context.Context, vmodel *component.IssuePageVModel, r *http.Request) error {
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
projects, err := h.issueManager.GetUserProjects(ctx, user)
|
||||
projects, err := h.forge.GetUserProjects(ctx, user)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@ -114,7 +114,7 @@ func (h *Handler) handleIssueSummaryForm(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
issueTemplate = strings.TrimSpace(issueTemplate)
|
||||
|
||||
issueTitle, issueBody, issueTips, err := h.issueManager.GenerateIssue(ctx, httpCtx.User(ctx), projectID, issueSummary, issueTemplate)
|
||||
issueTitle, issueBody, issueTips, err := h.forge.GenerateIssue(ctx, httpCtx.User(ctx), projectID, issueSummary, issueTemplate)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
@ -168,7 +168,7 @@ func (h *Handler) handleIssueForm(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
issueURL, err := h.issueManager.CreateIssue(ctx, user, projectID, title, body)
|
||||
issueURL, err := h.forge.CreateIssue(ctx, user, projectID, title, body)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
|
@ -0,0 +1,313 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/form"
|
||||
common "forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common/component"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/yuin/goldmark"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PullRequestPageVModel struct {
|
||||
PullRequestURL string
|
||||
SummaryForm *form.Form
|
||||
PullRequestForm *form.Form
|
||||
PullRequestTips string
|
||||
Projects []*model.Project
|
||||
PullRequests []*model.PullRequest
|
||||
SelectedProjectID string
|
||||
SelectedPullRequestID string
|
||||
}
|
||||
|
||||
const summaryPlaceholder = `
|
||||
Décrivez rapidement les modifications apportées par la PR, ClearCase utilisera le modèle de PR présent dans le dépôt (ou un modèle par défaut) afin de générer une version mise en forme et complétée.
|
||||
|
||||
Afin de fournir plus d'information de contexte au LLM, vous pouvez faire référence à d'autres tickets du dépôt via un ou plusieurs '#<pr_id>' et/ou des chemins vers des fichiers présents dans celui ci.
|
||||
`
|
||||
|
||||
const bodyPlaceholder = `
|
||||
Une fois votre PR générée, vous pourrez l'éditer puis la créer directement en cliquant sur le bouton 'Mettre à jour' ci-dessous.
|
||||
`
|
||||
|
||||
const prTemplatePlaceholder = `
|
||||
Vous pouvez surcharger le modèle de PR fourni par le projet en remplissant ce champ.
|
||||
`
|
||||
|
||||
func NewPullRequestSummaryForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"project",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"pullrequest",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"summary",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": strings.TrimSpace(summaryPlaceholder),
|
||||
},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"template",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": strings.TrimSpace(prTemplatePlaceholder),
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewPullRequestForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"title",
|
||||
form.Attrs{
|
||||
"type": "text",
|
||||
"placeholder": "Écrivez le résumé de votre demande et cliquez sur 'Générer' pour remplir automatiquement ces champs.",
|
||||
},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"body",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": strings.TrimSpace(bodyPlaceholder),
|
||||
},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
templ PullRequestPage(vmodel PullRequestPageVModel) {
|
||||
@common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Éditer une PR"),
|
||||
)) {
|
||||
if vmodel.PullRequestURL != "" {
|
||||
<article class="message is-primary">
|
||||
<div class="message-header">
|
||||
<p>Pull Request modifiée !</p>
|
||||
<button class="delete" aria-label="delete" hx-on:click="onCloseMessage(this)"></button>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
Votre PR a été mise à jour et est disponible à l'adresse suivante:
|
||||
<a href={ templ.SafeURL(vmodel.PullRequestURL) } target="_blank"><code>{ vmodel.PullRequestURL }</code></a>.
|
||||
</div>
|
||||
</article>
|
||||
<script type="text/javascript">
|
||||
function clearSummary(projectId) {
|
||||
sessionStorage.removeItem(`pr-summary-${projectId}`)
|
||||
}
|
||||
function openPR(prUrl) {
|
||||
window.open(prUrl, "_blank");
|
||||
}
|
||||
</script>
|
||||
@templ.JSFuncCall("clearSummary", vmodel.SelectedProjectID)
|
||||
@templ.JSFuncCall("openPR", vmodel.PullRequestURL)
|
||||
}
|
||||
<div class="columns">
|
||||
<div class="column is-4">
|
||||
<form id="summary-form" action={ common.CurrentURL(ctx) } method="put" hx-disabled-elt="textarea, input, select, button" hx-on:htmx:before-send="savePreferred()" hx-indicator="#generation-progress">
|
||||
<h2 class="title is-size-3">Résumé de la PR</h2>
|
||||
@common.FormSelect(
|
||||
vmodel.SummaryForm, "pr-project", "project", "Projet",
|
||||
common.WithOptions(projectsToOptions(vmodel.Projects)...),
|
||||
common.WithAttrs(
|
||||
"hx-get", string(common.CurrentURL(ctx, common.WithoutValues("project", "*"))),
|
||||
"hx-target", "body",
|
||||
"hx-push-url", "true",
|
||||
),
|
||||
)
|
||||
@common.FormSelect(
|
||||
vmodel.SummaryForm, "pr-pullrequest", "pullrequest", "PR",
|
||||
common.WithOptions(pullRequestsToOptions(vmodel.PullRequests)...),
|
||||
common.WithAttrs(
|
||||
"hx-get", string(common.CurrentURL(ctx, common.WithoutValues("pullrequest", "*"))),
|
||||
"hx-target", "body",
|
||||
"hx-push-url", "true",
|
||||
),
|
||||
)
|
||||
@common.FormTextarea(
|
||||
vmodel.SummaryForm, "pr-summary", "summary", "Résumé",
|
||||
common.WithTextareaAttrs(
|
||||
"hx-on:change", "onSummaryChange(event)",
|
||||
),
|
||||
)
|
||||
<details class="my-3">
|
||||
<summary class="is-clickable">Paramètres avancés</summary>
|
||||
@common.FormTextarea(
|
||||
vmodel.SummaryForm, "pr-template", "template", "Surcharger le modèle de demande",
|
||||
common.WithTextareaAttrs(
|
||||
"hx-on:change", "onPullRequestTemplateChange(event)",
|
||||
),
|
||||
)
|
||||
</details>
|
||||
<div class="buttons is-right">
|
||||
<button type="submit" class="button is-info is-large">
|
||||
<span class="icon">
|
||||
<i class="fa fa-robot"></i>
|
||||
</span>
|
||||
<span>Générer</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2 class="title is-size-3">Votre PR</h2>
|
||||
<form action={ common.CurrentURL(ctx) } method="post" hx-disabled-elt="textarea, input, select, button" hx-indicator="#generation-progress">
|
||||
@common.FormField(vmodel.PullRequestForm, "pr-title", "title", "Titre")
|
||||
@common.FormTextarea(vmodel.PullRequestForm, "pr-body", "body", "Corps")
|
||||
<div class="buttons is-right">
|
||||
<button type="submit" class="button is-info is-large">
|
||||
<span class="icon">
|
||||
<i class="fa fa-rocket"></i>
|
||||
</span>
|
||||
<span>Mettre à jour</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<progress id="generation-progress" class="htmx-indicator progress"></progress>
|
||||
if vmodel.PullRequestTips != "" {
|
||||
{{ html := markdownToHTML(ctx, vmodel.PullRequestTips) }}
|
||||
if html != "" {
|
||||
<article class="message is-info mt-5">
|
||||
<div class="message-header">
|
||||
<p><span class="icon"><i class="fa fa-lightbulb"></i></span>Questionnements</p>
|
||||
<button class="delete" aria-label="delete" hx-on:click="onCloseMessage(this)"></button>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
<div class="content">
|
||||
<p>Utilisez ces quelques questions pour réfléchir aux éléments d'informations nécessaire à la bonne rédaction de votre PR:</p>
|
||||
@templ.Raw(html)
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
}
|
||||
<script type="text/javascript">
|
||||
function onCloseMessage(closeElement) {
|
||||
closeElement.closest('.message').style.display = 'none';
|
||||
}
|
||||
|
||||
function onSummaryChange(evt) {
|
||||
const summary = evt.currentTarget.value;
|
||||
const projectId = document.getElementById("pr-project").value;
|
||||
sessionStorage.setItem(`pr-summary-${projectId}`, summary);
|
||||
}
|
||||
|
||||
function onPullRequestTemplateChange(evt) {
|
||||
const prTemplate = evt.currentTarget.value;
|
||||
const pullRequestId = document.getElementById("pr-pullrequest").value;
|
||||
localStorage.setItem(`pr-template-${pullRequestId}`, prTemplate);
|
||||
}
|
||||
|
||||
function savePreferred() {
|
||||
savePreferredProject()
|
||||
savePreferredPullRequest()
|
||||
}
|
||||
|
||||
function savePreferredProject() {
|
||||
const projectId = document.getElementById("pr-project").value;
|
||||
localStorage.setItem(`preferred-project`, projectId);
|
||||
}
|
||||
|
||||
function restorePreferredProject() {
|
||||
const preferredProject = localStorage.getItem(`preferred-project`);
|
||||
if (!preferredProject) return;
|
||||
const projectElement = document.getElementById("pr-project");
|
||||
if (!projectElement) return;
|
||||
if (preferredProject === projectElement.value) return;
|
||||
projectElement.value = preferredProject;
|
||||
projectElement.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
function savePreferredPullRequest() {
|
||||
const pullRequestId = document.getElementById("pr-pullrequest").value;
|
||||
localStorage.setItem(`preferred-pullrequest`, pullRequestId);
|
||||
}
|
||||
|
||||
function restorePreferredPullRequest() {
|
||||
const preferredPullRequest = localStorage.getItem(`preferred-pullrequest`);
|
||||
if (!preferredPullRequest) return;
|
||||
const pullRequestElement = document.getElementById("pr-pullrequest");
|
||||
if (!pullRequestElement) return;
|
||||
if (preferredPullRequest === pullRequestElement.value) return;
|
||||
pullRequestElement.value = preferredPullRequest;
|
||||
pullRequestElement.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
function restoreLastSummary() {
|
||||
const summaryTextarea = document.getElementById("pr-summary");
|
||||
if (!summaryTextarea) return;
|
||||
const summary = summaryTextarea.value;
|
||||
if (summary !== "") return;
|
||||
const projectId = document.getElementById("pr-project").value;
|
||||
if (!projectId) return;
|
||||
const savedSummary = sessionStorage.getItem(`pr-summary-${projectId}`);
|
||||
if (!savedSummary) return;
|
||||
summaryTextarea.value = savedSummary;
|
||||
}
|
||||
|
||||
function restorePullRequestTemplate() {
|
||||
const prTemplateTextarea = document.getElementById("pr-template");
|
||||
if (!prTemplateTextarea) return;
|
||||
const prTemplate = prTemplateTextarea.value;
|
||||
if (prTemplate !== "") return;
|
||||
const projectId = document.getElementById("pr-project").value;
|
||||
if (!projectId) return;
|
||||
const savedprTemplate = localStorage.getItem(`pr-template-${projectId}`);
|
||||
if (!savedprTemplate) return;
|
||||
prTemplateTextarea.value = savedprTemplate;
|
||||
}
|
||||
|
||||
htmx.onLoad(function(){
|
||||
restoreLastSummary();
|
||||
restorePreferredProject();
|
||||
restorePreferredPullRequest();
|
||||
restorePullRequestTemplate();
|
||||
})
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
||||
func projectsToOptions(projects []*model.Project) []string {
|
||||
options := make([]string, 0, len(projects))
|
||||
options = append(options, "", "")
|
||||
for _, p := range projects {
|
||||
options = append(options, p.Name, p.ID)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func pullRequestsToOptions(pullRequests []*model.PullRequest) []string {
|
||||
options := make([]string, 0, len(pullRequests))
|
||||
options = append(options, "", "")
|
||||
for _, pr := range pullRequests {
|
||||
options = append(options, pr.Title, pr.ID)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func markdownToHTML(ctx context.Context, text string) string {
|
||||
var buff bytes.Buffer
|
||||
if err := goldmark.Convert([]byte(text), &buff); err != nil {
|
||||
slog.ErrorContext(ctx, "could not convert markdown to html", slog.Any("error", errors.WithStack(err)))
|
||||
return ""
|
||||
}
|
||||
|
||||
return buff.String()
|
||||
}
|
@ -0,0 +1,320 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.819
|
||||
package component
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/form"
|
||||
common "forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common/component"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/yuin/goldmark"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PullRequestPageVModel struct {
|
||||
PullRequestURL string
|
||||
SummaryForm *form.Form
|
||||
PullRequestForm *form.Form
|
||||
PullRequestTips string
|
||||
Projects []*model.Project
|
||||
PullRequests []*model.PullRequest
|
||||
SelectedProjectID string
|
||||
SelectedPullRequestID string
|
||||
}
|
||||
|
||||
const summaryPlaceholder = `
|
||||
Décrivez rapidement les modifications apportées par la PR, ClearCase utilisera le modèle de PR présent dans le dépôt (ou un modèle par défaut) afin de générer une version mise en forme et complétée.
|
||||
|
||||
Afin de fournir plus d'information de contexte au LLM, vous pouvez faire référence à d'autres tickets du dépôt via un ou plusieurs '#<pr_id>' et/ou des chemins vers des fichiers présents dans celui ci.
|
||||
`
|
||||
|
||||
const bodyPlaceholder = `
|
||||
Une fois votre PR générée, vous pourrez l'éditer puis la créer directement en cliquant sur le bouton 'Mettre à jour' ci-dessous.
|
||||
`
|
||||
|
||||
const prTemplatePlaceholder = `
|
||||
Vous pouvez surcharger le modèle de PR fourni par le projet en remplissant ce champ.
|
||||
`
|
||||
|
||||
func NewPullRequestSummaryForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"project",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"pullrequest",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"summary",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": strings.TrimSpace(summaryPlaceholder),
|
||||
},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"template",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": strings.TrimSpace(prTemplatePlaceholder),
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewPullRequestForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"title",
|
||||
form.Attrs{
|
||||
"type": "text",
|
||||
"placeholder": "Écrivez le résumé de votre demande et cliquez sur 'Générer' pour remplir automatiquement ces champs.",
|
||||
},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
form.NewField(
|
||||
"body",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": strings.TrimSpace(bodyPlaceholder),
|
||||
},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func PullRequestPage(vmodel PullRequestPageVModel) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
if vmodel.PullRequestURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<article class=\"message is-primary\"><div class=\"message-header\"><p>Pull Request modifiée !</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\">Votre PR a été mise à jour et est disponible à l'adresse suivante: <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(vmodel.PullRequestURL)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" target=\"_blank\"><code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.PullRequestURL)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/pullrequest/component/pullrequest_page.templ`, Line: 106, Col: 99}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</code></a>.</div></article><script type=\"text/javascript\">\n\t\t\t\t\t\tfunction clearSummary(projectId) {\n\t\t\t\t\t\t\tsessionStorage.removeItem(`pr-summary-${projectId}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfunction openPR(prUrl) {\n\t\t\t\t\t\t\twindow.open(prUrl, \"_blank\");\n\t\t\t\t\t\t}\n\t\t\t\t\t</script> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.JSFuncCall("clearSummary", vmodel.SelectedProjectID).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.JSFuncCall("openPR", vmodel.PullRequestURL).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " <div class=\"columns\"><div class=\"column is-4\"><form id=\"summary-form\" action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" method=\"put\" hx-disabled-elt=\"textarea, input, select, button\" hx-on:htmx:before-send=\"savePreferred()\" hx-indicator=\"#generation-progress\"><h2 class=\"title is-size-3\">Résumé de la PR</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormSelect(
|
||||
vmodel.SummaryForm, "pr-project", "project", "Projet",
|
||||
common.WithOptions(projectsToOptions(vmodel.Projects)...),
|
||||
common.WithAttrs(
|
||||
"hx-get", string(common.CurrentURL(ctx, common.WithoutValues("project", "*"))),
|
||||
"hx-target", "body",
|
||||
"hx-push-url", "true",
|
||||
),
|
||||
).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormSelect(
|
||||
vmodel.SummaryForm, "pr-pullrequest", "pullrequest", "PR",
|
||||
common.WithOptions(pullRequestsToOptions(vmodel.PullRequests)...),
|
||||
common.WithAttrs(
|
||||
"hx-get", string(common.CurrentURL(ctx, common.WithoutValues("pullrequest", "*"))),
|
||||
"hx-target", "body",
|
||||
"hx-push-url", "true",
|
||||
),
|
||||
).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormTextarea(
|
||||
vmodel.SummaryForm, "pr-summary", "summary", "Résumé",
|
||||
common.WithTextareaAttrs(
|
||||
"hx-on:change", "onSummaryChange(event)",
|
||||
),
|
||||
).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<details class=\"my-3\"><summary class=\"is-clickable\">Paramètres avancés</summary>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormTextarea(
|
||||
vmodel.SummaryForm, "pr-template", "template", "Surcharger le modèle de demande",
|
||||
common.WithTextareaAttrs(
|
||||
"hx-on:change", "onPullRequestTemplateChange(event)",
|
||||
),
|
||||
).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</details><div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-robot\"></i></span> <span>Générer</span></button></div></form></div><div class=\"column\"><h2 class=\"title is-size-3\">Votre PR</h2><form action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" method=\"post\" hx-disabled-elt=\"textarea, input, select, button\" hx-indicator=\"#generation-progress\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormField(vmodel.PullRequestForm, "pr-title", "title", "Titre").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormTextarea(vmodel.PullRequestForm, "pr-body", "body", "Corps").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-rocket\"></i></span> <span>Mettre à jour</span></button></div></form></div></div><progress id=\"generation-progress\" class=\"htmx-indicator progress\"></progress> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if vmodel.PullRequestTips != "" {
|
||||
html := markdownToHTML(ctx, vmodel.PullRequestTips)
|
||||
if html != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<article class=\"message is-info mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-lightbulb\"></i></span>Questionnements</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\"><div class=\"content\"><p>Utilisez ces quelques questions pour réfléchir aux éléments d'informations nécessaire à la bonne rédaction de votre PR:</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(html).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></article>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " <script type=\"text/javascript\">\n\t\tfunction onCloseMessage(closeElement) {\n\t\t\tcloseElement.closest('.message').style.display = 'none';\n\t\t}\n\n\t\tfunction onSummaryChange(evt) {\n\t\t\tconst summary = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"pr-project\").value;\n\t\t\tsessionStorage.setItem(`pr-summary-${projectId}`, summary);\n\t\t}\n\n\t\tfunction onPullRequestTemplateChange(evt) {\n\t\t\tconst prTemplate = evt.currentTarget.value;\n\t\t\tconst pullRequestId = document.getElementById(\"pr-pullrequest\").value;\n\t\t\tlocalStorage.setItem(`pr-template-${pullRequestId}`, prTemplate);\n\t\t}\n\n\t\tfunction savePreferred() {\n\t\t\tsavePreferredProject()\n\t\t\tsavePreferredPullRequest()\n\t\t}\n\n\t\tfunction savePreferredProject() {\n\t\t\tconst projectId = document.getElementById(\"pr-project\").value;\n\t\t\tlocalStorage.setItem(`preferred-project`, projectId);\n\t\t}\n\n\t\tfunction restorePreferredProject() {\n\t\t\tconst preferredProject = localStorage.getItem(`preferred-project`);\n\t\t\tif (!preferredProject) return;\n\t\t\tconst projectElement = document.getElementById(\"pr-project\");\n\t\t\tif (!projectElement) return;\n\t\t\tif (preferredProject === projectElement.value) return;\n\t\t\tprojectElement.value = preferredProject;\n\t\t\tprojectElement.dispatchEvent(new Event('change'));\n\t\t}\n\n\t\tfunction savePreferredPullRequest() {\n\t\t\tconst pullRequestId = document.getElementById(\"pr-pullrequest\").value;\n\t\t\tlocalStorage.setItem(`preferred-pullrequest`, pullRequestId);\n\t\t}\n\n\t\tfunction restorePreferredPullRequest() {\n\t\t\tconst preferredPullRequest = localStorage.getItem(`preferred-pullrequest`);\n\t\t\tif (!preferredPullRequest) return;\n\t\t\tconst pullRequestElement = document.getElementById(\"pr-pullrequest\");\n\t\t\tif (!pullRequestElement) return;\n\t\t\tif (preferredPullRequest === pullRequestElement.value) return;\n\t\t\tpullRequestElement.value = preferredPullRequest;\n\t\t\tpullRequestElement.dispatchEvent(new Event('change'));\n\t\t}\n\n\t\tfunction restoreLastSummary() {\n\t\t\tconst summaryTextarea = document.getElementById(\"pr-summary\");\n\t\t\tif (!summaryTextarea) return;\n const summary = summaryTextarea.value;\n\t\t\tif (summary !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"pr-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedSummary = sessionStorage.getItem(`pr-summary-${projectId}`);\n\t\t\tif (!savedSummary) return;\n\t\t\tsummaryTextarea.value = savedSummary;\n\t\t}\n\n\t\tfunction restorePullRequestTemplate() {\n\t\t\tconst prTemplateTextarea = document.getElementById(\"pr-template\");\n\t\t\tif (!prTemplateTextarea) return;\n const prTemplate = prTemplateTextarea.value;\n\t\t\tif (prTemplate !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"pr-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedprTemplate = localStorage.getItem(`pr-template-${projectId}`);\n\t\t\tif (!savedprTemplate) return;\n\t\t\tprTemplateTextarea.value = savedprTemplate;\n\t\t}\n\n\t\thtmx.onLoad(function(){\n\t\t\trestoreLastSummary();\n\t\t\trestorePreferredProject();\n\t\t\trestorePreferredPullRequest();\n\t\t\trestorePullRequestTemplate();\n })\n\t\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Éditer une PR"),
|
||||
)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func projectsToOptions(projects []*model.Project) []string {
|
||||
options := make([]string, 0, len(projects))
|
||||
options = append(options, "", "")
|
||||
for _, p := range projects {
|
||||
options = append(options, p.Name, p.ID)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func pullRequestsToOptions(pullRequests []*model.PullRequest) []string {
|
||||
options := make([]string, 0, len(pullRequests))
|
||||
options = append(options, "", "")
|
||||
for _, pr := range pullRequests {
|
||||
options = append(options, pr.Title, pr.ID)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func markdownToHTML(ctx context.Context, text string) string {
|
||||
var buff bytes.Buffer
|
||||
if err := goldmark.Convert([]byte(text), &buff); err != nil {
|
||||
slog.ErrorContext(ctx, "could not convert markdown to html", slog.Any("error", errors.WithStack(err)))
|
||||
return ""
|
||||
}
|
||||
|
||||
return buff.String()
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
46
internal/http/handler/webui/pullrequest/handler.go
Normal file
46
internal/http/handler/webui/pullrequest/handler.go
Normal file
@ -0,0 +1,46 @@
|
||||
package pullrequest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/service"
|
||||
httpCtx "forge.cadoles.com/wpetit/clearcase/internal/http/context"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/url"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
mux *http.ServeMux
|
||||
forge *service.ForgeManager
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewHandler(forge *service.ForgeManager) *Handler {
|
||||
h := &Handler{
|
||||
mux: http.NewServeMux(),
|
||||
forge: forge,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("GET /", h.getPullRequestPage)
|
||||
h.mux.HandleFunc("PUT /", h.handlePullRequestSummaryForm)
|
||||
h.mux.HandleFunc("POST /", h.handlePullRequestForm)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if errors.Is(err, service.ErrForgeNotAvailable) {
|
||||
baseURL := url.Mutate(httpCtx.BaseURL(r.Context()), url.WithPath("/auth/logout"))
|
||||
http.Redirect(w, r, baseURL.String(), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
common.HandleError(w, r, errors.WithStack(err))
|
||||
}
|
||||
|
||||
var _ http.Handler = &Handler{}
|
242
internal/http/handler/webui/pullrequest/pullrequest_page.go
Normal file
242
internal/http/handler/webui/pullrequest/pullrequest_page.go
Normal file
@ -0,0 +1,242 @@
|
||||
package pullrequest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
httpCtx "forge.cadoles.com/wpetit/clearcase/internal/http/context"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/form"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/pullrequest/component"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (h *Handler) getPullRequestPage(w http.ResponseWriter, r *http.Request) {
|
||||
vmodel, err := h.fillPullRequestPageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
pr := component.PullRequestPage(*vmodel)
|
||||
templ.Handler(pr).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) fillPullRequestPageVModel(r *http.Request) (*component.PullRequestPageVModel, error) {
|
||||
vmodel := &component.PullRequestPageVModel{
|
||||
SummaryForm: component.NewPullRequestSummaryForm(),
|
||||
PullRequestForm: component.NewPullRequestForm(),
|
||||
}
|
||||
|
||||
err := common.FillViewModel(
|
||||
r.Context(), vmodel, r,
|
||||
h.fillPullRequestPageSelectedProject,
|
||||
h.fillPullRequestPagePullRequests,
|
||||
h.fillPullRequestPageProjects,
|
||||
h.fillPullRequestPageSelectedPullRequest,
|
||||
h.fillPullRequestPageSummary,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return vmodel, nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillPullRequestPageProjects(ctx context.Context, vmodel *component.PullRequestPageVModel, r *http.Request) error {
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
projects, err := h.forge.GetUserProjects(ctx, user)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.Projects = projects
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillPullRequestPagePullRequests(ctx context.Context, vmodel *component.PullRequestPageVModel, r *http.Request) error {
|
||||
if vmodel.SelectedProjectID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
pullRequests, err := h.forge.GetUserProjectOpenedPullRequests(ctx, user, vmodel.SelectedProjectID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.PullRequests = pullRequests
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillPullRequestPageSelectedProject(ctx context.Context, vmodel *component.PullRequestPageVModel, r *http.Request) error {
|
||||
project := r.URL.Query().Get("project")
|
||||
if project == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
vmodel.SelectedProjectID = project
|
||||
vmodel.SummaryForm.Field("project").Set("value", project)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillPullRequestPageSelectedPullRequest(ctx context.Context, vmodel *component.PullRequestPageVModel, r *http.Request) error {
|
||||
pullRequest := r.URL.Query().Get("pullrequest")
|
||||
if pullRequest == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
vmodel.SelectedPullRequestID = pullRequest
|
||||
vmodel.SummaryForm.Field("pullrequest").Set("value", pullRequest)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillPullRequestPageSummary(ctx context.Context, vmodel *component.PullRequestPageVModel, r *http.Request) error {
|
||||
if vmodel.SelectedProjectID == "" || vmodel.SelectedPullRequestID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
pullRequest, err := h.forge.GetUserProjectPullRequest(ctx, user, vmodel.SelectedProjectID, vmodel.SelectedPullRequestID)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "could not retrieve selected pull request", slog.Any("error", errors.WithStack(err)))
|
||||
return nil
|
||||
}
|
||||
|
||||
vmodel.SummaryForm.Field("summary").Set("value", pullRequest.Body)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) handlePullRequestSummaryForm(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
pullRequestSummaryForm := component.NewPullRequestSummaryForm()
|
||||
|
||||
if err := pullRequestSummaryForm.Handle(r); err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel, err := h.fillPullRequestPageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel.SummaryForm = pullRequestSummaryForm
|
||||
|
||||
if errs := pullRequestSummaryForm.Validate(); errs != nil {
|
||||
page := component.PullRequestPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
projectID, err := form.FormFieldAttr[string](pullRequestSummaryForm, "project", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
pullRequestID, err := form.FormFieldAttr[string](pullRequestSummaryForm, "pullrequest", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
prSummary, err := form.FormFieldAttr[string](pullRequestSummaryForm, "summary", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
prTemplate, err := form.FormFieldAttr[string](pullRequestSummaryForm, "template", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
prTemplate = strings.TrimSpace(prTemplate)
|
||||
|
||||
prTitle, prBody, prTips, err := h.forge.GeneratePullRequest(ctx, httpCtx.User(ctx), projectID, pullRequestID, prSummary, prTemplate)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel.PullRequestTips = prTips
|
||||
vmodel.PullRequestForm.Field("title").Set("value", prTitle)
|
||||
vmodel.PullRequestForm.Field("body").Set("value", prBody)
|
||||
|
||||
page := component.PullRequestPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) handlePullRequestForm(w http.ResponseWriter, r *http.Request) {
|
||||
pullRequestForm := component.NewPullRequestForm()
|
||||
|
||||
if err := pullRequestForm.Handle(r); err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel, err := h.fillPullRequestPageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
if errs := pullRequestForm.Validate(); errs != nil {
|
||||
vmodel.PullRequestForm = pullRequestForm
|
||||
|
||||
page := component.PullRequestPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
projectID := r.URL.Query().Get("project")
|
||||
pullRequestID := r.URL.Query().Get("pullrequest")
|
||||
|
||||
title, err := form.FormFieldAttr[string](pullRequestForm, "title", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := form.FormFieldAttr[string](pullRequestForm, "body", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
issueURL, err := h.forge.UpdatePullRequest(ctx, user, projectID, pullRequestID, title, body)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel.PullRequestURL = issueURL
|
||||
|
||||
vmodel.SummaryForm.Field("summary").Set("value", "")
|
||||
vmodel.PullRequestForm.Field("title").Set("value", "")
|
||||
vmodel.PullRequestForm.Field("body").Set("value", "")
|
||||
|
||||
page := component.PullRequestPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
}
|
76
internal/setup/forge_manager.go
Normal file
76
internal/setup/forge_manager.go
Normal file
@ -0,0 +1,76 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/config"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/service"
|
||||
"github.com/bornholm/genai/llm"
|
||||
"github.com/bornholm/genai/llm/provider"
|
||||
"github.com/bornholm/genai/llm/retry"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "github.com/bornholm/genai/llm/provider/all"
|
||||
"github.com/bornholm/genai/llm/provider/openrouter"
|
||||
)
|
||||
|
||||
func NewForgeManagerFromConfig(ctx context.Context, conf *config.Config) (*service.ForgeManager, error) {
|
||||
client, err := provider.Create(ctx,
|
||||
provider.WithChatCompletionOptions(provider.ClientOptions{
|
||||
Provider: provider.Name(conf.LLM.Provider.Name),
|
||||
BaseURL: conf.LLM.Provider.BaseURL,
|
||||
APIKey: conf.LLM.Provider.Key,
|
||||
Model: conf.LLM.Provider.Model,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not create llm client '%s'", conf.LLM.Provider.Name)
|
||||
}
|
||||
|
||||
if conf.LLM.Provider.Name == string(openrouter.Name) {
|
||||
client = &extendedContextClient{
|
||||
client: client,
|
||||
extend: func(ctx context.Context) context.Context {
|
||||
// Automatically "compress" prompts when using openrouter
|
||||
// See https://openrouter.ai/docs/features/message-transforms
|
||||
ctx = openrouter.WithTransforms(ctx, []string{"middle-out"})
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
client = retry.Wrap(client, time.Second, 5)
|
||||
|
||||
forgeFactories, err := getForgeFactories(conf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get forge factories")
|
||||
}
|
||||
|
||||
forgeManager := service.NewForgeManager(client, forgeFactories...)
|
||||
|
||||
return forgeManager, nil
|
||||
}
|
||||
|
||||
type extendedContextClient struct {
|
||||
client llm.Client
|
||||
extend func(ctx context.Context) context.Context
|
||||
}
|
||||
|
||||
// Embeddings implements llm.Client.
|
||||
func (c *extendedContextClient) Embeddings(ctx context.Context, input string, funcs ...llm.EmbeddingsOptionFunc) (llm.EmbeddingsResponse, error) {
|
||||
ctx = c.extend(ctx)
|
||||
return c.client.Embeddings(ctx, input, funcs...)
|
||||
}
|
||||
|
||||
// ChatCompletion implements llm.Client.
|
||||
func (c *extendedContextClient) ChatCompletion(ctx context.Context, funcs ...llm.ChatCompletionOptionFunc) (llm.ChatCompletionResponse, error) {
|
||||
ctx = c.extend(ctx)
|
||||
return c.client.ChatCompletion(ctx, funcs...)
|
||||
}
|
||||
|
||||
// Model implements llm.Client.
|
||||
func (c *extendedContextClient) Model() string {
|
||||
return c.Model()
|
||||
}
|
||||
|
||||
var _ llm.Client = &extendedContextClient{}
|
@ -12,12 +12,12 @@ import (
|
||||
)
|
||||
|
||||
func NewIssueHandlerFromConfig(ctx context.Context, conf *config.Config) (*issue.Handler, error) {
|
||||
issueManager, err := NewIssueManagerFromConfig(ctx, conf)
|
||||
forgeManager, err := NewForgeManagerFromConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return issue.NewHandler(issueManager), nil
|
||||
return issue.NewHandler(forgeManager), nil
|
||||
}
|
||||
|
||||
type authProviderBasedForgeFactory struct {
|
||||
|
@ -1,35 +0,0 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/config"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/service"
|
||||
"github.com/bornholm/genai/llm/provider"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "github.com/bornholm/genai/llm/provider/openai"
|
||||
_ "github.com/bornholm/genai/llm/provider/openrouter"
|
||||
)
|
||||
|
||||
func NewIssueManagerFromConfig(ctx context.Context, conf *config.Config) (*service.IssueManager, error) {
|
||||
client, err := provider.Create(ctx,
|
||||
provider.WithConfig(&provider.Config{
|
||||
Provider: provider.Name(conf.LLM.Provider.Name),
|
||||
BaseURL: conf.LLM.Provider.BaseURL,
|
||||
Key: conf.LLM.Provider.Key,
|
||||
Model: conf.LLM.Provider.Model,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not create llm client '%s'", conf.LLM.Provider.Name)
|
||||
}
|
||||
|
||||
forgeFactories, err := getForgeFactories(conf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get forge factories")
|
||||
}
|
||||
|
||||
issueManager := service.NewIssueManager(client, forgeFactories...)
|
||||
|
||||
return issueManager, nil
|
||||
}
|
18
internal/setup/pullrequest_handler.go
Normal file
18
internal/setup/pullrequest_handler.go
Normal file
@ -0,0 +1,18 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/config"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/pullrequest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func NewPullRequestHandlerFromConfig(ctx context.Context, conf *config.Config) (*pullrequest.Handler, error) {
|
||||
forgeManager, err := NewForgeManagerFromConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return pullrequest.NewHandler(forgeManager), nil
|
||||
}
|
@ -2,6 +2,7 @@ package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/config"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui"
|
||||
@ -22,13 +23,27 @@ func NewWebUIHandlerFromConfig(ctx context.Context, conf *config.Config) (*webui
|
||||
|
||||
opts = append(opts, webui.WithMount("/auth/", authHandler))
|
||||
|
||||
// Configure index redirect
|
||||
|
||||
opts = append(opts, webui.WithMount("/", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/issue", http.StatusTemporaryRedirect)
|
||||
}))))
|
||||
|
||||
// Configure issue handler
|
||||
issueHandler, err := NewIssueHandlerFromConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not configure issue handler from config")
|
||||
}
|
||||
|
||||
opts = append(opts, webui.WithMount("/", authMiddleware(issueHandler)))
|
||||
opts = append(opts, webui.WithMount("/issue/", authMiddleware(issueHandler)))
|
||||
|
||||
// Configure pull request handler
|
||||
pullRequestHandler, err := NewPullRequestHandlerFromConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not configure pull request handler from config")
|
||||
}
|
||||
|
||||
opts = append(opts, webui.WithMount("/pullrequest/", authMiddleware(pullRequestHandler)))
|
||||
|
||||
// Configure common handler
|
||||
commonHandler, err := NewCommonHandlerFromConfig(ctx, conf)
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.23 AS build
|
||||
FROM golang:1.24 AS build
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y make git
|
||||
|
Reference in New Issue
Block a user