PLT-2525: Render attachment fields similar to Slack

* Render attachment fields similar to Slack

* Add /loadtest json url command

This allows us to easily create test posts with 
more props like Slack attachments
Этот коммит содержится в:
Andy Lo-A-Foe
2016-04-06 14:23:26 +02:00
коммит произвёл Christopher Speller
родитель 1954c44993
Коммит 1a1fd39cf8
4 изменённых файлов: 209 добавлений и 62 удалений

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

@@ -49,6 +49,11 @@ var usage = `Mattermost load testing commands to help configure the system
Example:
/loadtest http://www.example.com/sample_file.md
Json - Add a post using the JSON file as payload to the current channel.
/loadtest json url
Example
/loadtest json http://www.example.com/sample_body.json
`
@@ -105,7 +110,9 @@ func (me *LoadTestProvider) DoCommand(c *Context, channelId string, message stri
if strings.HasPrefix(message, "url") {
return me.UrlCommand(c, channelId, message)
}
if strings.HasPrefix(message, "json") {
return me.JsonCommand(c, channelId, message)
}
return me.HelpCommand(c, channelId, message)
}
@@ -330,6 +337,42 @@ func (me *LoadTestProvider) UrlCommand(c *Context, channelId string, message str
return &model.CommandResponse{Text: "Loading data...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) JsonCommand(c *Context, channelId string, message string) *model.CommandResponse {
url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
if len(url) == 0 {
return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
// provide a shortcut to easily access tests stored in doc/developer/tests
if !strings.HasPrefix(url, "http") {
url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url
if path.Ext(url) == "" {
url += ".json"
}
}
var contents io.ReadCloser
if r, err := http.Get(url); err != nil {
return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else if r.StatusCode > 400 {
return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
contents = r.Body
}
post := model.PostFromJson(contents)
post.ChannelId = channelId
if post.Message == "" {
post.Message = message
}
if _, err := CreatePost(c, post, false); err != nil {
return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
return &model.CommandResponse{Text: "Loading data...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func parseRange(command string, cmd string) (utils.Range, bool) {
tokens := strings.Fields(strings.TrimPrefix(command, cmd))
var begin int

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

@@ -221,3 +221,44 @@ func TestLoadTestUrlCommands(t *testing.T) {
time.Sleep(2 * time.Second)
}
func TestLoadTestJsonCommands(t *testing.T) {
Setup()
// enable testing to use /loadtest but don't save it since we don't want to overwrite config.json
enableTesting := utils.Cfg.ServiceSettings.EnableTesting
defer func() {
utils.Cfg.ServiceSettings.EnableTesting = enableTesting
}()
utils.Cfg.ServiceSettings.EnableTesting = true
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel)
command := "/loadtest json "
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Command must contain a url" {
t.Fatal("/loadtest url with no url should've failed")
}
command = "/loadtest json http://missingfiletonwhere/path/asdf/qwerty"
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Unable to get file" {
t.Log(r.Text)
t.Fatal("/loadtest url with invalid url should've failed")
}
command = "/loadtest url https://secure.beldienst.nl/test.json" // Chicken-egg so will replace with mattermost/platform URL soon
if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading data..." {
t.Fatal("/loadtest url for README.md should've executed")
}
time.Sleep(2 * time.Second)
}

56
tests/test-slack-attachments.json Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
{
"message": "Hello world",
"props": {
"attachments": [
{
"color": "#7CD197",
"fields": [
{
"short": false,
"title": "Area",
"value": "Testing with a very long piece of text that will take up the whole width of the table. This is one more sentence to really make it a long field."
},
{
"short": true,
"title": "Iteration",
"value": "Testing"
},
{
"short": true,
"title": "State",
"value": "New"
},
{
"short": false,
"title": "Reason",
"value": "New defect reported"
},
{
"short": false,
"title": "Random field",
"value": "This is a field which is not marked as short so it should be rendered on a separate row"
},
{
"short": true,
"title": "Short 1",
"value": "Short field"
},
{
"short": true,
"title": "Short 2",
"value": "Another one"
}
],
"mrkdwn_in": [
"pretext"
],
"pretext": "Some text here",
"text": "This is the text of the attachment. It should appear just above the image",
"thumb_url": "https://slack.global.ssl.fastly.net/7bf4/img/services/jenkins-ci_128.png",
"title": "A slack attachment",
"title_link": "https://www.google.com"
}
]
},
"type": "slack_attachment"
}

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

@@ -87,75 +87,82 @@ class PostAttachment extends React.Component {
return '';
}
const compactTable = fields.filter((field) => field.short).length > 0;
let tHead;
let tBody;
let fieldTables = [];
if (compactTable) {
let headerCols = [];
let bodyCols = [];
let headerCols = [];
let bodyCols = [];
let rowPos = 0;
let lastWasLong = false;
let nrTables = 0;
fields.forEach((field, i) => {
headerCols.push(
<th
className='attachment___field-caption'
key={'attachment__field-caption-' + i}
fields.forEach((field, i) => {
if (rowPos === 2 || !(field.short === true) || lastWasLong) {
fieldTables.push(
<table
className='attachment___fields'
key={'attachment__table__' + nrTables}
>
{field.title}
</th>
<thead>
<tr>
{headerCols}
</tr>
</thead>
<tbody>
<tr>
{bodyCols}
</tr>
</tbody>
</table>
);
bodyCols.push(
<td
className='attachment___field'
key={'attachment__field-' + i}
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
headerCols = [];
bodyCols = [];
rowPos = 0;
nrTables += 1;
lastWasLong = false;
}
headerCols.push(
<th
className='attachment___field-caption'
key={'attachment__field-caption-' + i + '__' + nrTables}
width='50%'
>
{field.title}
</th>
);
bodyCols.push(
<td
className='attachment___field'
key={'attachment__field-' + i + '__' + nrTables}
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
>
</td>
);
rowPos += 1;
lastWasLong = !(field.short === true);
});
if (headerCols.length > 0) { // Flush last fields
fieldTables.push(
<table
className='attachment___fields'
key={'attachment__table__' + nrTables}
>
</td>
<thead>
<tr>
{headerCols}
</tr>
</thead>
<tbody>
<tr>
{bodyCols}
</tr>
</tbody>
</table>
);
});
tHead = (
<tr>
{headerCols}
</tr>
);
tBody = (
<tr>
{bodyCols}
</tr>
);
} else {
tBody = [];
fields.forEach((field, i) => {
tBody.push(
<tr key={'attachment__field-' + i}>
<td
className='attachment___field-caption'
>
{field.title}
</td>
<td
className='attachment___field'
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
>
</td>
</tr>
);
});
}
return (
<table
className='attachment___fields'
>
<thead>
{tHead}
</thead>
<tbody>
{tBody}
</tbody>
</table>
<div>
{fieldTables}
</div>
);
}