renaming visualization to dashboard

This commit is contained in:
Sonja Happ 2019-07-18 13:11:05 +02:00
parent c565c04ba9
commit c33438b395
20 changed files with 550 additions and 1947 deletions

View file

@ -105,10 +105,10 @@ test:signal:
variables:
TEST_FOLDER: routes/signal
test:visualization:
test:dashboard:
extends: test:simulation
variables:
TEST_FOLDER: routes/visualization
TEST_FOLDER: routes/dashboard
test:widget:
extends: test:simulation

View file

@ -62,7 +62,7 @@ func DropTables(db *gorm.DB) {
db.DropTableIfExists(&File{})
db.DropTableIfExists(&Simulation{})
db.DropTableIfExists(&User{})
db.DropTableIfExists(&Visualization{})
db.DropTableIfExists(&Dashboard{})
db.DropTableIfExists(&Widget{})
}
@ -74,7 +74,7 @@ func MigrateModels(db *gorm.DB) {
db.AutoMigrate(&File{})
db.AutoMigrate(&Simulation{})
db.AutoMigrate(&User{})
db.AutoMigrate(&Visualization{})
db.AutoMigrate(&Dashboard{})
db.AutoMigrate(&Widget{})
}
@ -154,10 +154,10 @@ func DummyPopulateDB(test_db *gorm.DB) {
checkErr(test_db.Create(&usr_A).Error)
checkErr(test_db.Create(&usr_B).Error)
vis_A := Visualization{Name: "Visualization_A"}
vis_B := Visualization{Name: "Visualization_B"}
checkErr(test_db.Create(&vis_A).Error)
checkErr(test_db.Create(&vis_B).Error)
dab_A := Dashboard{Name: "Dashboard_A"}
dab_B := Dashboard{Name: "Dashboard_B"}
checkErr(test_db.Create(&dab_A).Error)
checkErr(test_db.Create(&dab_B).Error)
widg_A := Widget{Name: "Widget_A"}
widg_B := Widget{Name: "Widget_B"}
@ -178,13 +178,13 @@ func DummyPopulateDB(test_db *gorm.DB) {
checkErr(test_db.Model(&simn_A).Association("SimulationModels").Append(&mo_A).Error)
checkErr(test_db.Model(&simn_A).Association("SimulationModels").Append(&mo_B).Error)
// Simulation HM Visualizations
checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_A).Error)
checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_B).Error)
// Simulation HM Dashboards
checkErr(test_db.Model(&simn_A).Association("Dashboards").Append(&dab_A).Error)
checkErr(test_db.Model(&simn_A).Association("Dashboards").Append(&dab_B).Error)
// Visualization HM Widget
checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_A).Error)
checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_B).Error)
// Dashboard HM Widget
checkErr(test_db.Model(&dab_A).Association("Widgets").Append(&widg_A).Error)
checkErr(test_db.Model(&dab_A).Association("Widgets").Append(&widg_B).Error)
// SimulationModel HM Signals
checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_A).Error)

View file

