1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-07-15 23:54:29 +02:00

[MM-43781] Feature: boards insights (#3005)

* Add boilerplate functions and handlers for boards insights

* Fix function signatures to add 'duration' parameter, fix where clauses in db queries

* Fix where clause to include boards of which userId in parameter is a member

* Modify queries to work with sqlite, postgres, mysql

* Integration tests, and results of make generate

* Lint Fixes

* Add icons to board insights

* Lint fixes

* Format insights queries without squirrel to fix parameterization issues

* Add tests for sqlstore utility functions

* Improve team insights tests by creating 2 boards

* Refactor endpoints/app to adhere to developments in 7.0 release

* Refactor queries to use squirrel

* Lint fixes

* Fix client, integration tests

* Remove old integration tests

* Add storetests, refactor functions to handle authorized board_ids

* Make queries compatible with mysql, sqlite

* Add app tests

* Fix lint errors

* Revert makefile changes, fix docstring in api

* Lint fixes and doc correction suggested by @wiggin77

* Fix mock store call count error

* adding client code

* Make the following changes

 - use serviceAPI to get user.Timezone
 - rename licenseAndGuestUserCheck to insightPermissionGate, and handle returned error better
 - validate page, perPage parameters aren't < 0

* Lint fix

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Benjamin Cooke <benjamincooke@Benjamins-MacBook-Pro.local>
This commit is contained in:
Shivashis Padhi
2022-08-08 11:42:02 +05:30
committed by GitHub
parent 9c6cfa68aa
commit f00b5c9e61
18 changed files with 912 additions and 0 deletions

View File

@ -181,6 +181,10 @@ func (a *API) RegisterRoutes(r *mux.Router) {
// System APIs
r.HandleFunc("/hello", a.handleHello).Methods("GET")
// Insights APIs
apiv2.HandleFunc("/teams/{teamID}/boards/insights", a.sessionRequired(a.handleTeamBoardsInsights)).Methods("GET")
apiv2.HandleFunc("/users/me/boards/insights", a.sessionRequired(a.handleUserBoardsInsights)).Methods("GET")
}
func (a *API) RegisterAdminRoutes(r *mux.Router) {
@ -2179,6 +2183,216 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("fileID", fileID)
auditRec.Success()
}
func (a *API) handleTeamBoardsInsights(w http.ResponseWriter, r *http.Request) {
// swagger:operation GET /teams/{teamID}/boards/insights handleTeamBoardsInsights
//
// Returns team boards insights
//
// ---
// produces:
// - application/json
// parameters:
// - name: teamID
// in: path
// description: Team ID
// required: true
// type: string
// - name: time_range
// in: query
// description: duration of data to calculate insights for
// required: true
// type: string
// - name: page
// in: query
// description: page offset for top boards
// required: true
// type: string
// - name: per_page
// in: query
// description: limit for boards in a page.
// required: true
// type: string
// security:
// - BearerAuth: []
// responses:
// '200':
// description: success
// schema:
// type: array
// items:
// "$ref": "#/definitions/BoardInsight"
// default:
// description: internal error
// schema:
// "$ref": "#/definitions/ErrorResponse"
vars := mux.Vars(r)
teamID := vars["teamID"]
userID := getUserID(r)
query := r.URL.Query()
timeRange := query.Get("time_range")
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"})
return
}
auditRec := a.makeAuditRecord(r, "getTeamBoardsInsights", audit.Fail)
defer a.audit.LogRecord(audit.LevelRead, auditRec)
page, err := strconv.Atoi(query.Get("page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting page parameter to integer", err)
return
}
if page < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
}
perPage, err := strconv.Atoi(query.Get("per_page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting per_page parameter to integer", err)
return
}
if perPage < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
}
userTimezone, aErr := a.app.GetUserTimezone(userID)
if aErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Error getting time zone of user", aErr)
return
}
userLocation, _ := time.LoadLocation(userTimezone)
if userLocation == nil {
userLocation = time.Now().UTC().Location()
}
// get unix time for duration
startTime := mmModel.StartOfDayForTimeRange(timeRange, userLocation)
boardsInsights, err := a.app.GetTeamBoardsInsights(userID, teamID, &mmModel.InsightsOpts{
StartUnixMilli: mmModel.GetMillisForTime(*startTime),
Page: page,
PerPage: perPage,
})
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "time_range="+timeRange, err)
return
}
data, err := json.Marshal(boardsInsights)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
jsonBytesResponse(w, http.StatusOK, data)
auditRec.AddMeta("teamBoardsInsightCount", len(boardsInsights.Items))
auditRec.Success()
}
func (a *API) handleUserBoardsInsights(w http.ResponseWriter, r *http.Request) {
// swagger:operation GET /users/me/boards/insights getUserBoardsInsights
//
// Returns user boards insights
//
// ---
// produces:
// - application/json
// parameters:
// - name: teamID
// in: path
// description: Team ID
// required: true
// type: string
// - name: time_range
// in: query
// description: duration of data to calculate insights for
// required: true
// type: string
// - name: page
// in: query
// description: page offset for top boards
// required: true
// type: string
// - name: per_page
// in: query
// description: limit for boards in a page.
// required: true
// type: string
// security:
// - BearerAuth: []
// responses:
// '200':
// description: success
// schema:
// type: array
// items:
// "$ref": "#/definitions/BoardInsight"
// default:
// description: internal error
// schema:
// "$ref": "#/definitions/ErrorResponse"
userID := getUserID(r)
query := r.URL.Query()
teamID := query.Get("team_id")
timeRange := query.Get("time_range")
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"})
return
}
auditRec := a.makeAuditRecord(r, "getUserBoardsInsights", audit.Fail)
defer a.audit.LogRecord(audit.LevelRead, auditRec)
page, err := strconv.Atoi(query.Get("page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting page parameter to integer", err)
return
}
if page < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
}
perPage, err := strconv.Atoi(query.Get("per_page"))
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "error converting per_page parameter to integer", err)
return
}
if perPage < 0 {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Invalid page parameter", nil)
}
userTimezone, aErr := a.app.GetUserTimezone(userID)
if aErr != nil {
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "Error getting time zone of user", aErr)
return
}
userLocation, _ := time.LoadLocation(userTimezone)
if userLocation == nil {
userLocation = time.Now().UTC().Location()
}
// get unix time for duration
startTime := mmModel.StartOfDayForTimeRange(timeRange, userLocation)
boardsInsights, err := a.app.GetUserBoardsInsights(userID, teamID, &mmModel.InsightsOpts{
StartUnixMilli: mmModel.GetMillisForTime(*startTime),
Page: page,
PerPage: perPage,
})
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "time_range="+timeRange, err)
return
}
data, err := json.Marshal(boardsInsights)
if err != nil {
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
return
}
jsonBytesResponse(w, http.StatusOK, data)
auditRec.AddMeta("userBoardInsightCount", len(boardsInsights.Items))
auditRec.Success()
}
func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
// swagger:operation GET /teams/{teamID}/users getTeamUsers