MM-32882: Add an unread badge/marker to the Main Menu icon and the ‘Plugin Marketplace’ Menu Item (#16992)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
16e6296c54
Коммит
ca9f4c9ed8
@@ -16,6 +16,8 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/audit"
|
"github.com/mattermost/mattermost-server/v5/audit"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/store"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,6 +40,9 @@ func (api *API) InitPlugin() {
|
|||||||
api.BaseRoutes.Plugins.Handle("/webapp", api.ApiHandler(getWebappPlugins)).Methods("GET")
|
api.BaseRoutes.Plugins.Handle("/webapp", api.ApiHandler(getWebappPlugins)).Methods("GET")
|
||||||
|
|
||||||
api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(getMarketplacePlugins)).Methods("GET")
|
api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(getMarketplacePlugins)).Methods("GET")
|
||||||
|
|
||||||
|
api.BaseRoutes.Plugins.Handle("/marketplace/first_admin_visit", api.ApiHandler(setFirstAdminVisitMarketplaceStatus)).Methods("POST")
|
||||||
|
api.BaseRoutes.Plugins.Handle("/marketplace/first_admin_visit", api.ApiSessionRequired(getFirstAdminVisitMarketplaceStatus)).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
|
func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -383,3 +388,61 @@ func installPlugin(c *Context, w http.ResponseWriter, plugin io.ReadSeeker, forc
|
|||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
w.Write([]byte(manifest.ToJson()))
|
w.Write([]byte(manifest.ToJson()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setFirstAdminVisitMarketplaceStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
auditRec := c.MakeAuditRecord("setFirstAdminVisitMarketplaceStatus", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
c.LogAudit("attempt")
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
firstAdminVisitMarketplaceObj := model.System{
|
||||||
|
Name: model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE,
|
||||||
|
Value: "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.App.Srv().Store.System().SaveOrUpdate(&firstAdminVisitMarketplaceObj); err != nil {
|
||||||
|
c.Err = model.NewAppError("setFirstAdminVisitMarketplaceStatus", "api.error_set_first_admin_visit_marketplace_status", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message := model.NewWebSocketEvent(model.WEBSOCKET_FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED, "", "", "", nil)
|
||||||
|
message.Add("firstAdminVisitMarketplaceStatus", firstAdminVisitMarketplaceObj.Value)
|
||||||
|
c.App.Publish(message)
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
ReturnStatusOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFirstAdminVisitMarketplaceStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
auditRec := c.MakeAuditRecord("getFirstAdminVisitMarketplaceStatus", audit.Fail)
|
||||||
|
defer c.LogAuditRec(auditRec)
|
||||||
|
c.LogAudit("attempt")
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
firstAdminVisitMarketplaceObj, err := c.App.Srv().Store.System().GetByName(model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE)
|
||||||
|
if err != nil {
|
||||||
|
var nfErr *store.ErrNotFound
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &nfErr):
|
||||||
|
firstAdminVisitMarketplaceObj = &model.System{
|
||||||
|
Name: model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE,
|
||||||
|
Value: "false",
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.Err = model.NewAppError("getFirstAdminVisitMarketplaceStatus", "api.error_get_first_admin_visit_marketplace_status", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auditRec.Success()
|
||||||
|
w.Write([]byte(firstAdminVisitMarketplaceObj.ToJson()))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1376,6 +1376,14 @@
|
|||||||
"id": "api.emoji.upload.open.app_error",
|
"id": "api.emoji.upload.open.app_error",
|
||||||
"translation": "Unable to create the emoji. An error occurred when trying to open the attached image."
|
"translation": "Unable to create the emoji. An error occurred when trying to open the attached image."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.error_get_first_admin_visit_marketplace_status",
|
||||||
|
"translation": "Error trying to retrieve the first admin visit marketplace status from the store."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.error_set_first_admin_visit_marketplace_status",
|
||||||
|
"translation": "Error trying to save the first admin visit marketplace status in the store."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.export.export_not_found.app_error",
|
"id": "api.export.export_not_found.app_error",
|
||||||
"translation": "Unable to find export file."
|
"translation": "Unable to find export file."
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const (
|
|||||||
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500 = "warn_metric_number_of_active_users_500"
|
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500 = "warn_metric_number_of_active_users_500"
|
||||||
SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M = "warn_metric_number_of_posts_2M"
|
SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M = "warn_metric_number_of_posts_2M"
|
||||||
SYSTEM_WARN_METRIC_LAST_RUN_TIMESTAMP_KEY = "LastWarnMetricRunTimestamp"
|
SYSTEM_WARN_METRIC_LAST_RUN_TIMESTAMP_KEY = "LastWarnMetricRunTimestamp"
|
||||||
|
SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE = "FirstAdminVisitMarketplace"
|
||||||
AWS_METERING_REPORT_INTERVAL = 1
|
AWS_METERING_REPORT_INTERVAL = 1
|
||||||
AWS_METERING_DIMENSION_USAGE_HRS = "UsageHrs"
|
AWS_METERING_DIMENSION_USAGE_HRS = "UsageHrs"
|
||||||
USER_LIMIT_OVERAGE_CYCLE_END_DATE = "UserLimitOverageCycleEndDate"
|
USER_LIMIT_OVERAGE_CYCLE_END_DATE = "UserLimitOverageCycleEndDate"
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const (
|
|||||||
WEBSOCKET_EVENT_THREAD_UPDATED = "thread_updated"
|
WEBSOCKET_EVENT_THREAD_UPDATED = "thread_updated"
|
||||||
WEBSOCKET_EVENT_THREAD_FOLLOW_CHANGED = "thread_follow_changed"
|
WEBSOCKET_EVENT_THREAD_FOLLOW_CHANGED = "thread_follow_changed"
|
||||||
WEBSOCKET_EVENT_THREAD_READ_CHANGED = "thread_read_changed"
|
WEBSOCKET_EVENT_THREAD_READ_CHANGED = "thread_read_changed"
|
||||||
|
WEBSOCKET_FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED = "first_admin_visit_marketplace_status_received"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebSocketMessage interface {
|
type WebSocketMessage interface {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package sqlstore
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -99,6 +100,9 @@ func (s SqlSystemStore) Get() (model.StringMap, error) {
|
|||||||
func (s SqlSystemStore) GetByName(name string) (*model.System, error) {
|
func (s SqlSystemStore) GetByName(name string) (*model.System, error) {
|
||||||
var system model.System
|
var system model.System
|
||||||
if err := s.GetMaster().SelectOne(&system, "SELECT * FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
|
if err := s.GetMaster().SelectOne(&system, "SELECT * FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, store.NewErrNotFound("System", fmt.Sprintf("name=%s", system.Name))
|
||||||
|
}
|
||||||
return nil, errors.Wrapf(err, "failed to get system property with name=%s", system.Name)
|
return nil, errors.Wrapf(err, "failed to get system property with name=%s", system.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ func TestSystemStore(t *testing.T, ss store.Store) {
|
|||||||
testInsertIfExists(t, ss)
|
testInsertIfExists(t, ss)
|
||||||
})
|
})
|
||||||
t.Run("SaveOrUpdateWithWarnMetricHandling", func(t *testing.T) { testSystemStoreSaveOrUpdateWithWarnMetricHandling(t, ss) })
|
t.Run("SaveOrUpdateWithWarnMetricHandling", func(t *testing.T) { testSystemStoreSaveOrUpdateWithWarnMetricHandling(t, ss) })
|
||||||
|
t.Run("GetByNameNoEntries", func(t *testing.T) { testSystemStoreGetByNameNoEntries(t, ss) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSystemStore(t *testing.T, ss store.Store) {
|
func testSystemStore(t *testing.T, ss store.Store) {
|
||||||
@@ -83,6 +84,14 @@ func testSystemStoreSaveOrUpdateWithWarnMetricHandling(t *testing.T, ss store.St
|
|||||||
assert.Equal(t, val1, val2)
|
assert.Equal(t, val1, val2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSystemStoreGetByNameNoEntries(t *testing.T, ss store.Store) {
|
||||||
|
res, nErr := ss.System().GetByName(model.SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE)
|
||||||
|
_, ok := nErr.(*store.ErrNotFound)
|
||||||
|
require.Error(t, nErr)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
func testSystemStorePermanentDeleteByName(t *testing.T, ss store.Store) {
|
func testSystemStorePermanentDeleteByName(t *testing.T, ss store.Store) {
|
||||||
s1 := &model.System{Name: model.NewId(), Value: "value"}
|
s1 := &model.System{Name: model.NewId(), Value: "value"}
|
||||||
s2 := &model.System{Name: model.NewId(), Value: "value"}
|
s2 := &model.System{Name: model.NewId(), Value: "value"}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user