@ -34,7 +34,7 @@ func TestDummyDBAssociations(t *testing.T) {
var simn Simulation
var usr User
var usrs []User
var vis Visualization
var dab Dashboard
var widg Widget
var sigs []Signal
@ -42,7 +42,7 @@ func TestDummyDBAssociations(t *testing.T) {
var files []File
var files_sm []File
var simns []Simulation
var viss []Visualization
var dabs []Dashboard
var widgs []Widget
// User
@ -77,10 +77,10 @@ func TestDummyDBAssociations(t *testing.T) {
"Expected to have %v simulation models. Has %v.", 2, len(mos))
}
a.NoError(db.Model(&simn).Related(&viss, "Visualizations").Error)
if len(viss) != 2 {
a.NoError(db.Model(&simn).Related(&dabs, "Dashboards").Error)
if len(dabs) != 2 {
a.Fail("Simulation Associations",
"Expected to have %v Visualizations. Has %v.", 2, len(viss))
"Expected to have %v Dashboards. Has %v.", 2, len(dabs))
}
// Simulator
@ -115,14 +115,14 @@ func TestDummyDBAssociations(t *testing.T) {
fmt.Println("SimulatorID: ", mo.SimulatorID)
// Visualization
// Dashboard
a.NoError(db.Find(&vis, 1).Error, fM("Visualization"))
a.EqualValues("Visualization_A", vis.Name)
a.NoError(db.Find(&dab, 1).Error, fM("Dashboard"))
a.EqualValues("Dashboard_A", dab.Name)
// Visualization Associations
// Dashboard Associations
a.NoError(db.Model(&vis).Related(&widgs, "Widgets").Error)
a.NoError(db.Model(&dab).Related(&widgs, "Widgets").Error)
if len(widgs) != 2 {
a.Fail("Widget Associations",
"Expected to have %v Widget. Has %v.", 2, len(widgs))

View file

@ -30,8 +30,8 @@ type Simulation struct {
Users []*User `gorm:"not null;many2many:user_simulations"`
// SimulationModels that belong to the simulation
SimulationModels []SimulationModel `gorm:"foreignkey:SimulationID"`
// Visualizations that belong to the simulation
Visualizations []Visualization `gorm:"foreignkey:SimulationID"`
// Dashboards that belong to the simulation
Dashboards []Dashboard `gorm:"foreignkey:SimulationID"`
}
// SimulationModel data model
@ -98,18 +98,18 @@ type Simulator struct {
SimulationModels []SimulationModel `gorm:"foreignkey:SimulatorID"`
}
// Visualization data model
type Visualization struct {
// ID of visualization
// Dashboard data model
type Dashboard struct {
// ID of dashboard
ID uint `gorm:"primary_key;auto_increment"`
// Name of visualization
// Name of dashboard
Name string `gorm:"not null"`
// Grid of visualization
// Grid of dashboard
Grid int `gorm:"default:15"`
// ID of simulation to which visualization belongs
// ID of simulation to which dashboard belongs
SimulationID uint
// Widgets that belong to visualization
Widgets []Widget `gorm:"foreignkey:VisualizationID"`
// Widgets that belong to dashboard
Widgets []Widget `gorm:"foreignkey:DashboardID"`
}
// Widget data model
@ -138,8 +138,8 @@ type Widget struct {
IsLocked bool `gorm:"default:false"`
// Custom properties of widget as JSON string
CustomProperties string
// ID of visualization to which widget belongs
VisualizationID uint
// ID of dashboard to which widget belongs
DashboardID uint
// Files that belong to widget (for example images)
Files []File `gorm:"foreignkey:WidgetID"`
}

View file

@ -36,7 +36,7 @@ type SimulatorResponse struct {
RawProperties string `json:"RawProperties"`
}
type VisualizationResponse struct {
type DashboardResponse struct {
ID uint `json:"ID"`
Name string `json:"Name"`
Grid int `json:"Grid"`
@ -54,7 +54,7 @@ type WidgetResponse struct {
X int `json:"X"`
Y int `json:"Y"`
Z int `json:"Z"`
VisualizationID uint `json:"VisualizationID"`
DashboardID uint `json:"DashboardID"`
IsLocked bool `json:"IsLocked"`
CustomProperties string `json:"CustomProperties"`
}
@ -117,12 +117,12 @@ type ResponseMsgSignal struct {
Signal SignalResponse `json:"signal"`
}
type ResponseMsgVisualizations struct {
Visualizations []VisualizationResponse `json:"visualizations"`
type ResponseMsgDashboards struct {
Dashboards []DashboardResponse `json:"dashboards"`
}
type ResponseMsgVisualization struct {
Visualization VisualizationResponse `json:"visualization"`
type ResponseMsgDashboard struct {
Dashboard DashboardResponse `json:"dashboard"`
}
type ResponseMsgWidgets struct {

View file

@ -18,7 +18,7 @@ const ModelUser = ModelName("user")
const ModelSimulation = ModelName("simulation")
const ModelSimulator = ModelName("simulator")
const ModelSimulatorAction = ModelName("simulatoraction")
const ModelVisualization = ModelName("visualization")
const ModelDashboard = ModelName("dashboard")
const ModelWidget = ModelName("widget")
const ModelSimulationModel = ModelName("simulationmodel")
const ModelSignal = ModelName("signal")
@ -56,7 +56,7 @@ var Roles = RoleActions{
ModelSimulator: crud,
ModelSimulatorAction: crud,
ModelWidget: crud,
ModelVisualization: crud,
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
},
@ -67,14 +67,14 @@ var Roles = RoleActions{
ModelSimulator: _r__,
ModelSimulatorAction: _ru_,
ModelWidget: crud,
ModelVisualization: crud,
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
},
"Guest": {
ModelSimulation: _r__,
ModelSimulationModel: _r__,
ModelVisualization: _r__,
ModelDashboard: _r__,
ModelWidget: _r__,
ModelSimulator: _r__,
ModelSimulatorAction: _r__,

View file

@ -151,30 +151,30 @@ func (self *SimulatorSerializer) Response() SimulatorResponse {
return response
}
// Visualization/s Serializers
// Dashboard/s Serializers
type VisualizationsSerializer struct {
Ctx *gin.Context
Visualizations []Visualization
type DashboardsSerializer struct {
Ctx *gin.Context
Dashboards []Dashboard
}
func (self *VisualizationsSerializer) Response() []VisualizationResponse {
response := []VisualizationResponse{}
for _, visualization := range self.Visualizations {
serializer := VisualizationSerializer{self.Ctx, visualization}
func (self *DashboardsSerializer) Response() []DashboardResponse {
response := []DashboardResponse{}
for _, dashboard := range self.Dashboards {
serializer := DashboardSerializer{self.Ctx, dashboard}
response = append(response, serializer.Response())
}
return response
}
type VisualizationSerializer struct {
type DashboardSerializer struct {
Ctx *gin.Context
Visualization
Dashboard
}
func (self *VisualizationSerializer) Response() VisualizationResponse {
func (self *DashboardSerializer) Response() DashboardResponse {
response := VisualizationResponse{
response := DashboardResponse{
Name: self.Name,
Grid: self.Grid,
SimulationID: self.SimulationID,
@ -207,18 +207,18 @@ type WidgetSerializer struct {
func (self *WidgetSerializer) Response() WidgetResponse {
response := WidgetResponse{
ID: self.ID,
Name: self.Name,
Type: self.Type,
Width: self.Width,
Height: self.Height,
MinWidth: self.MinWidth,
MinHeight: self.MinHeight,
X: self.X,
Y: self.Y,
Z: self.Z,
VisualizationID: self.VisualizationID,
IsLocked: self.IsLocked,
ID: self.ID,
Name: self.Name,
Type: self.Type,
Width: self.Width,
Height: self.Height,
MinWidth: self.MinWidth,
MinHeight: self.MinHeight,
X: self.X,
Y: self.Y,
Z: self.Z,
DashboardID: self.DashboardID,
IsLocked: self.IsLocked,
//CustomProperties
}
return response

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,183 @@
package dashboard
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func RegisterDashboardEndpoints(r *gin.RouterGroup) {
r.GET("", getDashboards)
r.POST("", addDashboard)
r.PUT("/:dashboardID", updateDashboard)
r.GET("/:dashboardID", getDashboard)
r.DELETE("/:dashboardID", deleteDashboard)
}
// getDashboards godoc
// @Summary Get all dashboards of simulation
// @ID getDashboards
// @Produce json
// @Tags dashboards
// @Success 200 {array} common.DashboardResponse "Array of dashboards to which belong to simulation"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID query int true "Simulation ID"
// @Router /dashboards [get]
func getDashboards(c *gin.Context) {
ok, sim := simulation.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var dab []common.Dashboard
err := db.Order("ID asc").Model(sim).Related(&dab, "Dashboards").Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.DashboardsSerializer{c, dab}
c.JSON(http.StatusOK, gin.H{
"dashboards": serializer.Response(),
})
}
// addDashboard godoc
// @Summary Add a dashboard to a simulation
// @ID addDashboard
// @Accept json
// @Produce json
// @Tags dashboards
// @Param inputDab body common.DashboardResponse true "Dashboard to be added incl. ID of simulation"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /dashboards [post]
func addDashboard(c *gin.Context) {
var newDab Dashboard
err := c.BindJSON(&newDab)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
ok, _ := simulation.CheckPermissions(c, common.Create, "body", int(newDab.SimulationID))
if !ok {
return
}
// add dashboard to DB and add association to simulation
err = newDab.addToSimulation()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateDashboard godoc
// @Summary Update a dashboard
// @ID updateDashboard
// @Tags dashboards
// @Accept json
// @Produce json
// @Param inputDab body common.DashboardResponse true "Dashboard to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param dashboardID path int true "Dashboard ID"
// @Router /dashboards/{dashboardID} [put]
func updateDashboard(c *gin.Context) {
ok, d := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
var modifiedDab Dashboard
err := c.BindJSON(&modifiedDab)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = d.update(modifiedDab)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getDashboard godoc
// @Summary Get a dashboard
// @ID getDashboard
// @Tags dashboards
// @Produce json
// @Success 200 {object} common.DashboardResponse "Requested dashboard."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param dashboardID path int true "Dashboard ID"
// @Router /dashboards/{dashboardID} [get]
func getDashboard(c *gin.Context) {
ok, dab := CheckPermissions(c, common.Read, "path", -1)
if !ok {
return
}
serializer := common.DashboardSerializer{c, dab.Dashboard}
c.JSON(http.StatusOK, gin.H{
"dashboard": serializer.Response(),
})
}
// deleteDashboard godoc
// @Summary Delete a dashboard
// @ID deleteDashboard
// @Tags dashboards
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param dashboardID path int true "Dashboard ID"
// @Router /dashboards/{dashboardID} [delete]
func deleteDashboard(c *gin.Context) {
ok, dab := CheckPermissions(c, common.Delete, "path", -1)
if !ok {
return
}
err := dab.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -0,0 +1,75 @@
package dashboard
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
type Dashboard struct {
common.Dashboard
}
func (v *Dashboard) save() error {
db := common.GetDB()
err := db.Create(v).Error
return err
}
func (d *Dashboard) ByID(id uint) error {
db := common.GetDB()
err := db.Find(d, id).Error
if err != nil {
return fmt.Errorf("Dashboard with id=%v does not exist", id)
}
return nil
}
func (d *Dashboard) addToSimulation() error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(d.SimulationID)
if err != nil {
return err
}
// save dashboard to DB
err = d.save()
if err != nil {
return err
}
// associate dashboard with simulation
err = db.Model(&sim).Association("Dashboards").Append(d).Error
return err
}
func (d *Dashboard) update(modifiedDab Dashboard) error {
db := common.GetDB()
err := db.Model(d).Updates(map[string]interface{}{
"Name": modifiedDab.Name,
"Grid": modifiedDab.Grid,
}).Error
return err
}
func (d *Dashboard) delete() error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(d.SimulationID)
if err != nil {
return err
}
// remove association between Dashboard and Simulation
// Dashboard itself is not deleted from DB, it remains as "dangling"
err = db.Model(&sim).Association("Dashboards").Delete(d).Error
return err
}

View file

@ -1,4 +1,4 @@
package visualization
package dashboard
import (
"fmt"
@ -11,48 +11,48 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func CheckPermissions(c *gin.Context, operation common.CRUD, visIDSource string, visIDBody int) (bool, Visualization) {
func CheckPermissions(c *gin.Context, operation common.CRUD, dabIDSource string, dabIDBody int) (bool, Dashboard) {
var vis Visualization
var dab Dashboard
err := common.ValidateRole(c, common.ModelVisualization, operation)
err := common.ValidateRole(c, common.ModelDashboard, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, vis
return false, dab
}
var visID int
if visIDSource == "path" {
visID, err = strconv.Atoi(c.Param("visualizationID"))
var dabID int
if dabIDSource == "path" {
dabID, err = strconv.Atoi(c.Param("dashboardID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulationID path parameter")
errormsg := fmt.Sprintf("Bad request. No or incorrect format of dashboardID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, vis
return false, dab
}
} else if visIDSource == "query" {
visID, err = strconv.Atoi(c.Request.URL.Query().Get("visualizationID"))
} else if dabIDSource == "query" {
dabID, err = strconv.Atoi(c.Request.URL.Query().Get("dashboardID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of visualizationID query parameter")
errormsg := fmt.Sprintf("Bad request. No or incorrect format of dashboardID query parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, vis
return false, dab
}
} else if visIDSource == "body" {
visID = visIDBody
} else if dabIDSource == "body" {
dabID = dabIDBody
}
err = vis.ByID(uint(visID))
err = dab.ByID(uint(dabID))
if common.ProvideErrorResponse(c, err) {
return false, vis
return false, dab
}
ok, _ := simulation.CheckPermissions(c, operation, "body", int(vis.SimulationID))
ok, _ := simulation.CheckPermissions(c, operation, "body", int(dab.SimulationID))
if !ok {
return false, vis
return false, dab
}
return true, vis
return true, dab
}

View file

@ -0,0 +1,162 @@
package dashboard
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
var token string
type credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
var cred = credentials{
Username: "User_A",
Password: "abc123",
}
var msgOK = common.ResponseMsg{
Message: "OK.",
}
var dabA = common.DashboardResponse{
ID: 1,
Name: "Dashboard_A",
Grid: 15,
SimulationID: 1,
}
var dabB = common.DashboardResponse{
ID: 2,
Name: "Dashboard_B",
Grid: 15,
SimulationID: 1,
}
var dabC = common.Dashboard{
ID: 3,
Name: "Dashboard_C",
Grid: 99,
SimulationID: 1,
}
var dabCupdated = common.Dashboard{
ID: dabC.ID,
Name: "Dashboard_CUpdated",
SimulationID: dabC.SimulationID,
Grid: dabC.Grid,
}
var dabC_response = common.DashboardResponse{
ID: dabC.ID,
Name: dabC.Name,
Grid: dabC.Grid,
SimulationID: dabC.SimulationID,
}
var dabC_responseUpdated = common.DashboardResponse{
ID: dabCupdated.ID,
Name: dabCupdated.Name,
Grid: dabCupdated.Grid,
SimulationID: dabCupdated.SimulationID,
}
var myDashboards = []common.DashboardResponse{
dabA,
dabB,
}
var msgDashboards = common.ResponseMsgDashboards{
Dashboards: myDashboards,
}
var msgDab = common.ResponseMsgDashboard{
Dashboard: dabC_response,
}
var msgDabupdated = common.ResponseMsgDashboard{
Dashboard: dabC_responseUpdated,
}
// Test /dashboards endpoints
func TestEndpoints(t *testing.T) {
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterDashboardEndpoints(api.Group("/dashboards"))
credjson, err := json.Marshal(cred)
if err != nil {
panic(err)
}
msgOKjson, err := json.Marshal(msgOK)
if err != nil {
panic(err)
}
msgDashboardsjson, err := json.Marshal(msgDashboards)
if err != nil {
panic(err)
}
msgDabjson, err := json.Marshal(msgDab)
if err != nil {
panic(err)
}
msgDabupdatedjson, err := json.Marshal(msgDabupdated)
if err != nil {
panic(err)
}
dabCjson, err := json.Marshal(dabC)
if err != nil {
panic(err)
}
dabCupdatedjson, err := json.Marshal(dabCupdated)
if err != nil {
panic(err)
}
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET models
common.TestEndpoint(t, router, token, "/api/dashboards?simulationID=1", "GET", nil, 200, string(msgDashboardsjson))
// test POST models
common.TestEndpoint(t, router, token, "/api/dashboards", "POST", dabCjson, 200, string(msgOKjson))
// test GET models/:ModelID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/dashboards/3", "GET", nil, 200, string(msgDabjson))
// test PUT models/:ModelID
common.TestEndpoint(t, router, token, "/api/dashboards/3", "PUT", dabCupdatedjson, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/dashboards/3", "GET", nil, 200, string(msgDabupdatedjson))
// test DELETE models/:ModelID
common.TestEndpoint(t, router, token, "/api/dashboards/3", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/dashboards?simulationID=1", "GET", nil, 200, string(msgDashboardsjson))
// TODO add testing for other return codes
}

View file

@ -1,183 +0,0 @@
package visualization
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func RegisterVisualizationEndpoints(r *gin.RouterGroup) {
r.GET("", getVisualizations)
r.POST("", addVisualization)
r.PUT("/:visualizationID", updateVisualization)
r.GET("/:visualizationID", getVisualization)
r.DELETE("/:visualizationID", deleteVisualization)
}
// getVisualizations godoc
// @Summary Get all visualizations of simulation
// @ID getVisualizations
// @Produce json
// @Tags visualizations
// @Success 200 {array} common.VisualizationResponse "Array of visualizations to which belong to simulation"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID query int true "Simulation ID"
// @Router /visualizations [get]
func getVisualizations(c *gin.Context) {
ok, sim := simulation.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var vis []common.Visualization
err := db.Order("ID asc").Model(sim).Related(&vis, "Visualizations").Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.VisualizationsSerializer{c, vis}
c.JSON(http.StatusOK, gin.H{
"visualizations": serializer.Response(),
})
}
// addVisualization godoc
// @Summary Add a visualization to a simulation
// @ID addVisualization
// @Accept json
// @Produce json
// @Tags visualizations
// @Param inputVis body common.VisualizationResponse true "Visualization to be added incl. ID of simulation"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /visualizations [post]
func addVisualization(c *gin.Context) {
var newVis Visualization
err := c.BindJSON(&newVis)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
ok, _ := simulation.CheckPermissions(c, common.Create, "body", int(newVis.SimulationID))
if !ok {
return
}
// add visualization to DB and add association to simulation
err = newVis.addToSimulation()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateVisualization godoc
// @Summary Update a visualization
// @ID updateVisualization
// @Tags visualizations
// @Accept json
// @Produce json
// @Param inputVis body common.VisualizationResponse true "Visualization to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID path int true "Visualization ID"
// @Router /visualizations/{visualizationID} [put]
func updateVisualization(c *gin.Context) {
ok, v := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
var modifiedVis Visualization
err := c.BindJSON(&modifiedVis)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = v.update(modifiedVis)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getVisualization godoc
// @Summary Get a visualization
// @ID getVisualization
// @Tags visualizations
// @Produce json
// @Success 200 {object} common.VisualizationResponse "Requested visualization."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID path int true "Visualization ID"
// @Router /visualizations/{visualizationID} [get]
func getVisualization(c *gin.Context) {
ok, vis := CheckPermissions(c, common.Read, "path", -1)
if !ok {
return
}
serializer := common.VisualizationSerializer{c, vis.Visualization}
c.JSON(http.StatusOK, gin.H{
"visualization": serializer.Response(),
})
}
// deleteVisualization godoc
// @Summary Delete a visualization
// @ID deleteVisualization
// @Tags visualizations
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID path int true "Visualization ID"
// @Router /visualizations/{visualizationID} [delete]
func deleteVisualization(c *gin.Context) {
ok, vis := CheckPermissions(c, common.Delete, "path", -1)
if !ok {
return
}
err := vis.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -1,75 +0,0 @@
package visualization
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
type Visualization struct {
common.Visualization
}
func (v *Visualization) save() error {
db := common.GetDB()
err := db.Create(v).Error
return err
}
func (v *Visualization) ByID(id uint) error {
db := common.GetDB()
err := db.Find(v, id).Error
if err != nil {
return fmt.Errorf("Visualization with id=%v does not exist", id)
}
return nil
}
func (v *Visualization) addToSimulation() error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(v.SimulationID)
if err != nil {
return err
}
// save visualization to DB
err = v.save()
if err != nil {
return err
}
// associate visualization with simulation
err = db.Model(&sim).Association("Visualizations").Append(v).Error
return err
}
func (v *Visualization) update(modifiedVis Visualization) error {
db := common.GetDB()
err := db.Model(v).Updates(map[string]interface{}{
"Name": modifiedVis.Name,
"Grid": modifiedVis.Grid,
}).Error
return err
}
func (v *Visualization) delete() error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(v.SimulationID)
if err != nil {
return err
}
// remove association between Visualization and Simulation
// Visualization itself is not deleted from DB, it remains as "dangling"
err = db.Model(&sim).Association("Visualizations").Delete(v).Error
return err
}

View file

@ -1,162 +0,0 @@
package visualization
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
var token string
type credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
var cred = credentials{
Username: "User_A",
Password: "abc123",
}
var msgOK = common.ResponseMsg{
Message: "OK.",
}
var visA = common.VisualizationResponse{
ID: 1,
Name: "Visualization_A",
Grid: 15,
SimulationID: 1,
}
var visB = common.VisualizationResponse{
ID: 2,
Name: "Visualization_B",
Grid: 15,
SimulationID: 1,
}
var visC = common.Visualization{
ID: 3,
Name: "Visualization_C",
Grid: 99,
SimulationID: 1,
}
var visCupdated = common.Visualization{
ID: visC.ID,
Name: "Visualization_CUpdated",
SimulationID: visC.SimulationID,
Grid: visC.Grid,
}
var visC_response = common.VisualizationResponse{
ID: visC.ID,
Name: visC.Name,
Grid: visC.Grid,
SimulationID: visC.SimulationID,
}
var visC_responseUpdated = common.VisualizationResponse{
ID: visCupdated.ID,
Name: visCupdated.Name,
Grid: visCupdated.Grid,
SimulationID: visCupdated.SimulationID,
}
var myVisualizations = []common.VisualizationResponse{
visA,
visB,
}
var msgVisualizations = common.ResponseMsgVisualizations{
Visualizations: myVisualizations,
}
var msgVis = common.ResponseMsgVisualization{
Visualization: visC_response,
}
var msgVisupdated = common.ResponseMsgVisualization{
Visualization: visC_responseUpdated,
}
// Test /models endpoints
func TestVisualizationEndpoints(t *testing.T) {
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterVisualizationEndpoints(api.Group("/visualizations"))
credjson, err := json.Marshal(cred)
if err != nil {
panic(err)
}
msgOKjson, err := json.Marshal(msgOK)
if err != nil {
panic(err)
}
msgVisualizationsjson, err := json.Marshal(msgVisualizations)
if err != nil {
panic(err)
}
msgVisjson, err := json.Marshal(msgVis)
if err != nil {
panic(err)
}
msgVisupdatedjson, err := json.Marshal(msgVisupdated)
if err != nil {
panic(err)
}
visCjson, err := json.Marshal(visC)
if err != nil {
panic(err)
}
visCupdatedjson, err := json.Marshal(visCupdated)
if err != nil {
panic(err)
}
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET models
common.TestEndpoint(t, router, token, "/api/visualizations?simulationID=1", "GET", nil, 200, string(msgVisualizationsjson))
// test POST models
common.TestEndpoint(t, router, token, "/api/visualizations", "POST", visCjson, 200, string(msgOKjson))
// test GET models/:ModelID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/visualizations/3", "GET", nil, 200, string(msgVisjson))
// test PUT models/:ModelID
common.TestEndpoint(t, router, token, "/api/visualizations/3", "PUT", visCupdatedjson, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/visualizations/3", "GET", nil, 200, string(msgVisupdatedjson))
// test DELETE models/:ModelID
common.TestEndpoint(t, router, token, "/api/visualizations/3", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/visualizations?simulationID=1", "GET", nil, 200, string(msgVisualizationsjson))
// TODO add testing for other return codes
}

View file

@ -6,7 +6,7 @@ import (
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
func RegisterWidgetEndpoints(r *gin.RouterGroup) {
@ -18,27 +18,27 @@ func RegisterWidgetEndpoints(r *gin.RouterGroup) {
}
// getWidgets godoc
// @Summary Get all widgets of visualization
// @Summary Get all widgets of dashboard
// @ID getWidgets
// @Produce json
// @Tags widgets
// @Success 200 {array} common.WidgetResponse "Array of widgets to which belong to visualization"
// @Success 200 {array} common.WidgetResponse "Array of widgets to which belong to dashboard"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID query int true "Visualization ID"
// @Param dashboardID query int true "Dashboard ID"
// @Router /widgets [get]
func getWidgets(c *gin.Context) {
ok, vis := visualization.CheckPermissions(c, common.Read, "query", -1)
ok, dab := dashboard.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var widgets []common.Widget
err := db.Order("ID asc").Model(vis).Related(&widgets, "Widgets").Error
err := db.Order("ID asc").Model(dab).Related(&widgets, "Widgets").Error
if common.ProvideErrorResponse(c, err) {
return
}
@ -50,12 +50,12 @@ func getWidgets(c *gin.Context) {
}
// addWidget godoc
// @Summary Add a widget to a visualization
// @Summary Add a widget to a dashboard
// @ID addWidget
// @Accept json
// @Produce json
// @Tags widgets
// @Param inputWidget body common.WidgetResponse true "Widget to be added incl. ID of visualization"
// @Param inputWidget body common.WidgetResponse true "Widget to be added incl. ID of dashboard"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
@ -74,12 +74,12 @@ func addWidget(c *gin.Context) {
return
}
ok, _ := visualization.CheckPermissions(c, common.Create, "body", int(newWidget.VisualizationID))
ok, _ := dashboard.CheckPermissions(c, common.Create, "body", int(newWidget.DashboardID))
if !ok {
return
}
err = newWidget.addToVisualization()
err = newWidget.addToDashboard()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{

View file

@ -4,7 +4,7 @@ import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
type Widget struct {
@ -26,22 +26,22 @@ func (w *Widget) ByID(id uint) error {
return nil
}
func (w *Widget) addToVisualization() error {
func (w *Widget) addToDashboard() error {
db := common.GetDB()
var vis visualization.Visualization
err := vis.ByID(uint(w.VisualizationID))
var dab dashboard.Dashboard
err := dab.ByID(uint(w.DashboardID))
if err != nil {
return err
}
// save visualization to DB
// save widget to DB
err = w.save()
if err != nil {
return err
}
// associate visualization with simulation
err = db.Model(&vis).Association("Widgets").Append(w).Error
// associate dashboard with simulation
err = db.Model(&dab).Association("Widgets").Append(w).Error
return err
}
@ -69,15 +69,15 @@ func (w *Widget) update(modifiedWidget Widget) error {
func (w *Widget) delete() error {
db := common.GetDB()
var vis visualization.Visualization
err := vis.ByID(w.VisualizationID)
var dab dashboard.Dashboard
err := dab.ByID(w.DashboardID)
if err != nil {
return err
}
// remove association between Visualization and Widget
// remove association between Dashboard and Widget
// Widget itself is not deleted from DB, it remains as "dangling"
err = db.Model(&vis).Association("Widgets").Delete(w).Error
err = db.Model(&dab).Association("Widgets").Delete(w).Error
// TODO: What about files that belong to a widget? Keep them or remove them here?

View file

@ -8,7 +8,7 @@ import (
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
func CheckPermissions(c *gin.Context, operation common.CRUD, widgetIDBody int) (bool, Widget) {
@ -40,7 +40,7 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, widgetIDBody int) (
return false, w
}
ok, _ := visualization.CheckPermissions(c, operation, "body", int(w.VisualizationID))
ok, _ := dashboard.CheckPermissions(c, operation, "body", int(w.DashboardID))
if !ok {
return false, w
}

View file

@ -39,7 +39,7 @@ var wdgA = common.WidgetResponse{
Z: 0,
IsLocked: false,
CustomProperties: "",
VisualizationID: 1,
DashboardID: 1,
}
var wdgB = common.WidgetResponse{
@ -55,7 +55,7 @@ var wdgB = common.WidgetResponse{
Z: 0,
IsLocked: false,
CustomProperties: "",
VisualizationID: 1,
DashboardID: 1,
}
var wdgC = common.Widget{
@ -71,7 +71,7 @@ var wdgC = common.Widget{
Z: 13,
IsLocked: false,
CustomProperties: "",
VisualizationID: 1,
DashboardID: 1,
}
var wdgCupdated = common.Widget{
@ -87,7 +87,7 @@ var wdgCupdated = common.Widget{
Z: wdgC.Z,
IsLocked: wdgC.IsLocked,
CustomProperties: wdgC.CustomProperties,
VisualizationID: wdgC.VisualizationID,
DashboardID: wdgC.DashboardID,
}
var wdgC_response = common.WidgetResponse{
@ -103,7 +103,7 @@ var wdgC_response = common.WidgetResponse{
Z: wdgC.Z,
IsLocked: wdgC.IsLocked,
CustomProperties: wdgC.CustomProperties,
VisualizationID: wdgC.VisualizationID,
DashboardID: wdgC.DashboardID,
}
var wdgC_responseUpdated = common.WidgetResponse{
@ -119,7 +119,7 @@ var wdgC_responseUpdated = common.WidgetResponse{
Z: wdgC.Z,
IsLocked: wdgC.IsLocked,
CustomProperties: wdgC.CustomProperties,
VisualizationID: wdgC.VisualizationID,
DashboardID: wdgC.DashboardID,
}
var myWidgets = []common.WidgetResponse{
@ -139,7 +139,7 @@ var msgWdgupdated = common.ResponseMsgWidget{
Widget: wdgC_responseUpdated,
}
// Test /models endpoints
// Test /widgets endpoints
func TestWidgetEndpoints(t *testing.T) {
db := common.DummyInitDB()
@ -194,22 +194,22 @@ func TestWidgetEndpoints(t *testing.T) {
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET models
common.TestEndpoint(t, router, token, "/api/widgets?visualizationID=1", "GET", nil, 200, string(msgWidgetsjson))
// test GET widgets
common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, string(msgWidgetsjson))
// test POST models
// test POST widgets
common.TestEndpoint(t, router, token, "/api/widgets", "POST", wdgCjson, 200, string(msgOKjson))
// test GET models/:ModelID to check if previous POST worked correctly
// test GET widgets/:widgetID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, string(msgWdgjson))
// test PUT models/:ModelID
// test PUT widgets/:widgetID
common.TestEndpoint(t, router, token, "/api/widgets/3", "PUT", wdgCupdatedjson, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, string(msgWdgupdatedjson))
// test DELETE models/:ModelID
// test DELETE widgets/:widgetID
common.TestEndpoint(t, router, token, "/api/widgets/3", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/widgets?visualizationID=1", "GET", nil, 200, string(msgWidgetsjson))
common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, string(msgWidgetsjson))
// TODO add testing for other return codes

View file

@ -12,11 +12,11 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
_ "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/doc/api" // doc/api folder is used by Swag CLI, you have to import it
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
)
@ -57,7 +57,7 @@ func main() {
simulation.RegisterSimulationEndpoints(api.Group("/simulations"))
simulationmodel.RegisterSimulationModelEndpoints(api.Group("/models"))
signal.RegisterSignalEndpoints(api.Group("/signals"))
visualization.RegisterVisualizationEndpoints(api.Group("/visualizations"))
dashboard.RegisterDashboardEndpoints(api.Group("/dashboards"))
widget.RegisterWidgetEndpoints(api.Group("/widgets"))
file.RegisterFileEndpoints(api.Group("/files"))
user.RegisterUserEndpoints(api.Group("/users"))