From 417b9e59a871c1745db94164aeeaa3fabf65c644 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 24 Oct 2024 17:00:40 +0200 Subject: [PATCH] feat(backend): analytics - handler and model for dashboards --- backend/pkg/analytics/api/handler.go | 317 +++++++++++++++++++++ backend/pkg/analytics/api/model.go | 62 ++++ backend/pkg/analytics/api/router.go | 309 ++------------------ backend/pkg/analytics/service/dashboard.go | 136 ++++++--- 4 files changed, 497 insertions(+), 327 deletions(-) create mode 100644 backend/pkg/analytics/api/handler.go create mode 100644 backend/pkg/analytics/api/model.go diff --git a/backend/pkg/analytics/api/handler.go b/backend/pkg/analytics/api/handler.go new file mode 100644 index 000000000..48569cc26 --- /dev/null +++ b/backend/pkg/analytics/api/handler.go @@ -0,0 +1,317 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "io" + "net/http" + "strconv" + "time" +) + +func (e *Router) createDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + bodySize = len(bodyBytes) + + req := &CreateDashboardRequest{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &CreateDashboardResponse{ + DashboardID: 1, + } + + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) getDashboard(w http.ResponseWriter, r *http.Request) { + +} + +func (e *Router) getDashboards(w http.ResponseWriter, r *http.Request) { + params := r.URL.Query() + page := params.Get("page") + limit := params.Get("limit") + pageNum, _ := strconv.ParseUint(page, 10, 64) + limitNum, _ := strconv.ParseUint(limit, 10, 64) + + req := &GetDashboardsRequest{ + Page: pageNum, + Limit: limitNum, + Order: params.Get("order"), + Query: params.Get("query"), + FilterBy: params.Get("filterBy"), + } + + // if err != nil { + // e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, time.Now(), r.URL.Path, 0) + // return + // } + + fmt.Printf("req: %+v\n", req) + + resp := &GetDashboardsResponse{ + Dashboards: []Dashboard{ + { + DashboardID: 1, + Name: "Dashboard 1", + Description: "Description 1", + IsPublic: true, + IsPinned: true, + }, + { + DashboardID: 2, + Name: "Dashboard 2", + Description: "Description 2", + IsPublic: false, + IsPinned: false, + }, + }, + } + + e.ResponseWithJSON(r.Context(), w, resp, time.Now(), r.URL.Path, 0) +} + +func (e *Router) updateDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + + bodySize = len(bodyBytes) + + req := &UpdateDashboardRequest{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &UpdateDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) deleteDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &DeleteDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) pinDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &UpdateDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) addCardToDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + + bodySize = len(bodyBytes) + + req := &UpdateDashboardResponse{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &UpdateDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) createMetricAndAddToDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + + bodySize = len(bodyBytes) + + req := &UpdateDashboardRequest{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &UpdateDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) updateWidgetInDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + + bodySize = len(bodyBytes) + + req := &UpdateDashboardRequest{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &UpdateDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func (e *Router) removeWidgetFromDashboard(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + id, err := getDashboardId(r) + if err != nil { + e.ResponseWithError(r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp := &DeleteDashboardResponse{ + DashboardID: id, + } + e.ResponseWithJSON(r.Context(), w, resp, startTime, r.URL.Path, bodySize) +} + +func getDashboardId(r *http.Request) (int, error) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["dashboardId"]) + if err != nil { + return 0, err + } + return id, nil +} + +func recordMetrics(requestStart time.Time, url string, code, bodySize int) { + // TODO: Implement this +} + +func (e *Router) readBody(w http.ResponseWriter, r *http.Request, limit int64) ([]byte, error) { + body := http.MaxBytesReader(w, r.Body, limit) + bodyBytes, err := io.ReadAll(body) + + // Close body + if closeErr := body.Close(); closeErr != nil { + e.log.Warn(r.Context(), "error while closing request body: %s", closeErr) + } + if err != nil { + return nil, err + } + return bodyBytes, nil +} + +func (e *Router) ResponseOK(ctx context.Context, w http.ResponseWriter, requestStart time.Time, url string, bodySize int) { + w.WriteHeader(http.StatusOK) + e.log.Info(ctx, "response ok") + recordMetrics(requestStart, url, http.StatusOK, bodySize) +} + +func (e *Router) ResponseWithJSON(ctx context.Context, w http.ResponseWriter, res interface{}, requestStart time.Time, url string, bodySize int) { + e.log.Info(ctx, "response ok") + body, err := json.Marshal(res) + if err != nil { + e.log.Error(ctx, "can't marshal response: %s", err) + } + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(body) + if err != nil { + return + } + recordMetrics(requestStart, url, http.StatusOK, bodySize) +} + +type response struct { + Error string `json:"error"` +} + +func (e *Router) ResponseWithError(ctx context.Context, w http.ResponseWriter, code int, err error, requestStart time.Time, url string, bodySize int) { + e.log.Error(ctx, "response error, code: %d, error: %s", code, err) + body, err := json.Marshal(&response{err.Error()}) + if err != nil { + e.log.Error(ctx, "can't marshal response: %s", err) + } + w.WriteHeader(code) + _, err = w.Write(body) + if err != nil { + return + } + recordMetrics(requestStart, url, code, bodySize) +} diff --git a/backend/pkg/analytics/api/model.go b/backend/pkg/analytics/api/model.go new file mode 100644 index 000000000..5f3c51bd4 --- /dev/null +++ b/backend/pkg/analytics/api/model.go @@ -0,0 +1,62 @@ +package api + +type CreateDashboardRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` + Metrics []int `json:"metrics"` +} + +type GetDashboardsRequest struct { + Page uint64 `json:"page"` + Limit uint64 `json:"limit"` + Order string `json:"order"` + Query string `json:"query"` + FilterBy string `json:"filterBy"` +} + +type CreateDashboardResponse struct { + DashboardID int `json:"dashboard_id"` +} + +type Dashboard struct { + DashboardID int `json:"dashboard_id"` + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` +} + +type GetDashboardResponse struct { + Dashboard *Dashboard `json:"dashboard"` +} + +type GetDashboardsResponse struct { + Dashboards []Dashboard `json:"dashboards"` +} + +type UpdateDashboardRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` + Metrics []int `json:"metrics"` +} + +type UpdateDashboardResponse struct { + DashboardID int `json:"dashboard_id"` +} + +type DeleteDashboardResponse struct { + DashboardID int `json:"dashboard_id"` +} + +type Dashboards interface { + Add(projectID int, dashboard *Dashboard) error + Create(projectID int, dashboard *Dashboard) error + Get(projectID int, dashboardID int) (*Dashboard, error) + GetAll(projectID int) ([]Dashboard, error) + Update(projectID int, dashboardID int, dashboard *Dashboard) error + Delete(projectID int, dashboardID int) error +} diff --git a/backend/pkg/analytics/api/router.go b/backend/pkg/analytics/api/router.go index 2eda9e4e3..6bba46946 100644 --- a/backend/pkg/analytics/api/router.go +++ b/backend/pkg/analytics/api/router.go @@ -1,13 +1,11 @@ package api import ( - "encoding/json" "fmt" "net/http" analyticsConfig "openreplay/backend/internal/config/analytics" "openreplay/backend/pkg/common" "openreplay/backend/pkg/logger" - "strconv" "sync" "github.com/gorilla/mux" @@ -43,7 +41,32 @@ func NewRouter(cfg *analyticsConfig.Config, log logger.Logger, services *common. func (e *Router) init() { e.router = mux.NewRouter() e.router.HandleFunc("/", e.ping) - e.routes() + + e.router.HandleFunc("/{projectId}/dashboards", e.createDashboard).Methods("POST") + e.router.HandleFunc("/{projectId}/dashboards", e.getDashboards).Methods("GET") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}", e.getDashboard).Methods("GET") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}", e.updateDashboard).Methods("PUT") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}", e.deleteDashboard).Methods("DELETE") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/pin", e.pinDashboard).Methods("GET") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/cards", e.addCardToDashboard).Methods("POST") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/metrics", e.createMetricAndAddToDashboard).Methods("POST") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}", e.updateWidgetInDashboard).Methods("PUT") + e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}", e.removeWidgetFromDashboard).Methods("DELETE") + //e.router.HandleFunc("/{projectId}/cards/try", e.tryCard).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/try/sessions", e.tryCardSessions).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/try/issues", e.tryCardIssues).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards", e.getCards).Methods("GET") + //e.router.HandleFunc("/{projectId}/cards", e.createCard).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/search", e.searchCards).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}", e.getCard).Methods("GET") + //e.router.HandleFunc("/{projectId}/cards/{cardId}/sessions", e.getCardSessions).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}/issues", e.getCardFunnelIssues).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}/issues/{issueId}/sessions", e.getMetricFunnelIssueSessions).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}/errors", e.getCardErrorsList).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}/chart", e.getCardChart).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}", e.updateCard).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}/status", e.updateCardState).Methods("POST") + //e.router.HandleFunc("/{projectId}/cards/{cardId}", e.deleteCard).Methods("DELETE") } func (e *Router) ping(w http.ResponseWriter, r *http.Request) { @@ -58,291 +81,11 @@ func (e *Router) GetRouter() *mux.Router { return e.router } -func (e *Router) getAnalytics(w http.ResponseWriter, r *http.Request) { - //w.WriteHeader(http.StatusOK) - vars := mux.Vars(r) - id := vars["id"] - e.log.Info(r.Context(), id) - w.WriteHeader(http.StatusOK) - - //e.ResponseWithJSON(w, http.StatusOK, map[string]string{"message": "getAnalytics"}) -} - -func (e *Router) routes() { - e.router.HandleFunc("/{projectId}/dashboards", e.createDashboard).Methods("POST") - e.router.HandleFunc("/{projectId}/dashboards", e.getDashboards).Methods("GET") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}", e.getDashboard).Methods("GET") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}", e.updateDashboard).Methods("PUT") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}", e.deleteDashboard).Methods("DELETE") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/pin", e.pinDashboard).Methods("GET") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/cards", e.addCardToDashboard).Methods("POST") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/metrics", e.createMetricAndAddToDashboard).Methods("POST") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}", e.updateWidgetInDashboard).Methods("PUT") - e.router.HandleFunc("/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}", e.removeWidgetFromDashboard).Methods("DELETE") - e.router.HandleFunc("/{projectId}/cards/try", e.tryCard).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/try/sessions", e.tryCardSessions).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/try/issues", e.tryCardIssues).Methods("POST") - e.router.HandleFunc("/{projectId}/cards", e.getCards).Methods("GET") - e.router.HandleFunc("/{projectId}/cards", e.createCard).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/search", e.searchCards).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}", e.getCard).Methods("GET") - e.router.HandleFunc("/{projectId}/cards/{cardId}/sessions", e.getCardSessions).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}/issues", e.getCardFunnelIssues).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}/issues/{issueId}/sessions", e.getMetricFunnelIssueSessions).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}/errors", e.getCardErrorsList).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}/chart", e.getCardChart).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}", e.updateCard).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}/status", e.updateCardState).Methods("POST") - e.router.HandleFunc("/{projectId}/cards/{cardId}", e.deleteCard).Methods("DELETE") -} - -// CreateDashboardSchema TODO - refactor this to a separate file -type CreateDashboardSchema struct { - DashboardID int `json:"dashboard_id"` - Name string `json:"name"` - Description string `json:"description"` - IsPublic bool `json:"is_public"` - IsPinned bool `json:"is_pinned"` - Metrics []int `json:"metrics"` -} - type CurrentContext struct { UserID int `json:"user_id"` } -// createDashboard TODO - refactor this to a separate service -func (e *Router) createDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId, err := strconv.Atoi(vars["projectId"]) - if err != nil { - http.Error(w, "Invalid project ID", http.StatusBadRequest) - return - } - - fmt.Printf("Received projectId: %s\n", projectId) - - var data CreateDashboardSchema - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { - http.Error(w, "Invalid request payload", http.StatusBadRequest) - return - } - - context := e.getCurrentContext(r) - if context == nil { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - fmt.Printf("Received request to create dashboard: %+v\n", data) - - response := map[string]string{ - "message": "Dashboard created successfully", - } - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(response) - if err != nil { - return - } -} - func (e *Router) getCurrentContext(r *http.Request) *CurrentContext { // retrieving user info from headers or tokens return &CurrentContext{UserID: 1} } - -func (e *Router) getDashboards(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - fmt.Printf("Fetching dashboards for projectId: %s\n", projectId) - - dashboards := []CreateDashboardSchema{ - {DashboardID: 1, Name: "Dashboard 1", Description: "Description 1", IsPublic: true, IsPinned: false, Metrics: []int{1, 2, 3}}, - {DashboardID: 2, Name: "Dashboard 2", Description: "Description 2", IsPublic: false, IsPinned: true, Metrics: []int{4, 5, 6}}, - } - - w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(dashboards) - if err != nil { - return - } -} - -func (e *Router) getDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - fmt.Printf("Fetching dashboard for projectId: %s, dashboardId: %s\n", projectId, dashboardId) - - dashboard := CreateDashboardSchema{ - DashboardID: 1, - Name: "Dashboard 1", - Description: "Description 1", - IsPublic: true, - IsPinned: false, - Metrics: []int{1, 2, 3}, - } - - w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(dashboard) - if err != nil { - return - } -} - -func (e *Router) updateDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - fmt.Printf("Updating dashboard %s for project %s", dashboardId, projectId) - - var data CreateDashboardSchema - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { - http.Error(w, "Invalid request payload", http.StatusBadRequest) - return - } - - // Placeholder for updating logic - w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(map[string]string{"message": "Dashboard updated successfully"}) - if err != nil { - return - } -} - -func (e *Router) deleteDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - fmt.Printf("Deleting dashboard %s for project %s", dashboardId, projectId) - - // Placeholder for delete logic - w.WriteHeader(http.StatusNoContent) -} - -func (e *Router) pinDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - fmt.Printf("Pinning dashboard %s for project %s", dashboardId, projectId) - - // Placeholder for pinning logic - w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(map[string]string{"message": "Dashboard pinned successfully"}) - if err != nil { - return - } -} - -func (e *Router) addCardToDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - fmt.Printf("Adding card to dashboard %s for project %s\n", dashboardId, projectId) - - // Placeholder for adding card logic - w.WriteHeader(http.StatusCreated) - err := json.NewEncoder(w).Encode(map[string]string{"message": "Card added to dashboard successfully"}) - if err != nil { - return - } -} - -func (e *Router) createMetricAndAddToDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - fmt.Printf("Creating metric and adding to dashboard %s for project %s\n", dashboardId, projectId) - - // Placeholder for creating metric logic - w.WriteHeader(http.StatusCreated) - err := json.NewEncoder(w).Encode(map[string]string{"message": "Metric created and added to dashboard successfully"}) - if err != nil { - return - } -} - -func (e *Router) updateWidgetInDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - widgetId := vars["widgetId"] - fmt.Printf("Updating widget %s in dashboard %s for project %s\n", widgetId, dashboardId, projectId) - - // Placeholder for updating widget logic - w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(map[string]string{"message": "Widget updated successfully"}) - if err != nil { - return - } -} - -func (e *Router) removeWidgetFromDashboard(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - projectId := vars["projectId"] - dashboardId := vars["dashboardId"] - widgetId := vars["widgetId"] - fmt.Printf("Removing widget %s from dashboard %s for project %s\n", widgetId, dashboardId, projectId) - - // Placeholder for removing widget logic - w.WriteHeader(http.StatusNoContent) -} - -func (e *Router) tryCard(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusCreated) -} - -func (e *Router) tryCardSessions(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusCreated) -} - -func (e *Router) tryCardIssues(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusCreated) -} - -func (e *Router) getCards(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) createCard(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusCreated) -} - -func (e *Router) searchCards(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) getCard(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) getCardSessions(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) getCardFunnelIssues(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) getMetricFunnelIssueSessions(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) getCardErrorsList(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) getCardChart(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) updateCard(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) updateCardState(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (e *Router) deleteCard(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) -} diff --git a/backend/pkg/analytics/service/dashboard.go b/backend/pkg/analytics/service/dashboard.go index 43d5099a7..c4211be5f 100644 --- a/backend/pkg/analytics/service/dashboard.go +++ b/backend/pkg/analytics/service/dashboard.go @@ -1,44 +1,92 @@ -//package service -// -//import ( -// "encoding/json" -// "fmt" -// "github.com/gorilla/mux" -// "net/http" -//) -// -//type CreateDashboardSchema struct { -// DashboardID int `json:"dashboard_id"` -// Name string `json:"name"` -// Description string `json:"description"` -// IsPublic bool `json:"is_public"` -// IsPinned bool `json:"is_pinned"` -// Metrics []int `json:"metrics"` -//} -// -//type CurrentContext struct { -// UserID int `json:"user_id"` -//} -// -//func (e *Router) createDashboard(w http.ResponseWriter, r *http.Request) { -// vars := mux.Vars(r) -// projectId := vars["projectId"] -// fmt.Printf("Received projectId: %s\n", projectId) -// -// var data CreateDashboardSchema -// if err := json.NewDecoder(r.Body).Decode(&data); err != nil { -// http.Error(w, "Invalid request payload", http.StatusBadRequest) -// return -// } -// -// context := e.getCurrentContext(r) -// if context == nil { -// http.Error(w, "Unauthorized", http.StatusUnauthorized) -// return -// } -// -// data.DashboardID = 1 // Placeholder for dashboard ID generation logic -// -// w.Header().Set("Content-Type", "application/json") -// json.NewEncoder(w).Encode(data) -//} +package service + +import ( + "fmt" + "time" + + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/flakeid" + "openreplay/backend/pkg/logger" +) + +type dashboardsImpl struct { + flaker *flakeid.Flaker + log logger.Logger + pgconn pool.Pool +} + +type Dashboard struct { + DashboardID int `json:"dashboard_id"` + Name string `json:"name"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + IsPinned bool `json:"is_pinned"` + Metrics []int `json:"metrics"` +} + +type CurrentContext struct { + UserID int `json:"user_id"` +} + +type Dashboards interface { + Create(projectID int, dashboard *Dashboard) error + Get(projectID int, dashboardID int) (*Dashboard, error) + Update(projectID int, dashboardID int, dashboard *Dashboard) error + Delete(projectID int, dashboardID int) error +} + +func NewDashboards(log logger.Logger, pgconn pool.Pool, flaker *flakeid.Flaker) Dashboards { + return &dashboardsImpl{ + log: log, + pgconn: pgconn, + flaker: flaker, + } +} + +func (d *dashboardsImpl) Create(projectID int, dashboard *Dashboard) error { + switch { + case projectID == 0: + return fmt.Errorf("projectID is required") + case dashboard == nil: + return fmt.Errorf("dashboard is required") + } + + createdAt := time.Now() + dashboardID, err := d.flaker.Compose(uint64(createdAt.UnixMilli())) + if err != nil { + return err + } + newDashboard := &Dashboard{ + DashboardID: int(dashboardID), + Name: dashboard.Name, + Description: dashboard.Description, + IsPublic: dashboard.IsPublic, + IsPinned: dashboard.IsPinned, + Metrics: dashboard.Metrics, + } + + if err := d.add(newDashboard); err != nil { + return err + } + + return nil +} + +// Delete implements Dashboards. +func (d *dashboardsImpl) Delete(projectID int, dashboardID int) error { + panic("unimplemented") +} + +// Get implements Dashboards. +func (d *dashboardsImpl) Get(projectID int, dashboardID int) (*Dashboard, error) { + panic("unimplemented") +} + +// Update implements Dashboards. +func (d *dashboardsImpl) Update(projectID int, dashboardID int, dashboard *Dashboard) error { + panic("unimplemented") +} + +func (d *dashboardsImpl) add(dashboard *Dashboard) error { + panic("unimplemented") +}