feat(analytics): dashboard manage cards (#2893)
This commit is contained in:
parent
99ddcd9708
commit
9d82c2935a
7 changed files with 260 additions and 51 deletions
|
|
@ -192,12 +192,6 @@ func (e *handlersImpl) pinDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
startTime := time.Now()
|
||||
bodySize := 0
|
||||
|
||||
//id, err := getDashboardId(r)
|
||||
//if err != nil {
|
||||
// e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
// return
|
||||
//}
|
||||
|
||||
e.log.Info(r.Context(), "Dashboard pinned")
|
||||
|
||||
e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize)
|
||||
|
|
@ -208,13 +202,52 @@ func (e *handlersImpl) addCardToDashboard(w http.ResponseWriter, r *http.Request
|
|||
startTime := time.Now()
|
||||
bodySize := 0
|
||||
|
||||
//id, err := getDashboardId(r)
|
||||
//if err != nil {
|
||||
// e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
// return
|
||||
//}
|
||||
projectID, err := getIDFromRequest(r, "projectId")
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
e.log.Info(r.Context(), "Card added to dashboard")
|
||||
dashboardID, err := getIDFromRequest(r, "id")
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
u := r.Context().Value("userData").(*user.User)
|
||||
|
||||
bodyBytes, err := api.ReadBody(e.log, w, r, e.jsonSizeLimit)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &models.AddCardToDashboardRequest{}
|
||||
if err := json.Unmarshal(bodyBytes, req); err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
err = validate.Struct(req)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
err = e.service.AddCardsToDashboard(projectID, dashboardID, u.ID, req)
|
||||
if err != nil {
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
} else if err.Error() == "access_denied: user does not have access" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize)
|
||||
} else {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
|
|
@ -224,11 +257,41 @@ func (e *handlersImpl) removeCardFromDashboard(w http.ResponseWriter, r *http.Re
|
|||
startTime := time.Now()
|
||||
bodySize := 0
|
||||
|
||||
//id, err := getDashboardId(r)
|
||||
//if err != nil {
|
||||
// e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
// return
|
||||
//}
|
||||
projectID, err := getIDFromRequest(r, "projectId")
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
dashboardID, err := getIDFromRequest(r, "id")
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
cardID, err := getIDFromRequest(r, "cardId")
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
u := r.Context().Value("userData").(*user.User)
|
||||
_, err = e.service.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
} else if err.Error() == "access_denied: user does not have access" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize)
|
||||
} else {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
}
|
||||
|
||||
err = e.service.DeleteCardFromDashboard(dashboardID, cardID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
e.responser.ResponseOK(e.log, r.Context(), w, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ func (e *handlersImpl) GetAll() []*api.Description {
|
|||
{"/v1/analytics/{projectId}/dashboards/{id}", e.getDashboard, "GET"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.updateDashboard, "PUT"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.deleteDashboard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}/cards", e.addCardToDashboard, "POST"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}/cards/{cardId}", e.removeCardFromDashboard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/cards", e.createCard, "POST"},
|
||||
{"/v1/analytics/{projectId}/cards", e.getCardsPaginated, "GET"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.getCard, "GET"},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ type CardBase struct {
|
|||
Name string `json:"name" validate:"required"`
|
||||
IsPublic bool `json:"isPublic" validate:"omitempty"`
|
||||
DefaultConfig map[string]any `json:"defaultConfig"`
|
||||
Config map[string]any `json:"config"`
|
||||
Thumbnail *string `json:"thumbnail" validate:"omitempty,url"`
|
||||
MetricType string `json:"metricType" validate:"required,oneof=timeseries table funnel"`
|
||||
MetricOf string `json:"metricOf" validate:"required,oneof=session_count user_count"`
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package models
|
||||
|
||||
type Dashboard struct {
|
||||
DashboardID int `json:"dashboardId"`
|
||||
ProjectID int `json:"projectId"`
|
||||
UserID int `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
IsPinned bool `json:"isPinned"`
|
||||
OwnerEmail string `json:"ownerEmail"`
|
||||
OwnerName string `json:"ownerName"`
|
||||
DashboardID int `json:"dashboardId"`
|
||||
ProjectID int `json:"projectId"`
|
||||
UserID int `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
IsPinned bool `json:"isPinned"`
|
||||
OwnerEmail string `json:"ownerEmail"`
|
||||
OwnerName string `json:"ownerName"`
|
||||
Metrics []CardBase `json:"cards"`
|
||||
}
|
||||
|
||||
type CreateDashboardResponse struct {
|
||||
|
|
@ -61,9 +62,6 @@ type PinDashboardRequest struct {
|
|||
}
|
||||
|
||||
type AddCardToDashboardRequest struct {
|
||||
CardIDs []int `json:"card_ids"`
|
||||
}
|
||||
|
||||
type DeleteCardFromDashboardRequest struct {
|
||||
CardIDs []int `json:"card_ids"`
|
||||
MetricIDs []int `json:"metric_ids" validate:"required,min=1,dive,gt=0"`
|
||||
Config map[string]interface{} `json:"config"` // Optional
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ type Service interface {
|
|||
CreateDashboard(projectId int, userId uint64, req *models.CreateDashboardRequest) (*models.GetDashboardResponse, error)
|
||||
UpdateDashboard(projectId int, dashboardId int, userId uint64, req *models.UpdateDashboardRequest) (*models.GetDashboardResponse, error)
|
||||
DeleteDashboard(projectId int, dashboardId int, userId uint64) error
|
||||
AddCardsToDashboard(projectId int, dashboardId int, userId uint64, req *models.AddCardToDashboardRequest) error
|
||||
DeleteCardFromDashboard(dashboardId int, cardId int) error
|
||||
GetCard(projectId int, cardId int) (*models.CardGetResponse, error)
|
||||
GetCardWithSeries(projectId int, cardId int) (*models.CardGetResponse, error)
|
||||
GetCards(projectId int) (*models.GetCardsResponse, error)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func (s serviceImpl) CreateCard(projectId int, userID uint64, req *models.CardCreateRequest) (*models.CardGetResponse, error) {
|
||||
func (s *serviceImpl) CreateCard(projectId int, userID uint64, req *models.CardCreateRequest) (*models.CardGetResponse, error) {
|
||||
if req.MetricValue == nil {
|
||||
req.MetricValue = []string{}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func (s serviceImpl) CreateCard(projectId int, userID uint64, req *models.CardCr
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int64, series []models.CardSeriesBase) []models.CardSeries {
|
||||
func (s *serviceImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int64, series []models.CardSeriesBase) []models.CardSeries {
|
||||
if len(series) == 0 {
|
||||
return nil // No series to create
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ func (s serviceImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int64
|
|||
return seriesList
|
||||
}
|
||||
|
||||
func (s serviceImpl) GetCard(projectId int, cardID int) (*models.CardGetResponse, error) {
|
||||
func (s *serviceImpl) GetCard(projectId int, cardID int) (*models.CardGetResponse, error) {
|
||||
sql :=
|
||||
`SELECT metric_id, project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format, is_public, created_at, edited_at
|
||||
FROM public.metrics
|
||||
|
|
@ -145,7 +145,7 @@ func (s serviceImpl) GetCard(projectId int, cardID int) (*models.CardGetResponse
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) GetCardWithSeries(projectId int, cardID int) (*models.CardGetResponse, error) {
|
||||
func (s *serviceImpl) GetCardWithSeries(projectId int, cardID int) (*models.CardGetResponse, error) {
|
||||
sql := `
|
||||
SELECT m.metric_id, m.project_id, m.user_id, m.name, m.metric_type, m.view_type, m.metric_of,
|
||||
m.metric_value, m.metric_format, m.is_public, m.created_at, m.edited_at,
|
||||
|
|
@ -184,7 +184,7 @@ func (s serviceImpl) GetCardWithSeries(projectId int, cardID int) (*models.CardG
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) GetCards(projectId int) (*models.GetCardsResponse, error) {
|
||||
func (s *serviceImpl) GetCards(projectId int) (*models.GetCardsResponse, error) {
|
||||
sql := `
|
||||
SELECT metric_id, project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format, is_public, created_at, edited_at
|
||||
FROM public.metrics
|
||||
|
|
@ -211,7 +211,7 @@ func (s serviceImpl) GetCards(projectId int) (*models.GetCardsResponse, error) {
|
|||
return &models.GetCardsResponse{Cards: cards}, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) GetCardsPaginated(
|
||||
func (s *serviceImpl) GetCardsPaginated(
|
||||
projectId int,
|
||||
filters models.CardListFilter,
|
||||
sort models.CardListSort,
|
||||
|
|
@ -321,7 +321,7 @@ func (s serviceImpl) GetCardsPaginated(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) UpdateCard(projectId int, cardID int64, userID uint64, req *models.CardUpdateRequest) (*models.CardGetResponse, error) {
|
||||
func (s *serviceImpl) UpdateCard(projectId int, cardID int64, userID uint64, req *models.CardUpdateRequest) (*models.CardGetResponse, error) {
|
||||
if req.MetricValue == nil {
|
||||
req.MetricValue = []string{}
|
||||
}
|
||||
|
|
@ -380,7 +380,7 @@ func (s serviceImpl) UpdateCard(projectId int, cardID int64, userID uint64, req
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) DeleteCardSeries(cardId int64) error {
|
||||
func (s *serviceImpl) DeleteCardSeries(cardId int64) error {
|
||||
sql := `DELETE FROM public.metric_series WHERE metric_id = $1`
|
||||
err := s.pgconn.Exec(sql, cardId)
|
||||
if err != nil {
|
||||
|
|
@ -389,7 +389,7 @@ func (s serviceImpl) DeleteCardSeries(cardId int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) DeleteCard(projectId int, cardID int64, userID uint64) error {
|
||||
func (s *serviceImpl) DeleteCard(projectId int, cardID int64, userID uint64) error {
|
||||
sql := `
|
||||
UPDATE public.metrics
|
||||
SET deleted_at = now()
|
||||
|
|
@ -402,7 +402,7 @@ func (s serviceImpl) DeleteCard(projectId int, cardID int64, userID uint64) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) GetCardChartData(projectId int, userID uint64, req *models.GetCardChartDataRequest) ([]models.DataPoint, error) {
|
||||
func (s *serviceImpl) GetCardChartData(projectId int, userID uint64, req *models.GetCardChartDataRequest) ([]models.DataPoint, error) {
|
||||
jsonInput := `
|
||||
{
|
||||
"data": [
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
)
|
||||
|
||||
// CreateDashboard Create a new dashboard
|
||||
func (s serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.CreateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
func (s *serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.CreateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
sql := `
|
||||
INSERT INTO dashboards (project_id, user_id, name, description, is_public, is_pinned)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
|
|
@ -30,14 +32,53 @@ func (s serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.C
|
|||
}
|
||||
|
||||
// GetDashboard Fetch a specific dashboard by ID
|
||||
func (s serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64) (*models.GetDashboardResponse, error) {
|
||||
func (s *serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64) (*models.GetDashboardResponse, error) {
|
||||
sql := `
|
||||
SELECT dashboard_id, project_id, name, description, is_public, is_pinned, user_id
|
||||
FROM dashboards
|
||||
WHERE dashboard_id = $1 AND project_id = $2 AND deleted_at IS NULL`
|
||||
WITH series_agg AS (
|
||||
SELECT
|
||||
ms.metric_id,
|
||||
json_agg(
|
||||
json_build_object(
|
||||
'index', ms.index,
|
||||
'name', ms.name,
|
||||
'filter', ms.filter
|
||||
)
|
||||
) AS series
|
||||
FROM metric_series ms
|
||||
GROUP BY ms.metric_id
|
||||
)
|
||||
SELECT
|
||||
d.dashboard_id,
|
||||
d.project_id,
|
||||
d.name,
|
||||
d.description,
|
||||
d.is_public,
|
||||
d.is_pinned,
|
||||
d.user_id,
|
||||
COALESCE(json_agg(
|
||||
json_build_object(
|
||||
'config', dw.config,
|
||||
'metric_id', m.metric_id,
|
||||
'name', m.name,
|
||||
'metric_type', m.metric_type,
|
||||
'view_type', m.view_type,
|
||||
'metric_of', m.metric_of,
|
||||
'metric_value', m.metric_value,
|
||||
'metric_format', m.metric_format,
|
||||
'series', s.series
|
||||
)
|
||||
) FILTER (WHERE m.metric_id IS NOT NULL), '[]') AS metrics
|
||||
FROM dashboards d
|
||||
LEFT JOIN dashboard_widgets dw ON d.dashboard_id = dw.dashboard_id
|
||||
LEFT JOIN metrics m ON dw.metric_id = m.metric_id
|
||||
LEFT JOIN series_agg s ON m.metric_id = s.metric_id
|
||||
WHERE d.dashboard_id = $1 AND d.project_id = $2 AND d.deleted_at IS NULL
|
||||
GROUP BY d.dashboard_id, d.project_id, d.name, d.description, d.is_public, d.is_pinned, d.user_id`
|
||||
|
||||
dashboard := &models.GetDashboardResponse{}
|
||||
var ownerID int
|
||||
var metricsJSON []byte
|
||||
|
||||
err := s.pgconn.QueryRow(sql, dashboardID, projectId).Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.ProjectID,
|
||||
|
|
@ -46,6 +87,7 @@ func (s serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64)
|
|||
&dashboard.IsPublic,
|
||||
&dashboard.IsPinned,
|
||||
&ownerID,
|
||||
&metricsJSON,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -55,7 +97,10 @@ func (s serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64)
|
|||
return nil, fmt.Errorf("error fetching dashboard: %w", err)
|
||||
}
|
||||
|
||||
// Access control
|
||||
if err := json.Unmarshal(metricsJSON, &dashboard.Metrics); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling metrics: %w", err)
|
||||
}
|
||||
|
||||
if !dashboard.IsPublic && uint64(ownerID) != userID {
|
||||
return nil, fmt.Errorf("access_denied: user does not have access")
|
||||
}
|
||||
|
|
@ -63,7 +108,7 @@ func (s serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64)
|
|||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (s serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDashboardsResponse, error) {
|
||||
func (s *serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDashboardsResponse, error) {
|
||||
sql := `
|
||||
SELECT d.dashboard_id, d.user_id, d.project_id, d.name, d.description, d.is_public, d.is_pinned, u.email AS owner_email, u.name AS owner_name
|
||||
FROM dashboards d
|
||||
|
|
@ -98,7 +143,7 @@ func (s serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDas
|
|||
}
|
||||
|
||||
// GetDashboardsPaginated Fetch dashboards with pagination
|
||||
func (s serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *models.GetDashboardsRequest) (*models.GetDashboardsResponsePaginated, error) {
|
||||
func (s *serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *models.GetDashboardsRequest) (*models.GetDashboardsResponsePaginated, error) {
|
||||
baseSQL, args := buildBaseQuery(projectId, userID, req)
|
||||
|
||||
// Count total dashboards
|
||||
|
|
@ -147,7 +192,7 @@ func (s serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *m
|
|||
}
|
||||
|
||||
// UpdateDashboard Update a dashboard
|
||||
func (s serviceImpl) UpdateDashboard(projectId int, dashboardID int, userID uint64, req *models.UpdateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
func (s *serviceImpl) UpdateDashboard(projectId int, dashboardID int, userID uint64, req *models.UpdateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
sql := `
|
||||
UPDATE dashboards
|
||||
SET name = $1, description = $2, is_public = $3, is_pinned = $4
|
||||
|
|
@ -171,7 +216,7 @@ func (s serviceImpl) UpdateDashboard(projectId int, dashboardID int, userID uint
|
|||
}
|
||||
|
||||
// DeleteDashboard Soft-delete a dashboard
|
||||
func (s serviceImpl) DeleteDashboard(projectId int, dashboardID int, userID uint64) error {
|
||||
func (s *serviceImpl) DeleteDashboard(projectId int, dashboardID int, userID uint64) error {
|
||||
sql := `
|
||||
UPDATE dashboards
|
||||
SET deleted_at = now()
|
||||
|
|
@ -236,3 +281,101 @@ func getOrder(order string) string {
|
|||
}
|
||||
return "ASC"
|
||||
}
|
||||
|
||||
func (s *serviceImpl) CardsExist(projectId int, cardIDs []int) (bool, error) {
|
||||
sql := `
|
||||
SELECT COUNT(*) FROM public.metrics
|
||||
WHERE project_id = $1 AND metric_id = ANY($2)
|
||||
`
|
||||
var count int
|
||||
err := s.pgconn.QueryRow(sql, projectId, cardIDs).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count == len(cardIDs), nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) AddCardsToDashboard(projectId int, dashboardId int, userId uint64, req *models.AddCardToDashboardRequest) error {
|
||||
_, err := s.GetDashboard(projectId, dashboardId, userId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get dashboard: %w", err)
|
||||
}
|
||||
|
||||
// Check if all cards exist
|
||||
exists, err := s.CardsExist(projectId, req.MetricIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check card existence: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return errors.New("not_found: one or more cards do not exist")
|
||||
}
|
||||
|
||||
// Begin a transaction
|
||||
tx, err := s.pgconn.Begin() // Start transaction
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := tx.Commit(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert metrics into dashboard_widgets
|
||||
insertedWidgets := 0
|
||||
for _, metricID := range req.MetricIDs {
|
||||
// Check if the widget already exists
|
||||
var exists bool
|
||||
err := tx.QueryRow(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM public.dashboard_widgets
|
||||
WHERE dashboard_id = $1 AND metric_id = $2
|
||||
)
|
||||
`, dashboardId, metricID).Scan(&exists)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check existing widget: %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
continue // Skip duplicates
|
||||
}
|
||||
|
||||
// Insert new widget
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO public.dashboard_widgets (dashboard_id, metric_id, user_id, config)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, dashboardId, metricID, userId, req.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert widget: %w", err)
|
||||
}
|
||||
insertedWidgets++
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) DeleteCardFromDashboard(dashboardId int, cardId int) error {
|
||||
sql := `DELETE FROM public.dashboard_widgets WHERE dashboard_id = $1 AND metric_id = $2`
|
||||
err := s.pgconn.Exec(sql, dashboardId, cardId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete card from dashboard: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue