* Add use case onboarding feature flag
* Add endpoints and storage for whether first admin completed setup
* Add migration for FirstAdminSetupComplete

Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
Nathaniel Allred
2022-02-15 17:46:03 -06:00
коммит произвёл GitHub
родитель dde7ce5535
Коммит 459f54ac9d
8 изменённых файлов: 140 добавлений и 1 удалений

Просмотреть файл

@@ -68,7 +68,8 @@ func (api *API) InitSystem() {
api.BaseRoutes.System.Handle("/notices/{team_id:[A-Za-z0-9]+}", api.APISessionRequired(getProductNotices)).Methods("GET")
api.BaseRoutes.System.Handle("/notices/view", api.APISessionRequired(updateViewedProductNotices)).Methods("PUT")
api.BaseRoutes.System.Handle("/support_packet", api.APISessionRequired(generateSupportPacket)).Methods("GET")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APIHandler(completeOnboarding)).Methods("POST")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APISessionRequired(getOnboarding)).Methods("GET")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APISessionRequired(completeOnboarding)).Methods("POST")
}
func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -881,6 +882,29 @@ func updateViewedProductNotices(c *Context, w http.ResponseWriter, r *http.Reque
ReturnStatusOK(w)
}
func getOnboarding(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("getOnboarding", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
firstAdminCompleteSetupObj, err := c.App.GetOnboarding()
if err != nil {
c.Err = model.NewAppError("getOnboarding", "app.system.get_onboarding_request.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
auditRec.Success()
if err := json.NewEncoder(w).Encode(firstAdminCompleteSetupObj); err != nil {
mlog.Warn("Error while writing response", mlog.Err(err))
}
}
func completeOnboarding(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.Err = model.NewAppError("completeOnboarding", "app.system.complete_onboarding_request.no_first_user", nil, "", http.StatusForbidden)

Просмотреть файл

@@ -653,6 +653,7 @@ type AppIface interface {
GetOAuthLoginEndpoint(w http.ResponseWriter, r *http.Request, service, teamID, action, redirectTo, loginHint string, isMobile bool) (string, *model.AppError)
GetOAuthSignupEndpoint(w http.ResponseWriter, r *http.Request, service, teamID string) (string, *model.AppError)
GetOAuthStateToken(token string) (*model.Token, *model.AppError)
GetOnboarding() (*model.System, *model.AppError)
GetOpenGraphMetadata(requestURL string) ([]byte, error)
GetOrCreateDirectChannel(c *request.Context, userID, otherUserID string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError)
GetOutgoingWebhook(hookID string) (*model.OutgoingWebhook, *model.AppError)

Просмотреть файл

@@ -17,6 +17,7 @@ const GuestRolesCreationMigrationKey = "GuestRolesCreationMigrationComplete"
const SystemConsoleRolesCreationMigrationKey = "SystemConsoleRolesCreationMigrationComplete"
const ContentExtractionConfigDefaultTrueMigrationKey = "ContentExtractionConfigDefaultTrueMigrationComplete"
const PlaybookRolesCreationMigrationKey = "PlaybookRolesCreationMigrationComplete"
const FirstAdminSetupCompleteKey = "FirstAdminSetupComplete"
// This function migrates the default built in roles from code/config to the database.
func (a *App) DoAdvancedPermissionsMigration() {
@@ -434,6 +435,50 @@ func (s *Server) doPlaybooksRolesCreationMigration() {
}
// arbitrary choice, though if there is an longstanding installation with less than 10 messages,
// putting the first admin through onboarding shouldn't be very disruptive.
const existingInstallationPostsThreshold = 10
func (s *Server) doFirstAdminSetupCompleteMigration() {
// Don't run the migration until the flag is turned on.
if !s.Config().FeatureFlags.UseCaseOnboarding {
return
}
// If the migration is already marked as completed, don't do it again.
if _, err := s.Store.System().GetByName(FirstAdminSetupCompleteKey); err == nil {
return
}
teams, err := s.Store.Team().GetAll()
if err != nil {
// can not confirm that admin has started in this case.
return
}
if len(teams) == 0 {
// No teams, and no existing preference. This is most likely a new instance.
// So do not mark that the admin has already done the first time setup.
return
}
// if there are teams, then if this isn't a new installation, there should be posts
postCount, err := s.Store.Post().AnalyticsPostCount("", false, false)
if err != nil || postCount < existingInstallationPostsThreshold {
return
}
system := model.System{
Name: FirstAdminSetupCompleteKey,
Value: "true",
}
if err := s.Store.System().Save(&system); err != nil {
mlog.Critical("Failed to mark first admin setup migration as completed.", mlog.Err(err))
}
}
func (a *App) DoAppMigrations() {
a.Srv().doAppMigrations()
}
@@ -451,4 +496,5 @@ func (s *Server) doAppMigrations() {
}
s.doContentExtractionConfigDefaultTrueMigration()
s.doPlaybooksRolesCreationMigration()
s.doFirstAdminSetupCompleteMigration()
}

Просмотреть файл

@@ -7,9 +7,12 @@ import (
"net/http"
"sync"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v6/app/request"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/store"
)
func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnboardingRequest) *model.AppError {
@@ -56,7 +59,33 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo
}(pluginID)
}
firstAdminCompleteSetupObj := model.System{
Name: model.SystemFirstAdminCompleteSetup,
Value: "true",
}
if err := a.Srv().Store.System().SaveOrUpdate(&firstAdminCompleteSetupObj); err != nil {
return model.NewAppError("setFirstAdminCompleteSetup", "api.error_set_first_admin_complete_setup", nil, err.Error(), http.StatusInternalServerError)
}
wg.Wait()
return nil
}
func (a *App) GetOnboarding() (*model.System, *model.AppError) {
firstAdminCompleteSetupObj, err := a.Srv().Store.System().GetByName(model.SystemFirstAdminCompleteSetup)
if err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
return &model.System{
Name: model.SystemFirstAdminCompleteSetup,
Value: "false",
}, nil
default:
return nil, model.NewAppError("getFirstAdminCompleteSetup", "api.error_get_first_admin_complete_setup", nil, err.Error(), http.StatusInternalServerError)
}
}
return firstAdminCompleteSetupObj, nil
}

