Skip to content

Commit 0f9f997

Browse files
authored
Merge pull request #43 from devtron-labs/develop-sync
chore: develop sync
2 parents 7d59fe6 + c313f87 commit 0f9f997

2,096 files changed

Lines changed: 628805 additions & 84818 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.19.9-alpine3.18 AS build-env
1+
FROM golang:1.24.6-alpine AS build-env
22
RUN apk add --no-cache git gcc musl-dev
33
RUN apk add --update make
44
RUN go install github.com/google/wire/cmd/wire@latest

Wire.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,24 @@ package main
2121

2222
import (
2323
"github.com/devtron-labs/central-api/api"
24-
"github.com/devtron-labs/central-api/api/currency"
2524
util "github.com/devtron-labs/central-api/client"
25+
"github.com/devtron-labs/central-api/internal/logger"
2626
"github.com/devtron-labs/central-api/pkg"
27-
currencyPkg "github.com/devtron-labs/central-api/pkg/currency"
2827
blob_storage "github.com/devtron-labs/common-lib/blob-storage"
29-
"github.com/devtron-labs/common-lib/utils"
3028
"github.com/google/wire"
3129
)
3230

3331
func InitializeApp() (*App, error) {
3432
wire.Build(
35-
utils.NewSugardLogger,
33+
logger.NewSugardLogger,
3634
//sql.PgSqlWireSet,
3735
//releaseNote.NewReleaseNoteRepositoryImpl,
3836
//wire.Bind(new(releaseNote.ReleaseNoteRepository), new(*releaseNote.ReleaseNoteRepositoryImpl)),
3937
blob_storage.NewBlobStorageServiceImpl,
4038
NewApp,
4139
api.NewMuxRouter,
4240
util.NewGitHubClient,
41+
util.NewGoogleSheetsClient,
4342
//logger.NewHttpClient,
4443
api.NewRestHandlerImpl,
4544
wire.Bind(new(api.RestHandler), new(*api.RestHandlerImpl)),
@@ -53,14 +52,17 @@ func InitializeApp() (*App, error) {
5352
pkg.NewCiBuildMetadataServiceImpl,
5453
wire.Bind(new(pkg.CiBuildMetadataService), new(*pkg.CiBuildMetadataServiceImpl)),
5554

56-
// Currency service dependencies
57-
currencyPkg.NewCurrencyConfig,
58-
currencyPkg.NewServiceImpl,
59-
wire.Bind(new(currencyPkg.Service), new(*currencyPkg.ServiceImpl)),
60-
currency.NewCurrencyRestHandlerImpl,
61-
wire.Bind(new(currency.CurrencyRestHandler), new(*currency.CurrencyRestHandlerImpl)),
62-
currency.NewRouter,
63-
wire.Bind(new(currency.Router), new(*currency.RouterImpl)),
55+
// S3 Upload Service
56+
pkg.NewS3UploadServiceImpl,
57+
wire.Bind(new(pkg.S3UploadService), new(*pkg.S3UploadServiceImpl)),
58+
59+
// Google Sheets Service
60+
pkg.NewGoogleSheetsServiceImpl,
61+
wire.Bind(new(pkg.GoogleSheetsService), new(*pkg.GoogleSheetsServiceImpl)),
62+
63+
// Feedback Service
64+
pkg.NewFeedbackServiceImpl,
65+
wire.Bind(new(pkg.FeedbackService), new(*pkg.FeedbackServiceImpl)),
6466
)
6567
return &App{}, nil
6668
}

api/RestHandler.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"net/http"
3030
"strconv"
3131
"strings"
32+
"time"
3233
)
3334

