From 4a10a0d74a58374949e364dfdb65a82a37b8a1bd Mon Sep 17 00:00:00 2001 From: deo002 Date: Thu, 13 Feb 2025 14:23:28 +0530 Subject: [PATCH] feat(dashboard): Dashboard api returning different analytics Signed-off-by: deo002 --- cmd/laas/docs/docs.go | 107 ++++++++++++++++++++++++++++++ cmd/laas/docs/swagger.json | 107 ++++++++++++++++++++++++++++++ cmd/laas/docs/swagger.yaml | 72 ++++++++++++++++++++ pkg/api/api.go | 8 +++ pkg/api/dashboard.go | 130 +++++++++++++++++++++++++++++++++++++ pkg/api/licenses.go | 1 + pkg/models/types.go | 24 +++++++ 7 files changed, 449 insertions(+) create mode 100644 pkg/api/dashboard.go diff --git a/cmd/laas/docs/docs.go b/cmd/laas/docs/docs.go index cd115dd..908d6ca 100644 --- a/cmd/laas/docs/docs.go +++ b/cmd/laas/docs/docs.go @@ -269,6 +269,42 @@ const docTemplate = `{ } } }, + "/dashboard": { + "get": { + "security": [ + { + "ApiKeyAuth": [], + "{}": [] + } + ], + "description": "Fetches data to be displayed on the dashboard", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Fetches data to be displayed on the dashboard", + "operationId": "GetDashboardData", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.DashboardResponse" + } + }, + "500": { + "description": "Something went wrong", + "schema": { + "$ref": "#/definitions/models.LicenseError" + } + } + } + } + }, "/health": { "get": { "description": "Check health of the service", @@ -2298,6 +2334,19 @@ const docTemplate = `{ } } }, + "models.CategoryObligationCount": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "GENERAL" + }, + "count": { + "type": "integer", + "example": 6 + } + } + }, "models.ChangeLog": { "type": "object", "properties": { @@ -2341,6 +2390,51 @@ const docTemplate = `{ } } }, + "models.Dashboard": { + "type": "object", + "properties": { + "category_obligation_frequency": { + "type": "array", + "items": { + "$ref": "#/definitions/models.CategoryObligationCount" + } + }, + "licenses_count": { + "type": "integer", + "example": 2 + }, + "monthly_license_changes_count": { + "type": "integer", + "example": 6 + }, + "obligations_count": { + "type": "integer", + "example": 7 + }, + "risk_license_frequency": { + "type": "array", + "items": { + "$ref": "#/definitions/models.RiskLicenseCount" + } + }, + "users_count": { + "type": "integer", + "example": 5 + } + } + }, + "models.DashboardResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Dashboard" + }, + "status": { + "type": "integer", + "example": 200 + } + } + }, "models.ImportLicensesResponse": { "type": "object", "properties": { @@ -3050,6 +3144,19 @@ const docTemplate = `{ } } }, + "models.RiskLicenseCount": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "example": 6 + }, + "risk": { + "type": "integer", + "example": 2 + } + } + }, "models.SearchLicense": { "type": "object", "required": [ diff --git a/cmd/laas/docs/swagger.json b/cmd/laas/docs/swagger.json index b74c5eb..c81e61a 100644 --- a/cmd/laas/docs/swagger.json +++ b/cmd/laas/docs/swagger.json @@ -262,6 +262,42 @@ } } }, + "/dashboard": { + "get": { + "security": [ + { + "ApiKeyAuth": [], + "{}": [] + } + ], + "description": "Fetches data to be displayed on the dashboard", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Fetches data to be displayed on the dashboard", + "operationId": "GetDashboardData", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.DashboardResponse" + } + }, + "500": { + "description": "Something went wrong", + "schema": { + "$ref": "#/definitions/models.LicenseError" + } + } + } + } + }, "/health": { "get": { "description": "Check health of the service", @@ -2291,6 +2327,19 @@ } } }, + "models.CategoryObligationCount": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "GENERAL" + }, + "count": { + "type": "integer", + "example": 6 + } + } + }, "models.ChangeLog": { "type": "object", "properties": { @@ -2334,6 +2383,51 @@ } } }, + "models.Dashboard": { + "type": "object", + "properties": { + "category_obligation_frequency": { + "type": "array", + "items": { + "$ref": "#/definitions/models.CategoryObligationCount" + } + }, + "licenses_count": { + "type": "integer", + "example": 2 + }, + "monthly_license_changes_count": { + "type": "integer", + "example": 6 + }, + "obligations_count": { + "type": "integer", + "example": 7 + }, + "risk_license_frequency": { + "type": "array", + "items": { + "$ref": "#/definitions/models.RiskLicenseCount" + } + }, + "users_count": { + "type": "integer", + "example": 5 + } + } + }, + "models.DashboardResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Dashboard" + }, + "status": { + "type": "integer", + "example": 200 + } + } + }, "models.ImportLicensesResponse": { "type": "object", "properties": { @@ -3043,6 +3137,19 @@ } } }, + "models.RiskLicenseCount": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "example": 6 + }, + "risk": { + "type": "integer", + "example": 2 + } + } + }, "models.SearchLicense": { "type": "object", "required": [ diff --git a/cmd/laas/docs/swagger.yaml b/cmd/laas/docs/swagger.yaml index 4a4e22b..c600053 100644 --- a/cmd/laas/docs/swagger.yaml +++ b/cmd/laas/docs/swagger.yaml @@ -54,6 +54,15 @@ definitions: example: 200 type: integer type: object + models.CategoryObligationCount: + properties: + category: + example: GENERAL + type: string + count: + example: 6 + type: integer + type: object models.ChangeLog: properties: audit_id: @@ -84,6 +93,37 @@ definitions: example: 200 type: integer type: object + models.Dashboard: + properties: + category_obligation_frequency: + items: + $ref: '#/definitions/models.CategoryObligationCount' + type: array + licenses_count: + example: 2 + type: integer + monthly_license_changes_count: + example: 6 + type: integer + obligations_count: + example: 7 + type: integer + risk_license_frequency: + items: + $ref: '#/definitions/models.RiskLicenseCount' + type: array + users_count: + example: 5 + type: integer + type: object + models.DashboardResponse: + properties: + data: + $ref: '#/definitions/models.Dashboard' + status: + example: 200 + type: integer + type: object models.ImportLicensesResponse: properties: data: @@ -586,6 +626,15 @@ definitions: user_password: type: string type: object + models.RiskLicenseCount: + properties: + count: + example: 6 + type: integer + risk: + example: 2 + type: integer + type: object models.SearchLicense: properties: field: @@ -877,6 +926,29 @@ paths: summary: Get a changelog tags: - Audits + /dashboard: + get: + consumes: + - application/json + description: Fetches data to be displayed on the dashboard + operationId: GetDashboardData + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.DashboardResponse' + "500": + description: Something went wrong + schema: + $ref: '#/definitions/models.LicenseError' + security: + - '{}': [] + ApiKeyAuth: [] + summary: Fetches data to be displayed on the dashboard + tags: + - Dashboard /health: get: consumes: diff --git a/pkg/api/api.go b/pkg/api/api.go index 1ea1190..52ec7b7 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -162,6 +162,10 @@ func Router() *gin.Engine { audit.GET(":audit_id/changes", GetChangeLogs) audit.GET(":audit_id/changes/:id", GetChangeLogbyId) } + dashboard := authorizedv1.Group("/dashboard") + { + dashboard.GET("", GetDashboardData) + } } } else { unAuthorizedv1 := r.Group("/api/v1") @@ -213,6 +217,10 @@ func Router() *gin.Engine { { oidc.POST("", auth.CreateOidcUser) } + dashboard := unAuthorizedv1.Group("/dashboard") + { + dashboard.GET("", GetDashboardData) + } } authorizedv1 := r.Group("/api/v1") diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go new file mode 100644 index 0000000..87edf90 --- /dev/null +++ b/pkg/api/dashboard.go @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2025 Siemens AG +// SPDX-FileContributor: Dearsh Oberoi +// +// SPDX-License-Identifier: GPL-2.0-only + +package api + +import ( + "log" + "net/http" + "time" + + "github.com/fossology/LicenseDb/pkg/db" + "github.com/fossology/LicenseDb/pkg/models" + "github.com/gin-gonic/gin" +) + +// GetDashboardData fetches data to be displayed on the dashboard +// +// @Summary Fetches data to be displayed on the dashboard +// @Description Fetches data to be displayed on the dashboard +// @Id GetDashboardData +// @Tags Dashboard +// @Accept json +// @Produce json +// @Success 200 {object} models.DashboardResponse +// @Failure 500 {object} models.LicenseError "Something went wrong" +// @Security ApiKeyAuth || {} +// @Router /dashboard [get] +func GetDashboardData(c *gin.Context) { + var licensesCount, obligationsCount, usersCount, licenseChangesSinceLastMonth int64 + var licenseFrequency []models.RiskLicenseCount + var categoryFrequency []models.CategoryObligationCount + + var active = true + if err := db.DB.Model(&models.LicenseDB{}).Where(&models.LicenseDB{Active: &active}).Count(&licensesCount).Error; err != nil { + log.Printf("\033[31mError: error fetching licenses count: %s\033[0m", err.Error()) + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "something went wrong", + Error: "error fetching licenses count", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + + if err := db.DB.Model(&models.Obligation{}).Where(&models.Obligation{Active: &active}).Count(&obligationsCount).Error; err != nil { + log.Printf("\033[31mError: error fetching obligations count: %s\033[0m", err.Error()) + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "something went wrong", + Error: "error fetching obligations count", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + + if err := db.DB.Model(&models.User{}).Where(&models.User{Active: &active}).Count(&usersCount).Error; err != nil { + log.Printf("\033[31mError: error fetching users count: %s\033[0m", err.Error()) + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "something went wrong", + Error: "error fetching users count", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + + now := time.Now() + startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) + iso8601StartOfMonth := startOfMonth.Format(time.RFC3339) + if err := db.DB.Model(&models.Audit{}).Where("timestamp > ? AND type='license'", iso8601StartOfMonth).Count(&licenseChangesSinceLastMonth).Error; err != nil { + log.Printf("\033[31mError: error fetching audits count: %s\033[0m", err.Error()) + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "something went wrong", + Error: "error fetching audits count", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + + if err := db.DB.Model(&models.LicenseDB{}).Select("rf_risk as risk, count(*) as count").Group("rf_risk").Scan(&licenseFrequency).Error; err != nil { + log.Printf("\033[31mError: error fetching risk license frequencies: %s\033[0m", err.Error()) + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "something went wrong", + Error: "error fetching risk liicense frequencies", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + + if err := db.DB.Model(&models.Obligation{}).Select("category, count(*) as count").Group("category").Scan(&categoryFrequency).Error; err != nil { + log.Printf("\033[31mError: error fetching category obligation frequencies: %s\033[0m", err.Error()) + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "something went wrong", + Error: "error fetching category obligation frequencies", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + + res := models.DashboardResponse{ + Data: models.Dashboard{ + LicensesCount: licensesCount, + ObligationsCount: obligationsCount, + LicenseChangesSinceLastMonth: licenseChangesSinceLastMonth, + UsersCount: usersCount, + RiskLicenseFrequency: licenseFrequency, + CategoryObligationFrequency: categoryFrequency, + }, + Status: http.StatusOK, + } + + c.JSON(http.StatusOK, res) +} diff --git a/pkg/api/licenses.go b/pkg/api/licenses.go index 15abbe3..0af594c 100644 --- a/pkg/api/licenses.go +++ b/pkg/api/licenses.go @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Kavya Shukla // SPDX-FileCopyrightText: 2023 Siemens AG // SPDX-FileContributor: Gaurav Mishra +// SPDX-FileContributor: Dearsh Oberoi // // SPDX-License-Identifier: GPL-2.0-only diff --git a/pkg/models/types.go b/pkg/models/types.go index 8e8156e..4669e89 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -790,3 +790,27 @@ type SwaggerDocAPISecurityScheme struct { OperationId string `json:"operationId" example:"GetLicense"` } `json:"paths"` } + +type RiskLicenseCount struct { + Risk int64 `json:"risk" example:"2"` + Count int64 `json:"count" example:"6"` +} + +type CategoryObligationCount struct { + Category string `json:"category" example:"GENERAL"` + Count int64 `json:"count" example:"6"` +} + +type Dashboard struct { + LicensesCount int64 `json:"licenses_count" example:"2"` + ObligationsCount int64 `json:"obligations_count" example:"7"` + UsersCount int64 `json:"users_count" example:"5"` + LicenseChangesSinceLastMonth int64 `json:"monthly_license_changes_count" example:"6"` + RiskLicenseFrequency []RiskLicenseCount `json:"risk_license_frequency"` + CategoryObligationFrequency []CategoryObligationCount `json:"category_obligation_frequency"` +} + +type DashboardResponse struct { + Status int `json:"status" example:"200"` + Data Dashboard `json:"data"` +}