Просмотреть файл

@@ -7121,6 +7121,28 @@ func (a *OpenTracingAppLayer) GetOAuthStateToken(token string) (*model.Token, *m
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetOnboarding() (*model.System, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetOnboarding")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetOnboarding()
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetOpenGraphMetadata(requestURL string) ([]byte, error) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetOpenGraphMetadata")

Просмотреть файл

@@ -1681,10 +1681,18 @@
"id": "api.emoji.upload.open.app_error",
"translation": "Unable to create the emoji. An error occurred when trying to open the attached image."
},
{
"id": "api.error_get_first_admin_complete_setup",
"translation": "Error trying to retrieve first admin complete setup from the store."
},
{
"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_complete_setup",
"translation": "Error trying to save first admin complete setup in 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."
@@ -6091,6 +6099,10 @@
"id": "app.system.get_by_name.app_error",
"translation": "Unable to find the system variable."
},
{
"id": "app.system.get_onboarding_request.app_error",
"translation": "Failed to get onboarding completion status."
},
{
"id": "app.system.permanent_delete_by_name.app_error",
"translation": "We could not permanently delete the system table entry."

Просмотреть файл

@@ -67,6 +67,9 @@ type FeatureFlags struct {
NormalizeLdapDNs bool
// Enable special onboarding flow for first admin
UseCaseOnboarding bool
// Enable Workspace optimization dashboard
WorkspaceOptimizationDashboard bool
@@ -95,6 +98,7 @@ func (f *FeatureFlags) SetDefaults() {
f.InlinePostEditing = false
f.BoardsDataRetention = false
f.NormalizeLdapDNs = false
f.UseCaseOnboarding = false
f.WorkspaceOptimizationDashboard = false
f.GraphQL = false
}

Просмотреть файл

@@ -32,6 +32,7 @@ const (
SystemWarnMetricLastRunTimestampKey = "LastWarnMetricRunTimestamp"
SystemMetricSupportEmailNotConfigured = "warn_metric_support_email_not_configured"
SystemFirstAdminVisitMarketplace = "FirstAdminVisitMarketplace"
SystemFirstAdminCompleteSetup = "FirstAdminCompleteSetup"
AwsMeteringReportInterval = 1
AwsMeteringDimensionUsageHrs = "UsageHrs"
UserLimitOverageCycleEndDate = "UserLimitOverageCycleEndDate"