mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-24 13:43:12 +02:00
Display board statistics (#4025)
* initial commit for displaying board statistics * lint fixes * i18n-extract, remove log entries, cleanup * more lint fixes * add check for standalone mode * update tests due to change to NotImplemented * lint fix * revert removing empty comment lines Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
e9d4aeba0e
commit
e3ae682eea
@ -27,7 +27,7 @@ func normalizeAppErr(appErr *mm_model.AppError) error {
|
||||
// serviceAPIAdapter is an adapter that flattens the APIs provided by suite services so they can
|
||||
// be used as per the Plugin API.
|
||||
// Note: when supporting a plugin build is no longer needed this adapter may be removed as the Boards app
|
||||
// can be modified to use the services in modular fashion.
|
||||
// can be modified to use the services in modular fashion.
|
||||
type serviceAPIAdapter struct {
|
||||
api *boardsProduct
|
||||
ctx *request.Context
|
||||
@ -123,6 +123,10 @@ func (a *serviceAPIAdapter) CreateMember(teamID string, userID string) (*mm_mode
|
||||
// Permissions service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) HasPermissionTo(userID string, permission *mm_model.Permission) bool {
|
||||
return a.api.permissionsService.HasPermissionTo(userID, permission)
|
||||
}
|
||||
|
||||
func (a *serviceAPIAdapter) HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool {
|
||||
return a.api.permissionsService.HasPermissionToTeam(userID, teamID, permission)
|
||||
}
|
||||
@ -134,6 +138,7 @@ func (a *serviceAPIAdapter) HasPermissionToChannel(askingUserID string, channelI
|
||||
//
|
||||
// Bot service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
|
||||
return a.api.botService.EnsureBot(a.ctx, boardsProductID, bot)
|
||||
}
|
||||
@ -141,6 +146,7 @@ func (a *serviceAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
|
||||
//
|
||||
// License service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetLicense() *mm_model.License {
|
||||
return a.api.licenseService.GetLicense()
|
||||
}
|
||||
@ -148,6 +154,7 @@ func (a *serviceAPIAdapter) GetLicense() *mm_model.License {
|
||||
//
|
||||
// FileInfoStore service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, error) {
|
||||
fi, appErr := a.api.fileInfoStoreService.GetFileInfo(fileID)
|
||||
return fi, normalizeAppErr(appErr)
|
||||
@ -156,6 +163,7 @@ func (a *serviceAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, erro
|
||||
//
|
||||
// Cluster store.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mm_model.WebsocketBroadcast) {
|
||||
a.api.clusterService.PublishWebSocketEvent(boardsProductID, event, payload, broadcast)
|
||||
}
|
||||
@ -167,6 +175,7 @@ func (a *serviceAPIAdapter) PublishPluginClusterEvent(ev mm_model.PluginClusterE
|
||||
//
|
||||
// Cloud service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
|
||||
return a.api.cloudService.GetCloudLimits()
|
||||
}
|
||||
@ -174,6 +183,7 @@ func (a *serviceAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
|
||||
//
|
||||
// Config service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetConfig() *mm_model.Config {
|
||||
return a.api.configService.Config()
|
||||
}
|
||||
@ -181,6 +191,7 @@ func (a *serviceAPIAdapter) GetConfig() *mm_model.Config {
|
||||
//
|
||||
// Logger service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetLogger() mlog.LoggerIFace {
|
||||
return a.api.logger
|
||||
}
|
||||
@ -188,6 +199,7 @@ func (a *serviceAPIAdapter) GetLogger() mlog.LoggerIFace {
|
||||
//
|
||||
// KVStore service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) KVSetWithOptions(key string, value []byte, options mm_model.PluginKVSetOptions) (bool, error) {
|
||||
b, appErr := a.api.kvStoreService.SetPluginKeyWithOptions(boardsProductID, key, value, options)
|
||||
return b, normalizeAppErr(appErr)
|
||||
@ -196,6 +208,7 @@ func (a *serviceAPIAdapter) KVSetWithOptions(key string, value []byte, options m
|
||||
//
|
||||
// Store service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetMasterDB() (*sql.DB, error) {
|
||||
return a.api.storeService.GetMasterDB(), nil
|
||||
}
|
||||
@ -203,6 +216,7 @@ func (a *serviceAPIAdapter) GetMasterDB() (*sql.DB, error) {
|
||||
//
|
||||
// System service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetDiagnosticID() string {
|
||||
return a.api.systemService.GetDiagnosticId()
|
||||
}
|
||||
@ -210,6 +224,7 @@ func (a *serviceAPIAdapter) GetDiagnosticID() string {
|
||||
//
|
||||
// Router service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) RegisterRouter(sub *mux.Router) {
|
||||
a.api.routerService.RegisterRouter(boardsProductName, sub)
|
||||
}
|
||||
@ -217,6 +232,7 @@ func (a *serviceAPIAdapter) RegisterRouter(sub *mux.Router) {
|
||||
//
|
||||
// Preferences service.
|
||||
//
|
||||
|
||||
func (a *serviceAPIAdapter) GetPreferencesForUser(userID string) (mm_model.Preferences, error) {
|
||||
p, appErr := a.api.preferencesService.GetPreferencesForUser(userID)
|
||||
return p, normalizeAppErr(appErr)
|
||||
|
@ -125,6 +125,10 @@ func (a *pluginAPIAdapter) CreateMember(teamID string, userID string) (*mm_model
|
||||
// Permissions service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) HasPermissionTo(userID string, permission *mm_model.Permission) bool {
|
||||
return a.api.HasPermissionTo(userID, permission)
|
||||
}
|
||||
|
||||
func (a *pluginAPIAdapter) HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool {
|
||||
return a.api.HasPermissionToTeam(userID, teamID, permission)
|
||||
}
|
||||
@ -136,6 +140,7 @@ func (a *pluginAPIAdapter) HasPermissionToChannel(askingUserID string, channelID
|
||||
//
|
||||
// Bot service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
|
||||
return a.api.EnsureBotUser(bot)
|
||||
}
|
||||
@ -143,6 +148,7 @@ func (a *pluginAPIAdapter) EnsureBot(bot *mm_model.Bot) (string, error) {
|
||||
//
|
||||
// License service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetLicense() *mm_model.License {
|
||||
return a.api.GetLicense()
|
||||
}
|
||||
@ -150,6 +156,7 @@ func (a *pluginAPIAdapter) GetLicense() *mm_model.License {
|
||||
//
|
||||
// FileInfoStore service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, error) {
|
||||
fi, appErr := a.api.GetFileInfo(fileID)
|
||||
return fi, normalizeAppErr(appErr)
|
||||
@ -158,6 +165,7 @@ func (a *pluginAPIAdapter) GetFileInfo(fileID string) (*mm_model.FileInfo, error
|
||||
//
|
||||
// Cluster store.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mm_model.WebsocketBroadcast) {
|
||||
a.api.PublishWebSocketEvent(event, payload, broadcast)
|
||||
}
|
||||
@ -169,6 +177,7 @@ func (a *pluginAPIAdapter) PublishPluginClusterEvent(ev mm_model.PluginClusterEv
|
||||
//
|
||||
// Cloud service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
|
||||
return a.api.GetCloudLimits()
|
||||
}
|
||||
@ -176,6 +185,7 @@ func (a *pluginAPIAdapter) GetCloudLimits() (*mm_model.ProductLimits, error) {
|
||||
//
|
||||
// Config service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetConfig() *mm_model.Config {
|
||||
return a.api.GetUnsanitizedConfig()
|
||||
}
|
||||
@ -183,6 +193,7 @@ func (a *pluginAPIAdapter) GetConfig() *mm_model.Config {
|
||||
//
|
||||
// Logger service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetLogger() mlog.LoggerIFace {
|
||||
return a.logger
|
||||
}
|
||||
@ -190,6 +201,7 @@ func (a *pluginAPIAdapter) GetLogger() mlog.LoggerIFace {
|
||||
//
|
||||
// KVStore service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) KVSetWithOptions(key string, value []byte, options mm_model.PluginKVSetOptions) (bool, error) {
|
||||
b, appErr := a.api.KVSetWithOptions(key, value, options)
|
||||
return b, normalizeAppErr(appErr)
|
||||
@ -198,6 +210,7 @@ func (a *pluginAPIAdapter) KVSetWithOptions(key string, value []byte, options mm
|
||||
//
|
||||
// Store service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetMasterDB() (*sql.DB, error) {
|
||||
return a.storeService.GetMasterDB()
|
||||
}
|
||||
@ -205,6 +218,7 @@ func (a *pluginAPIAdapter) GetMasterDB() (*sql.DB, error) {
|
||||
//
|
||||
// System service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetDiagnosticID() string {
|
||||
return a.api.GetDiagnosticId()
|
||||
}
|
||||
@ -212,6 +226,7 @@ func (a *pluginAPIAdapter) GetDiagnosticID() string {
|
||||
//
|
||||
// Router service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) RegisterRouter(sub *mux.Router) {
|
||||
// NOOP for plugin
|
||||
}
|
||||
@ -219,6 +234,7 @@ func (a *pluginAPIAdapter) RegisterRouter(sub *mux.Router) {
|
||||
//
|
||||
// Preferences service.
|
||||
//
|
||||
|
||||
func (a *pluginAPIAdapter) GetPreferencesForUser(userID string) (mm_model.Preferences, error) {
|
||||
preferences, appErr := a.api.GetPreferencesForUser(userID)
|
||||
if appErr != nil {
|
||||
|
@ -352,6 +352,30 @@ export default class Plugin {
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
// Site statistics handler
|
||||
if (registry.registerSiteStatisticsHandler) {
|
||||
registry.registerSiteStatisticsHandler(async () => {
|
||||
const siteStats = await octoClient.getSiteStatistics()
|
||||
if(siteStats){
|
||||
return {
|
||||
boards_count: {
|
||||
name: intl.formatMessage({id: 'SiteStats.total_boards', defaultMessage: 'Total Boards'}),
|
||||
id: 'total_boards',
|
||||
icon: 'icon-product-boards',
|
||||
value: siteStats.board_count,
|
||||
},
|
||||
cards_count: {
|
||||
name: intl.formatMessage({id: 'SiteStats.total_cards', defaultMessage: 'Total Cards'}),
|
||||
id: 'total_cards',
|
||||
icon: 'icon-products',
|
||||
value: siteStats.card_count,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.boardSelectorId = this.registry.registerRootComponent((props: {webSocketClient: MMWebSocketClient}) => (
|
||||
|
@ -18,6 +18,7 @@ export interface PluginRegistry {
|
||||
registerRightHandSidebarComponent(component: React.ElementType, title: React.Element)
|
||||
registerRootComponent(component: React.ElementType)
|
||||
registerInsightsHandler(handler: (timeRange: string, page: number, perPage: number, teamId: string, insightType: string) => void)
|
||||
registerSiteStatisticsHandler(handler: () => void)
|
||||
|
||||
// Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ func (a *API) RegisterRoutes(r *mux.Router) {
|
||||
a.registerTemplatesRoutes(apiv2)
|
||||
a.registerBoardsRoutes(apiv2)
|
||||
a.registerBlocksRoutes(apiv2)
|
||||
a.registerStatisticsRoutes(apiv2)
|
||||
|
||||
// V3 routes
|
||||
a.registerCardsRoutes(apiv2)
|
||||
|
70
server/api/statistics.go
Normal file
70
server/api/statistics.go
Normal file
@ -0,0 +1,70 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func (a *API) registerStatisticsRoutes(r *mux.Router) {
|
||||
// statistics
|
||||
r.HandleFunc("/statistics", a.sessionRequired(a.handleStatistics)).Methods("GET")
|
||||
}
|
||||
|
||||
func (a *API) handleStatistics(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation GET /statistics handleStatistics
|
||||
//
|
||||
// Fetches the statistic of the server.
|
||||
//
|
||||
// ---
|
||||
// produces:
|
||||
// - application/json
|
||||
// security:
|
||||
// - BearerAuth: []
|
||||
// responses:
|
||||
// '200':
|
||||
// description: success
|
||||
// schema:
|
||||
// "$ref": "#/definitions/BoardStatistics"
|
||||
// default:
|
||||
// description: internal error
|
||||
// schema:
|
||||
// "$ref": "#/definitions/ErrorResponse"
|
||||
if !a.MattermostAuth {
|
||||
a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode"))
|
||||
return
|
||||
}
|
||||
|
||||
// user must have right to access analytics
|
||||
userID := getUserID(r)
|
||||
if !a.permissions.HasPermissionTo(userID, mmModel.PermissionGetAnalytics) {
|
||||
a.errorResponse(w, r, model.NewErrPermission("access denied System Statistics"))
|
||||
return
|
||||
}
|
||||
|
||||
boardCount, err := a.app.GetBoardCount()
|
||||
if err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
cardCount, err := a.app.GetUsedCardsCount()
|
||||
if err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
stats := model.BoardsStatistics{
|
||||
Boards: int(boardCount),
|
||||
Cards: cardCount,
|
||||
}
|
||||
data, err := json.Marshal(stats)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonBytesResponse(w, http.StatusOK, data)
|
||||
}
|
@ -55,6 +55,10 @@ func (a *App) GetBoardsCloudLimits() (*model.BoardsCloudLimits, error) {
|
||||
return boardsCloudLimits, nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsedCardsCount() (int, error) {
|
||||
return a.store.GetUsedCardsCount()
|
||||
}
|
||||
|
||||
// IsCloud returns true if the server is running as a plugin in a
|
||||
// cloud licensed server.
|
||||
func (a *App) IsCloud() bool {
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -56,7 +55,7 @@ func BuildErrorResponse(r *http.Response, err error) *Response {
|
||||
|
||||
func closeBody(r *http.Response) {
|
||||
if r.Body != nil {
|
||||
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||
_, _ = io.Copy(io.Discard, r.Body)
|
||||
_ = r.Body.Close()
|
||||
}
|
||||
}
|
||||
@ -903,3 +902,19 @@ func (c *Client) GetLimits() (*model.BoardsCloudLimits, *Response) {
|
||||
|
||||
return limits, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetStatistics() (*model.BoardsStatistics, *Response) {
|
||||
r, err := c.DoAPIGet("/statistics", "")
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var stats *model.BoardsStatistics
|
||||
err = json.NewDecoder(r.Body).Decode(&stats)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
return stats, BuildResponse(r)
|
||||
}
|
||||
|
@ -72,6 +72,10 @@ type TestHelper struct {
|
||||
|
||||
type FakePermissionPluginAPI struct{}
|
||||
|
||||
func (*FakePermissionPluginAPI) HasPermissionTo(userID string, permission *mmModel.Permission) bool {
|
||||
return userID == userAdmin
|
||||
}
|
||||
|
||||
func (*FakePermissionPluginAPI) HasPermissionToTeam(userID string, teamID string, permission *mmModel.Permission) bool {
|
||||
if userID == userNoTeamMember {
|
||||
return false
|
||||
|
@ -3773,3 +3773,40 @@ func TestPermissionsChannel(t *testing.T) {
|
||||
runTestCases(t, ttCases, testData, clients)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermissionsGetStatistics(t *testing.T) {
|
||||
t.Run("plugin", func(t *testing.T) {
|
||||
th := SetupTestHelperPluginMode(t)
|
||||
defer th.TearDown()
|
||||
clients := setupClients(th)
|
||||
testData := setupData(t, th)
|
||||
ttCases := []TestCase{
|
||||
{"/statistics", methodGet, "", userAnon, http.StatusUnauthorized, 0},
|
||||
{"/statistics", methodGet, "", userNoTeamMember, http.StatusForbidden, 0},
|
||||
{"/statistics", methodGet, "", userTeamMember, http.StatusForbidden, 0},
|
||||
{"/statistics", methodGet, "", userViewer, http.StatusForbidden, 0},
|
||||
{"/statistics", methodGet, "", userCommenter, http.StatusForbidden, 0},
|
||||
{"/statistics", methodGet, "", userEditor, http.StatusForbidden, 0},
|
||||
{"/statistics", methodGet, "", userAdmin, http.StatusOK, 1},
|
||||
{"/statistics", methodGet, "", userGuest, http.StatusForbidden, 0},
|
||||
}
|
||||
runTestCases(t, ttCases, testData, clients)
|
||||
})
|
||||
t.Run("local", func(t *testing.T) {
|
||||
th := SetupTestHelperLocalMode(t)
|
||||
defer th.TearDown()
|
||||
clients := setupLocalClients(th)
|
||||
testData := setupData(t, th)
|
||||
ttCases := []TestCase{
|
||||
{"/statistics", methodGet, "", userAnon, http.StatusUnauthorized, 0},
|
||||
{"/statistics", methodGet, "", userNoTeamMember, http.StatusNotImplemented, 0},
|
||||
{"/statistics", methodGet, "", userTeamMember, http.StatusNotImplemented, 0},
|
||||
{"/statistics", methodGet, "", userViewer, http.StatusNotImplemented, 0},
|
||||
{"/statistics", methodGet, "", userCommenter, http.StatusNotImplemented, 0},
|
||||
{"/statistics", methodGet, "", userEditor, http.StatusNotImplemented, 0},
|
||||
{"/statistics", methodGet, "", userAdmin, http.StatusNotImplemented, 1},
|
||||
{"/statistics", methodGet, "", userGuest, http.StatusForbidden, 0},
|
||||
}
|
||||
runTestCases(t, ttCases, testData, clients)
|
||||
})
|
||||
}
|
||||
|
55
server/integrationtests/statistics_test.go
Normal file
55
server/integrationtests/statistics_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package integrationtests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/focalboard/server/client"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStatisticsLocalMode(t *testing.T) {
|
||||
th := SetupTestHelper(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("an unauthenticated client should not be able to get statistics", func(t *testing.T) {
|
||||
th.Logout(th.Client)
|
||||
|
||||
stats, resp := th.Client.GetStatistics()
|
||||
th.CheckUnauthorized(resp)
|
||||
require.Nil(t, stats)
|
||||
})
|
||||
|
||||
t.Run("Check authenticated user, not admin", func(t *testing.T) {
|
||||
th.Login1()
|
||||
|
||||
stats, resp := th.Client.GetStatistics()
|
||||
th.CheckNotImplemented(resp)
|
||||
require.Nil(t, stats)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStatisticsPluginMode(t *testing.T) {
|
||||
th := SetupTestHelperPluginMode(t)
|
||||
defer th.TearDown()
|
||||
|
||||
// Permissions are tested in permissions_test.go
|
||||
// This tests the functionality.
|
||||
t.Run("Check authenticated user, admin", func(t *testing.T) {
|
||||
th.Client = client.NewClient(th.Server.Config().ServerRoot, "")
|
||||
th.Client.HTTPHeader["Mattermost-User-Id"] = userAdmin
|
||||
|
||||
stats, resp := th.Client.GetStatistics()
|
||||
th.CheckOK(resp)
|
||||
require.NotNil(t, stats)
|
||||
|
||||
numberCards := 2
|
||||
th.CreateBoardAndCards("testTeam", model.BoardTypeOpen, numberCards)
|
||||
|
||||
stats, resp = th.Client.GetStatistics()
|
||||
th.CheckOK(resp)
|
||||
require.NotNil(t, stats)
|
||||
require.Equal(t, 1, stats.Boards)
|
||||
require.Equal(t, numberCards, stats.Cards)
|
||||
})
|
||||
}
|
15
server/model/board_statistics.go
Normal file
15
server/model/board_statistics.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
package model
|
||||
|
||||
// BoardsStatistics is the representation of the statistics for the Boards server
|
||||
// swagger:model
|
||||
type BoardsStatistics struct {
|
||||
// The maximum number of cards on the server
|
||||
// required: true
|
||||
Boards int `json:"board_count"`
|
||||
|
||||
// The maximum number of cards on the server
|
||||
// required: true
|
||||
Cards int `json:"card_count"`
|
||||
}
|
@ -347,6 +347,20 @@ func (mr *MockServicesAPIMockRecorder) GetUsersFromProfiles(arg0 interface{}) *g
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsersFromProfiles", reflect.TypeOf((*MockServicesAPI)(nil).GetUsersFromProfiles), arg0)
|
||||
}
|
||||
|
||||
// HasPermissionTo mocks base method.
|
||||
func (m *MockServicesAPI) HasPermissionTo(arg0 string, arg1 *model.Permission) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasPermissionTo", arg0, arg1)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasPermissionTo indicates an expected call of HasPermissionTo.
|
||||
func (mr *MockServicesAPIMockRecorder) HasPermissionTo(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPermissionTo", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionTo), arg0, arg1)
|
||||
}
|
||||
|
||||
// HasPermissionToChannel mocks base method.
|
||||
func (m *MockServicesAPI) HasPermissionToChannel(arg0, arg1 string, arg2 *model.Permission) bool {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -49,6 +49,7 @@ type ServicesAPI interface {
|
||||
CreateMember(teamID string, userID string) (*mm_model.TeamMember, error)
|
||||
|
||||
// Permissions service
|
||||
HasPermissionTo(userID string, permission *mm_model.Permission) bool
|
||||
HasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool
|
||||
HasPermissionToChannel(askingUserID string, channelID string, permission *mm_model.Permission) bool
|
||||
|
||||
|
@ -23,6 +23,10 @@ func New(store permissions.Store, logger mlog.LoggerIFace) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) HasPermissionTo(userID string, permission *mmModel.Permission) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
|
||||
if userID == "" || teamID == "" || permission == nil {
|
||||
return false
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type APIInterface interface {
|
||||
HasPermissionTo(userID string, permission *mmModel.Permission) bool
|
||||
HasPermissionToTeam(userID string, teamID string, permission *mmModel.Permission) bool
|
||||
HasPermissionToChannel(userID string, channelID string, permission *mmModel.Permission) bool
|
||||
}
|
||||
@ -30,6 +31,13 @@ func New(store permissions.Store, api APIInterface, logger mlog.LoggerIFace) *Se
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) HasPermissionTo(userID string, permission *mmModel.Permission) bool {
|
||||
if userID == "" || permission == nil {
|
||||
return false
|
||||
}
|
||||
return s.api.HasPermissionTo(userID, permission)
|
||||
}
|
||||
|
||||
func (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {
|
||||
if userID == "" || teamID == "" || permission == nil {
|
||||
return false
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type PermissionsService interface {
|
||||
HasPermissionTo(userID string, permission *mmModel.Permission) bool
|
||||
HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool
|
||||
HasPermissionToChannel(userID, channelID string, permission *mmModel.Permission) bool
|
||||
HasPermissionToBoard(userID, boardID string, permission *mmModel.Permission) bool
|
||||
|
@ -521,7 +521,8 @@ func (s *SQLStore) getBoardCount(db sq.BaseRunner) (int64, error) {
|
||||
query := s.getQueryBuilder(db).
|
||||
Select("COUNT(*) AS count").
|
||||
From(s.tablePrefix + "boards").
|
||||
Where(sq.Eq{"delete_at": 0})
|
||||
Where(sq.Eq{"delete_at": 0}).
|
||||
Where(sq.Eq{"is_template": false})
|
||||
|
||||
row := query.QueryRow()
|
||||
|
||||
|
@ -257,6 +257,8 @@
|
||||
"SidebarTour.SidebarCategories.Body": "All your boards are now organized under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference.",
|
||||
"SidebarTour.SidebarCategories.Link": "Learn more",
|
||||
"SidebarTour.SidebarCategories.Title": "Sidebar categories",
|
||||
"SiteStats.total_boards": "Total Boards",
|
||||
"SiteStats.total_cards": "Total Cards",
|
||||
"TableComponent.add-icon": "Add icon",
|
||||
"TableComponent.name": "Name",
|
||||
"TableComponent.plus-new": "+ New",
|
||||
|
@ -17,6 +17,7 @@ import {Constants} from './constants'
|
||||
|
||||
import {BoardsCloudLimits} from './boardsCloudLimits'
|
||||
import {TopBoardResponse} from './insights'
|
||||
import {BoardSiteStatistics} from './statistics'
|
||||
|
||||
//
|
||||
// OctoClient is the client interface to the server APIs
|
||||
@ -921,6 +922,18 @@ class OctoClient {
|
||||
return limits
|
||||
}
|
||||
|
||||
async getSiteStatistics(): Promise<BoardSiteStatistics | undefined> {
|
||||
const path = '/api/v2/statistics'
|
||||
const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})
|
||||
if (response.status !== 200) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const stats = (await this.getJson(response, {})) as BoardSiteStatistics
|
||||
Utils.log(`Site Statistics: cards=${stats.card_count} boards=${stats.board_count}`)
|
||||
return stats
|
||||
}
|
||||
|
||||
// insights
|
||||
async getMyTopBoards(timeRange: string, page: number, perPage: number, teamId: string): Promise<TopBoardResponse | undefined> {
|
||||
const path = `/api/v2/users/me/boards/insights?time_range=${timeRange}&page=${page}&per_page=${perPage}&team_id=${teamId}`
|
||||
|
7
webapp/src/statistics/index.ts
Normal file
7
webapp/src/statistics/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export interface BoardSiteStatistics {
|
||||
board_count: number
|
||||
card_count: number
|
||||
}
|
Loading…
Reference in New Issue
Block a user