| package api |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "net/http" |
| |
| "github.com/go-chi/chi/v5" |
| "go.skia.org/infra/go/alogin" |
| "go.skia.org/infra/go/httputils" |
| "go.skia.org/infra/go/skerr" |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/perf/go/userissue" |
| ) |
| |
| // userIssueApi provides a struct for handling Buganizer Annotation feature. |
| type userIssueApi struct { |
| loginProvider alogin.Login |
| userIssueStore userissue.Store |
| } |
| |
| // NewUserIssueApi returns a new instance of userIssueApi. |
| func NewUserIssueApi(loginProvider alogin.Login, userIssueStore userissue.Store) userIssueApi { |
| return userIssueApi{ |
| loginProvider: loginProvider, |
| userIssueStore: userIssueStore, |
| } |
| } |
| |
| // RegisterHandlers registers the api handlers for their respective routes. |
| func (ui userIssueApi) RegisterHandlers(router *chi.Mux) { |
| router.Post("/_/user_issues", ui.userIssuesHandler) |
| router.Post("/_/user_issue/save", ui.saveUserIssueHandler) |
| router.Post("/_/user_issue/delete", ui.deleteUserIssueHandler) |
| } |
| |
| // GetUserIssuesForTraceKeysRequest is the request to fetch all user issues |
| // corresponding to a list of trace keys and commit position range |
| type GetUserIssuesForTraceKeysRequest struct { |
| TraceKeys []string `json:"trace_keys"` |
| BeginCommitPosition int64 `json:"begin_commit_position"` |
| EndCommitPosition int64 `json:"end_commit_position"` |
| } |
| |
| type GetUserIssuesForTraceKeysResponse struct { |
| UserIssues []userissue.UserIssue |
| } |
| |
| // userIssuesHandler returns list of user reported buganzier issues |
| // corresponding to the a list of trace keys and commit position range. |
| func (ui userIssueApi) userIssuesHandler(w http.ResponseWriter, r *http.Request) { |
| ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout) |
| defer cancel() |
| w.Header().Set("Content-Type", "application/json") |
| |
| var getIssuesReq GetUserIssuesForTraceKeysRequest |
| if err := json.NewDecoder(r.Body).Decode(&getIssuesReq); err != nil { |
| httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError) |
| return |
| } |
| |
| traceKeys := getIssuesReq.TraceKeys |
| begin := getIssuesReq.BeginCommitPosition |
| end := getIssuesReq.EndCommitPosition |
| |
| if len(traceKeys) == 0 || begin == 0 || end == 0 { |
| e := fmt.Errorf("Failed to fetch data: ") |
| httputils.ReportError(w, e, "Missing Arguments", http.StatusBadRequest) |
| return |
| } |
| |
| userIssues, err := ui.userIssueStore.GetUserIssuesForTraceKeys(ctx, traceKeys, begin, end) |
| if err != nil { |
| httputils.ReportError(w, err, "Failed to fetch data", http.StatusInternalServerError) |
| return |
| } |
| |
| resp := GetUserIssuesForTraceKeysResponse{ |
| UserIssues: userIssues, |
| } |
| |
| if err := json.NewEncoder(w).Encode(resp); err != nil { |
| sklog.Errorf("Failed to encode response: %s", http.StatusInternalServerError) |
| } |
| } |
| |
| // SaveUserIssueRequest is the request to create a new User Issue |
| type SaveUserIssueRequest struct { |
| TraceKey string `json:"trace_key"` |
| CommitPosition int64 `json:"commit_position"` |
| IssueId int64 `json:"issue_id"` |
| } |
| |
| // saveUserIssueHandler creates a new userissue in the db |
| func (ui *userIssueApi) saveUserIssueHandler(w http.ResponseWriter, r *http.Request) { |
| ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout) |
| defer cancel() |
| w.Header().Set("Content-Type", "application/json") |
| |
| loggedInEmail := ui.loginProvider.LoggedInAs(r) |
| if loggedInEmail == "" { |
| httputils.ReportError(w, skerr.Fmt("Login Required"), "", http.StatusUnauthorized) |
| return |
| } |
| |
| var saveReq SaveUserIssueRequest |
| if err := json.NewDecoder(r.Body).Decode(&saveReq); err != nil { |
| httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusBadRequest) |
| return |
| } |
| |
| if len(saveReq.TraceKey) == 0 { |
| httputils.ReportError(w, skerr.Fmt("Invalid Argument: "), "trace_key", http.StatusBadRequest) |
| } |
| |
| if saveReq.CommitPosition == 0 || saveReq.IssueId == 0 { |
| httputils.ReportError(w, skerr.Fmt("Invalid Argument: "), "commit position and issue id", http.StatusBadRequest) |
| } |
| |
| userIssueObj := userissue.UserIssue{ |
| UserId: loggedInEmail.String(), |
| TraceKey: saveReq.TraceKey, |
| CommitPosition: saveReq.CommitPosition, |
| IssueId: saveReq.IssueId, |
| } |
| err := ui.userIssueStore.Save(ctx, &userIssueObj) |
| if err != nil { |
| httputils.ReportError(w, err, "Failed to save.", http.StatusInternalServerError) |
| } |
| } |
| |
| // DeleteUserIssueRequest deletes an existing userissue from the db |
| type DeleteUserIssueRequest struct { |
| TraceKey string `json:"trace_key"` |
| CommitPosition int64 `json:"commit_position"` |
| } |
| |
| // deleteUserIssueHandler deletes a user issue from the db |
| func (ui *userIssueApi) deleteUserIssueHandler(w http.ResponseWriter, r *http.Request) { |
| ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout) |
| defer cancel() |
| w.Header().Set("Content-Type", "application/json") |
| |
| loggedInEmail := ui.loginProvider.LoggedInAs(r) |
| if loggedInEmail == "" { |
| httputils.ReportError(w, skerr.Fmt("Login Required"), "", http.StatusUnauthorized) |
| return |
| } |
| |
| var deleteReq DeleteUserIssueRequest |
| if err := json.NewDecoder(r.Body).Decode(&deleteReq); err != nil { |
| httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError) |
| return |
| } |
| |
| if len(deleteReq.TraceKey) == 0 || deleteReq.CommitPosition == 0 { |
| httputils.ReportError(w, skerr.Fmt("Invalid arguments:"), "Both trace_key and commit_position needs be specified", http.StatusBadRequest) |
| return |
| } |
| |
| err := ui.userIssueStore.Delete(ctx, deleteReq.TraceKey, deleteReq.CommitPosition) |
| if err != nil { |
| httputils.ReportError(w, skerr.Fmt("Error:"), "Failed to remove bug from this data point.", http.StatusInternalServerError) |
| } |
| } |