github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/command_loadtest.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"path"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	goi18n "github.com/mattermost/go-i18n/i18n"
    16  	"github.com/mattermost/mattermost-server/v5/mlog"
    17  	"github.com/mattermost/mattermost-server/v5/model"
    18  	"github.com/mattermost/mattermost-server/v5/utils"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  var usage = `Mattermost testing commands to help configure the system
    23  
    24  	COMMANDS:
    25  
    26  	Setup - Creates a testing environment in current team.
    27  		/test setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>
    28  
    29  		Example:
    30  		/test setup teams fuzz 10 20 50
    31  
    32  	Users - Add a specified number of random users with fuzz text to current team.
    33  		/test users [fuzz] <Min Users> <Max Users>
    34  
    35  		Example:
    36  			/test users fuzz 5 10
    37  
    38  	Channels - Add a specified number of random channels with fuzz text to current team.
    39  		/test channels [fuzz] <Min Channels> <Max Channels>
    40  
    41  		Example:
    42  			/test channels fuzz 5 10
    43  
    44  	ThreadedPost - create a large threaded post
    45          /test threaded_post
    46  
    47  	Posts - Add some random posts with fuzz text to current channel.
    48  		/test posts [fuzz] <Min Posts> <Max Posts> <Max Images>
    49  
    50  		Example:
    51  			/test posts fuzz 5 10 3
    52  
    53  	Post - Add post to a channel as another user.
    54  		/test post u=@username p=passwd c=~channelname t=teamname "message"
    55  
    56  		Example:
    57  			/test post u=@user-1 p=user-1 c=~town-square t=ad-1 "message"
    58  
    59  	Url - Add a post containing the text from a given url to current channel.
    60  		/test url
    61  
    62  		Example:
    63  			/test http://www.example.com/sample_file.md
    64  
    65  	Json - Add a post using the JSON file as payload to the current channel.
    66  	        /test json url
    67  
    68  		Example
    69  		/test json http://www.example.com/sample_body.json
    70  
    71  `
    72  
    73  const (
    74  	CMD_TEST = "test"
    75  )
    76  
    77  var (
    78  	userRE    = regexp.MustCompile(`u=@([^\s]+)`)
    79  	passwdRE  = regexp.MustCompile(`p=([^\s]+)`)
    80  	teamRE    = regexp.MustCompile(`t=([^\s]+)`)
    81  	channelRE = regexp.MustCompile(`c=~([^\s]+)`)
    82  	messageRE = regexp.MustCompile(`"(.*)"`)
    83  )
    84  
    85  type LoadTestProvider struct {
    86  }
    87  
    88  func init() {
    89  	RegisterCommandProvider(&LoadTestProvider{})
    90  }
    91  
    92  func (me *LoadTestProvider) GetTrigger() string {
    93  	return CMD_TEST
    94  }
    95  
    96  func (me *LoadTestProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command {
    97  	if !*a.Config().ServiceSettings.EnableTesting {
    98  		return nil
    99  	}
   100  	return &model.Command{
   101  		Trigger:          CMD_TEST,
   102  		AutoComplete:     false,
   103  		AutoCompleteDesc: "Debug Load Testing",
   104  		AutoCompleteHint: "help",
   105  		DisplayName:      "test",
   106  	}
   107  }
   108  
   109  func (me *LoadTestProvider) DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
   110  	commandResponse, err := me.doCommand(a, args, message)
   111  	if err != nil {
   112  		mlog.Error("failed command /"+CMD_TEST, mlog.Err(err))
   113  	}
   114  
   115  	return commandResponse
   116  }
   117  
   118  func (me *LoadTestProvider) doCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   119  	//This command is only available when EnableTesting is true
   120  	if !*a.Config().ServiceSettings.EnableTesting {
   121  		return &model.CommandResponse{}, nil
   122  	}
   123  
   124  	if strings.HasPrefix(message, "setup") {
   125  		return me.SetupCommand(a, args, message)
   126  	}
   127  
   128  	if strings.HasPrefix(message, "users") {
   129  		return me.UsersCommand(a, args, message)
   130  	}
   131  
   132  	if strings.HasPrefix(message, "activate_user") {
   133  		return me.ActivateUserCommand(a, args, message)
   134  	}
   135  
   136  	if strings.HasPrefix(message, "deactivate_user") {
   137  		return me.DeActivateUserCommand(a, args, message)
   138  	}
   139  
   140  	if strings.HasPrefix(message, "channels") {
   141  		return me.ChannelsCommand(a, args, message)
   142  	}
   143  
   144  	if strings.HasPrefix(message, "posts") {
   145  		return me.PostsCommand(a, args, message)
   146  	}
   147  
   148  	if strings.HasPrefix(message, "post") {
   149  		return me.PostCommand(a, args, message)
   150  	}
   151  
   152  	if strings.HasPrefix(message, "threaded_post") {
   153  		return me.ThreadedPostCommand(a, args, message)
   154  	}
   155  
   156  	if strings.HasPrefix(message, "url") {
   157  		return me.UrlCommand(a, args, message)
   158  	}
   159  
   160  	if strings.HasPrefix(message, "json") {
   161  		return me.JsonCommand(a, args, message)
   162  	}
   163  
   164  	return me.HelpCommand(args, message), nil
   165  }
   166  
   167  func (me *LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse {
   168  	return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   169  }
   170  
   171  func (me *LoadTestProvider) SetupCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   172  	tokens := strings.Fields(strings.TrimPrefix(message, "setup"))
   173  	doTeams := contains(tokens, "teams")
   174  	doFuzz := contains(tokens, "fuzz")
   175  
   176  	numArgs := 0
   177  	if doTeams {
   178  		numArgs++
   179  	}
   180  	if doFuzz {
   181  		numArgs++
   182  	}
   183  
   184  	var numTeams int
   185  	var numChannels int
   186  	var numUsers int
   187  	var numPosts int
   188  
   189  	// Defaults
   190  	numTeams = 10
   191  	numChannels = 10
   192  	numUsers = 10
   193  	numPosts = 10
   194  
   195  	if doTeams {
   196  		if (len(tokens) - numArgs) >= 4 {
   197  			numTeams, _ = strconv.Atoi(tokens[numArgs+0])
   198  			numChannels, _ = strconv.Atoi(tokens[numArgs+1])
   199  			numUsers, _ = strconv.Atoi(tokens[numArgs+2])
   200  			numPosts, _ = strconv.Atoi(tokens[numArgs+3])
   201  		}
   202  	} else {
   203  		if (len(tokens) - numArgs) >= 3 {
   204  			numChannels, _ = strconv.Atoi(tokens[numArgs+0])
   205  			numUsers, _ = strconv.Atoi(tokens[numArgs+1])
   206  			numPosts, _ = strconv.Atoi(tokens[numArgs+2])
   207  		}
   208  	}
   209  	client := model.NewAPIv4Client(args.SiteURL)
   210  
   211  	if doTeams {
   212  		if err := a.CreateBasicUser(client); err != nil {
   213  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   214  		}
   215  		_, resp := client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD)
   216  		if resp.Error != nil {
   217  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   218  		}
   219  		environment, err := CreateTestEnvironmentWithTeams(
   220  			a,
   221  			client,
   222  			utils.Range{Begin: numTeams, End: numTeams},
   223  			utils.Range{Begin: numChannels, End: numChannels},
   224  			utils.Range{Begin: numUsers, End: numUsers},
   225  			utils.Range{Begin: numPosts, End: numPosts},
   226  			doFuzz)
   227  		if err != nil {
   228  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   229  		}
   230  
   231  		mlog.Info("Testing environment created")
   232  		for i := 0; i < len(environment.Teams); i++ {
   233  			mlog.Info("Team Created: " + environment.Teams[i].Name)
   234  			mlog.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD)
   235  		}
   236  	} else {
   237  		team, err := a.Srv().Store.Team().Get(args.TeamId)
   238  		if err != nil {
   239  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   240  		}
   241  
   242  		CreateTestEnvironmentInTeam(
   243  			a,
   244  			client,
   245  			team,
   246  			utils.Range{Begin: numChannels, End: numChannels},
   247  			utils.Range{Begin: numUsers, End: numUsers},
   248  			utils.Range{Begin: numPosts, End: numPosts},
   249  			doFuzz)
   250  	}
   251  
   252  	return &model.CommandResponse{Text: "Created environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   253  }
   254  
   255  func (me *LoadTestProvider) ActivateUserCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   256  	user_id := strings.TrimSpace(strings.TrimPrefix(message, "activate_user"))
   257  	if err := a.UpdateUserActive(user_id, true); err != nil {
   258  		return &model.CommandResponse{Text: "Failed to activate user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   259  	}
   260  
   261  	return &model.CommandResponse{Text: "Activated user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   262  }
   263  
   264  func (me *LoadTestProvider) DeActivateUserCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   265  	user_id := strings.TrimSpace(strings.TrimPrefix(message, "deactivate_user"))
   266  	if err := a.UpdateUserActive(user_id, false); err != nil {
   267  		return &model.CommandResponse{Text: "Failed to deactivate user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   268  	}
   269  
   270  	return &model.CommandResponse{Text: "DeActivated user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   271  }
   272  
   273  func (me *LoadTestProvider) UsersCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   274  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
   275  
   276  	doFuzz := false
   277  	if strings.Index(cmd, "fuzz") == 0 {
   278  		doFuzz = true
   279  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   280  	}
   281  
   282  	usersr, ok := parseRange(cmd, "")
   283  	if !ok {
   284  		usersr = utils.Range{Begin: 2, End: 5}
   285  	}
   286  
   287  	team, err := a.Srv().Store.Team().Get(args.TeamId)
   288  	if err != nil {
   289  		return &model.CommandResponse{Text: "Failed to add users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   290  	}
   291  
   292  	client := model.NewAPIv4Client(args.SiteURL)
   293  	userCreator := NewAutoUserCreator(a, client, team)
   294  	userCreator.Fuzzy = doFuzz
   295  	userCreator.CreateTestUsers(usersr)
   296  
   297  	return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   298  }
   299  
   300  func (me *LoadTestProvider) ChannelsCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   301  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
   302  
   303  	doFuzz := false
   304  	if strings.Index(cmd, "fuzz") == 0 {
   305  		doFuzz = true
   306  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   307  	}
   308  
   309  	channelsr, ok := parseRange(cmd, "")
   310  	if !ok {
   311  		channelsr = utils.Range{Begin: 2, End: 5}
   312  	}
   313  
   314  	team, err := a.Srv().Store.Team().Get(args.TeamId)
   315  	if err != nil {
   316  		return &model.CommandResponse{Text: "Failed to add channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   317  	}
   318  
   319  	channelCreator := NewAutoChannelCreator(a, team, args.UserId)
   320  	channelCreator.Fuzzy = doFuzz
   321  	if _, err := channelCreator.CreateTestChannels(channelsr); err != nil {
   322  		return &model.CommandResponse{Text: "Failed to create test channels: " + err.Error(), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   323  	}
   324  
   325  	return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   326  }
   327  
   328  func (me *LoadTestProvider) ThreadedPostCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   329  	var usernames []string
   330  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   331  	if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil {
   332  		usernames = make([]string, len(profileUsers))
   333  		i := 0
   334  		for _, userprof := range profileUsers {
   335  			usernames[i] = userprof.Username
   336  			i++
   337  		}
   338  	}
   339  
   340  	testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId)
   341  	testPoster.Fuzzy = true
   342  	testPoster.Users = usernames
   343  	rpost, err2 := testPoster.CreateRandomPost()
   344  	if err2 != nil {
   345  		return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err2
   346  	}
   347  	for i := 0; i < 1000; i++ {
   348  		testPoster.CreateRandomPostNested(rpost.Id, rpost.Id)
   349  	}
   350  
   351  	return &model.CommandResponse{Text: "Added threaded post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   352  }
   353  
   354  func (me *LoadTestProvider) PostsCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   355  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
   356  
   357  	doFuzz := false
   358  	if strings.Index(cmd, "fuzz") == 0 {
   359  		doFuzz = true
   360  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   361  	}
   362  
   363  	postsr, ok := parseRange(cmd, "")
   364  	if !ok {
   365  		postsr = utils.Range{Begin: 20, End: 30}
   366  	}
   367  
   368  	tokens := strings.Fields(cmd)
   369  	rimages := utils.Range{Begin: 0, End: 0}
   370  	if len(tokens) >= 3 {
   371  		if numImages, err := strconv.Atoi(tokens[2]); err == nil {
   372  			rimages = utils.Range{Begin: numImages, End: numImages}
   373  		}
   374  	}
   375  
   376  	var usernames []string
   377  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   378  	if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil {
   379  		usernames = make([]string, len(profileUsers))
   380  		i := 0
   381  		for _, userprof := range profileUsers {
   382  			usernames[i] = userprof.Username
   383  			i++
   384  		}
   385  	}
   386  
   387  	testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId)
   388  	testPoster.Fuzzy = doFuzz
   389  	testPoster.Users = usernames
   390  
   391  	numImages := utils.RandIntFromRange(rimages)
   392  	numPosts := utils.RandIntFromRange(postsr)
   393  	for i := 0; i < numPosts; i++ {
   394  		testPoster.HasImage = (i < numImages)
   395  		_, err := testPoster.CreateRandomPost()
   396  		if err != nil {
   397  			return &model.CommandResponse{Text: "Failed to add posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   398  		}
   399  
   400  	}
   401  
   402  	return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   403  }
   404  
   405  func getMatch(re *regexp.Regexp, text string) string {
   406  	if match := re.FindStringSubmatch(text); match != nil {
   407  		return match[1]
   408  	}
   409  
   410  	return ""
   411  }
   412  
   413  func (me *LoadTestProvider) PostCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   414  	textMessage := getMatch(messageRE, message)
   415  	if textMessage == "" {
   416  		return &model.CommandResponse{Text: "No message to post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   417  	}
   418  
   419  	teamName := getMatch(teamRE, message)
   420  	team, err := a.GetTeamByName(teamName)
   421  	if err != nil {
   422  		return &model.CommandResponse{Text: "Failed to get a team", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   423  	}
   424  
   425  	channelName := getMatch(channelRE, message)
   426  	channel, err := a.GetChannelByName(channelName, team.Id, true)
   427  	if err != nil {
   428  		return &model.CommandResponse{Text: "Failed to get a channel", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   429  	}
   430  
   431  	passwd := getMatch(passwdRE, message)
   432  	username := getMatch(userRE, message)
   433  	user, err := a.GetUserByUsername(username)
   434  	if err != nil {
   435  		return &model.CommandResponse{Text: "Failed to get a user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   436  	}
   437  
   438  	client := model.NewAPIv4Client(args.SiteURL)
   439  	_, resp := client.LoginById(user.Id, passwd)
   440  	if resp.Error != nil {
   441  		return &model.CommandResponse{Text: "Failed to login a user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   442  	}
   443  
   444  	post := &model.Post{
   445  		ChannelId: channel.Id,
   446  		Message:   textMessage,
   447  	}
   448  	_, resp = client.CreatePost(post)
   449  	if resp.Error != nil {
   450  		return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   451  	}
   452  
   453  	return &model.CommandResponse{Text: "Added a post to " + channel.DisplayName, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   454  }
   455  
   456  func (me *LoadTestProvider) UrlCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   457  	url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
   458  	if len(url) == 0 {
   459  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   460  	}
   461  
   462  	// provide a shortcut to easily access tests stored in doc/developer/tests
   463  	if !strings.HasPrefix(url, "http") {
   464  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   465  
   466  		if path.Ext(url) == "" {
   467  			url += ".md"
   468  		}
   469  	}
   470  
   471  	r, err := http.Get(url)
   472  	if err != nil {
   473  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   474  	}
   475  	defer func() {
   476  		io.Copy(ioutil.Discard, r.Body)
   477  		r.Body.Close()
   478  	}()
   479  
   480  	if r.StatusCode > 400 {
   481  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, errors.Errorf("unexpected status code %d", r.StatusCode)
   482  	}
   483  
   484  	bytes := make([]byte, 4000)
   485  
   486  	// break contents into 4000 byte posts
   487  	for {
   488  		length, err := r.Body.Read(bytes)
   489  		if err != nil && err != io.EOF {
   490  			return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   491  		}
   492  
   493  		if length == 0 {
   494  			break
   495  		}
   496  
   497  		post := &model.Post{}
   498  		post.Message = string(bytes[:length])
   499  		post.ChannelId = args.ChannelId
   500  		post.UserId = args.UserId
   501  
   502  		if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   503  			return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   504  		}
   505  	}
   506  
   507  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   508  }
   509  
   510  func (me *LoadTestProvider) JsonCommand(a *App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   511  	url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
   512  	if len(url) == 0 {
   513  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   514  	}
   515  
   516  	// provide a shortcut to easily access tests stored in doc/developer/tests
   517  	if !strings.HasPrefix(url, "http") {
   518  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   519  
   520  		if path.Ext(url) == "" {
   521  			url += ".json"
   522  		}
   523  	}
   524  
   525  	r, err := http.Get(url)
   526  	if err != nil {
   527  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   528  	}
   529  
   530  	if r.StatusCode > 400 {
   531  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, errors.Errorf("unexpected status code %d", r.StatusCode)
   532  	}
   533  	defer func() {
   534  		io.Copy(ioutil.Discard, r.Body)
   535  		r.Body.Close()
   536  	}()
   537  
   538  	post := model.PostFromJson(r.Body)
   539  	post.ChannelId = args.ChannelId
   540  	post.UserId = args.UserId
   541  	if post.Message == "" {
   542  		post.Message = message
   543  	}
   544  
   545  	if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   546  		return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   547  	}
   548  
   549  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   550  }
   551  
   552  func parseRange(command string, cmd string) (utils.Range, bool) {
   553  	tokens := strings.Fields(strings.TrimPrefix(command, cmd))
   554  	var begin int
   555  	var end int
   556  	var err1 error
   557  	var err2 error
   558  	switch {
   559  	case len(tokens) == 1:
   560  		begin, err1 = strconv.Atoi(tokens[0])
   561  		end = begin
   562  		if err1 != nil {
   563  			return utils.Range{Begin: 0, End: 0}, false
   564  		}
   565  	case len(tokens) >= 2:
   566  		begin, err1 = strconv.Atoi(tokens[0])
   567  		end, err2 = strconv.Atoi(tokens[1])
   568  		if err1 != nil || err2 != nil {
   569  			return utils.Range{Begin: 0, End: 0}, false
   570  		}
   571  	default:
   572  		return utils.Range{Begin: 0, End: 0}, false
   573  	}
   574  	return utils.Range{Begin: begin, End: end}, true
   575  }
   576  
   577  func contains(items []string, token string) bool {
   578  	for _, elem := range items {
   579  		if elem == token {
   580  			return true
   581  		}
   582  	}
   583  	return false
   584  }