3435
type RestHandler interface {
@@ -39,16 +40,19 @@ type RestHandler interface {
3940
GetModuleByName(w http.ResponseWriter, r *http.Request)
4041
GetDockerfileTemplateMetadata(w http.ResponseWriter, r *http.Request)
4142
GetBuildpackMetadata(w http.ResponseWriter, r *http.Request)
43+
SubmitFeedback(w http.ResponseWriter, r *http.Request)
4244
}
4345

4446
func NewRestHandlerImpl(logger *zap.SugaredLogger, releaseNoteService pkg.ReleaseNoteService,
45-
webhookSecretValidator pkg.WebhookSecretValidator, client *util.GitHubClient, ciBuildMetadataService pkg.CiBuildMetadataService) *RestHandlerImpl {
47+
webhookSecretValidator pkg.WebhookSecretValidator, client *util.GitHubClient,
48+
ciBuildMetadataService pkg.CiBuildMetadataService, feedbackService pkg.FeedbackService) *RestHandlerImpl {
4649
return &RestHandlerImpl{
4750
logger: logger,
4851
releaseNoteService: releaseNoteService,
4952
webhookSecretValidator: webhookSecretValidator,
5053
client: client,
5154
ciBuildMetadataService: ciBuildMetadataService,
55+
feedbackService: feedbackService,
5256
}
5357
}
5458

@@ -58,6 +62,7 @@ type RestHandlerImpl struct {
5862
webhookSecretValidator pkg.WebhookSecretValidator
5963
client *util.GitHubClient
6064
ciBuildMetadataService pkg.CiBuildMetadataService
65+
feedbackService pkg.FeedbackService
6166
}
6267

6368
func (impl *RestHandlerImpl) GetModules(w http.ResponseWriter, r *http.Request) {
@@ -249,3 +254,50 @@ func isVersionNewer(v1, v2 string) bool {
249254
// Compare using semver
250255
return ver1.GreaterThan(ver2)
251256
}
257+
258+
// SubmitFeedback handles the feedback submission endpoint
259+
func (impl *RestHandlerImpl) SubmitFeedback(w http.ResponseWriter, r *http.Request) {
260+
impl.logger.Info("received feedback submission request")
261+
setupResponse(&w, r)
262+
263+
// Read request body
264+
body, err := ioutil.ReadAll(r.Body)
265+
if err != nil {
266+
impl.logger.Errorw("error reading request body", "err", err)
267+
impl.WriteJsonResp(w, err, "Failed to read request body", http.StatusBadRequest)
268+
return
269+
}
270+
defer r.Body.Close()
271+
272+
// Parse request directly into FeedbackData
273+
var feedbackData common.FeedbackData
274+
err = json.Unmarshal(body, &feedbackData)
275+
if err != nil {
276+
impl.logger.Errorw("error unmarshalling feedback request", "err", err)
277+
impl.WriteJsonResp(w, err, "Invalid request format", http.StatusBadRequest)
278+
return
279+
}
280+
281+
// Set submitted time if not provided
282+
if feedbackData.SubmittedAt.IsZero() {
283+
feedbackData.SubmittedAt = time.Now().UTC()
284+
}
285+
286+
// Submit feedback (this will upload to S3 and add to Google Sheets)
287+
err = impl.feedbackService.SubmitFeedback(&feedbackData)
288+
if err != nil {
289+
impl.logger.Errorw("error submitting feedback", "err", err, "ucid", feedbackData.UCID)
290+
impl.WriteJsonResp(w, err, "Failed to submit feedback", http.StatusInternalServerError)
291+
return
292+
}
293+
294+
impl.logger.Infow("successfully submitted feedback", "ucid", feedbackData.UCID, "threadName", feedbackData.ThreadName)
295+
296+
// Return success response
297+
response := map[string]interface{}{
298+
"message": "Feedback submitted successfully",
299+
"ucid": feedbackData.UCID,
300+
"s3Url": feedbackData.FullConversationURL,
301+
}
302+
impl.WriteJsonResp(w, nil, response, http.StatusOK)
303+
}

api/Router.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,7 @@ func (r MuxRouter) Init() {
7474
currencyRouter := r.Router.PathPrefix("/currency").Subrouter()
7575
// Initialize currency routes
7676
r.currencyRouter.InitCurrencyRoutes(currencyRouter)
77+
78+
// athena-ai-chat feedback router
79+
r.Router.Path("/athena-feedback").HandlerFunc(r.restHandler.SubmitFeedback).Methods("POST")
7780
}

client/BlobConfig.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ type BlobConfigVariables struct {
3838
AzureBlobContainerName string `env:"AZURE_BLOB_CONTAINER_NAME"`
3939
GcpBucketName string `env:"GCP_BUCKET_NAME"`
4040
GcpCredentialFileJsonData string `env:"GCP_CREDENTIAL_FILE_JSON_DATA"`
41+
42+
// Feedback Storage Configuration
43+
FeedbackStorageType blob_storage.BlobStorageType `env:"FEEDBACK_STORAGE_TYPE" envDefault:"S3"` // S3, GCP, AZURE
44+
FeedbackS3AccessKey string `env:"FEEDBACK_S3_ACCESS_KEY"`
45+
FeedbackS3Passkey string `env:"FEEDBACK_S3_PASS_KEY"`
46+
FeedbackS3BucketName string `env:"FEEDBACK_S3_BUCKET_NAME"`
47+
FeedbackS3Region string `env:"FEEDBACK_S3_REGION" envDefault:"us-east-1"`
48+
FeedbackGcpBucketName string `env:"FEEDBACK_GCP_BUCKET_NAME"`
49+
FeedbackGcpCredentialFileJsonData string `env:"FEEDBACK_GCP_CREDENTIAL_FILE_JSON_DATA"` // Can use same as Google Sheets service account
50+
FeedbackAzureEnabled bool `env:"FEEDBACK_AZURE_ENABLED" envDefault:"false"`
51+
FeedbackAzureAccountName string `env:"FEEDBACK_AZURE_ACCOUNT_NAME"`
52+
FeedbackAzureAccountKey string `env:"FEEDBACK_AZURE_ACCOUNT_KEY"`
53+
FeedbackAzureBlobContainerName string `env:"FEEDBACK_AZURE_BLOB_CONTAINER_NAME"`
4154
}
4255

4356
func NewBlobConfig(logger *zap.SugaredLogger) (*BlobConfigVariables, error) {

client/GoogleSheetsClient.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2020-2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package util
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"github.com/caarlos0/env"
23+
"go.uber.org/zap"
24+
"golang.org/x/oauth2/google"
25+
"google.golang.org/api/option"
26+
"google.golang.org/api/sheets/v4"
27+
)
28+
29+
// GoogleCloudConfig holds configuration for Google Cloud services (Sheets, Storage, etc.)
30+
type GoogleCloudConfig struct {
31+
ServiceAccountJSON string `env:"FEEDBACK_GCP_CREDENTIAL_FILE_JSON_DATA" envDefault:""`
32+
SpreadsheetID string `env:"GOOGLE_SPREADSHEET_ID" envDefault:""`
33+
}
34+
35+
// GoogleSheetsClient provides access to Google Sheets API and service account credentials
36+
// The same service account can be used for Google Cloud Storage
37+
type GoogleSheetsClient struct {
38+
SheetsService *sheets.Service
39+
Config *GoogleCloudConfig
40+
}
41+
42+
/* #nosec */
43+
func NewGoogleSheetsClient(logger *zap.SugaredLogger, blobConfig *BlobConfigVariables) (*GoogleSheetsClient, error) {
44+
cfg := &GoogleCloudConfig{}
45+
err := env.Parse(cfg)
46+
if err != nil {
47+
logger.Errorw("error parsing google cloud config", "err", err)
48+
return &GoogleSheetsClient{}, err
49+
}
50+
51+
// Fallback: If GOOGLE_SERVICE_ACCOUNT_JSON is not set, try using FEEDBACK_GCP_CREDENTIAL_FILE_JSON_DATA
52+
serviceAccountJSON := cfg.ServiceAccountJSON
53+
if serviceAccountJSON == "" && blobConfig != nil && blobConfig.FeedbackGcpCredentialFileJsonData != "" {
54+
serviceAccountJSON = blobConfig.FeedbackGcpCredentialFileJsonData
55+
logger.Info("Using FEEDBACK_GCP_CREDENTIAL_FILE_JSON_DATA for Google Sheets authentication")
56+
}
57+
58+
// If service account JSON is not provided, return empty client
59+
if serviceAccountJSON == "" {
60+
logger.Warn("Google service account JSON not provided (neither GOOGLE_SERVICE_ACCOUNT_JSON nor FEEDBACK_GCP_CREDENTIAL_FILE_JSON_DATA), Google Sheets client will not be initialized")
61+
return &GoogleSheetsClient{
62+
SheetsService: nil,
63+
Config: cfg,
64+
}, nil
65+
}
66+
67+
// Store the actual service account JSON being used
68+
cfg.ServiceAccountJSON = serviceAccountJSON
69+
70+
ctx := context.Background()
71+
72+
// Parse the service account JSON
73+
credentials, err := google.CredentialsFromJSON(ctx, []byte(serviceAccountJSON), sheets.SpreadsheetsScope)
74+
if err != nil {
75+
logger.Errorw("error creating credentials from service account JSON", "err", err)
76+
return nil, err
77+
}
78+
79+
// Create the sheets service
80+
sheetsService, err := sheets.NewService(ctx, option.WithCredentials(credentials))
81+
if err != nil {
82+
logger.Errorw("error creating sheets service", "err", err)
83+
return nil, err
84+
}
85+
86+
logger.Info("Google Sheets client initialized successfully")
87+
88+
return &GoogleSheetsClient{
89+
SheetsService: sheetsService,
90+
Config: cfg,
91+
}, nil
92+
}
93+
94+
// IsConfigured returns true if the Google Sheets client is properly configured
95+
func (c *GoogleSheetsClient) IsConfigured() bool {
96+
if c == nil {
97+
return false
98+
}
99+
return c.SheetsService != nil && c.Config != nil && c.Config.SpreadsheetID != ""
100+
}
101+
102+
// GetSpreadsheetID returns the configured spreadsheet ID
103+
func (c *GoogleSheetsClient) GetSpreadsheetID() string {
104+
if c == nil || c.Config == nil {
105+
return ""
106+
}
107+
return c.Config.SpreadsheetID
108+
}
109+
110+
// GetServiceAccountJSON returns the service account JSON for use with other Google Cloud services
111+
func (c *GoogleSheetsClient) GetServiceAccountJSON() string {
112+
if c == nil || c.Config == nil {
113+
return ""
114+
}
115+
return c.Config.ServiceAccountJSON
116+
}
117+
118+
// MarshalServiceAccountJSON is a helper to convert service account key to JSON string
119+
func MarshalServiceAccountJSON(serviceAccountKey map[string]interface{}) (string, error) {
120+
jsonBytes, err := json.Marshal(serviceAccountKey)
121+
if err != nil {
122+
return "", err
123+
}
124+
return string(jsonBytes), nil
125+
}

common/bean.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,16 @@ type GroupVersionKind struct {
7272
type ResourceIdentifier struct {
7373
Labels map[string]string `json:"labels"`
7474
}
75+
76+
// FeedbackData represents the data structure for feedback submissions
77+
type FeedbackData struct {
78+
UCID string `json:"ucid"`
79+
ThreadName string `json:"threadName"`
80+
UserEmail string `json:"userEmail"`
81+
Reasons []string `json:"reasons"`
82+
AdditionalDetails string `json:"additionalDetails"`
83+
ConversationText string `json:"conversationText"`
84+
IsCompressed bool `json:"isCompressed"`
85+
SubmittedAt time.Time `json:"submittedAt"`
86+
FullConversationURL string `json:"-"` // Internal field, not serialized to JSON
87+
}

0 commit comments

Comments
 (0)