Add default message priority for applications
Co-authored-by: Jannis Mattheis <contact@jmattheis.de>
This commit is contained in:
parent
aedc3e2ba6
commit
72bd8c8ba6
|
|
@ -44,6 +44,10 @@ type ApplicationParams struct {
|
||||||
//
|
//
|
||||||
// example: Backup server for the interwebs
|
// example: Backup server for the interwebs
|
||||||
Description string `form:"description" query:"description" json:"description"`
|
Description string `form:"description" query:"description" json:"description"`
|
||||||
|
// The default priority of messages sent by this application. Defaults to 0.
|
||||||
|
//
|
||||||
|
// example: 5
|
||||||
|
DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateApplication creates an application and returns the access token.
|
// CreateApplication creates an application and returns the access token.
|
||||||
|
|
@ -85,6 +89,7 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) {
|
||||||
app := model.Application{
|
app := model.Application{
|
||||||
Name: applicationParams.Name,
|
Name: applicationParams.Name,
|
||||||
Description: applicationParams.Description,
|
Description: applicationParams.Description,
|
||||||
|
DefaultPriority: applicationParams.DefaultPriority,
|
||||||
Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists),
|
Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists),
|
||||||
UserID: auth.GetUserID(ctx),
|
UserID: auth.GetUserID(ctx),
|
||||||
Internal: false,
|
Internal: false,
|
||||||
|
|
@ -245,6 +250,7 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) {
|
||||||
if err := ctx.Bind(&applicationParams); err == nil {
|
if err := ctx.Bind(&applicationParams); err == nil {
|
||||||
app.Description = applicationParams.Description
|
app.Description = applicationParams.Description
|
||||||
app.Name = applicationParams.Name
|
app.Name = applicationParams.Name
|
||||||
|
app.DefaultPriority = applicationParams.DefaultPriority
|
||||||
|
|
||||||
if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success {
|
if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ func (s *ApplicationSuite) Test_ensureApplicationHasCorrectJsonRepresentation()
|
||||||
Image: "asd",
|
Image: "asd",
|
||||||
Internal: true,
|
Internal: true,
|
||||||
}
|
}
|
||||||
test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true}`)
|
test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true, "defaultPriority":0}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApplicationSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
|
func (s *ApplicationSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
|
||||||
|
|
@ -527,6 +527,29 @@ func (s *ApplicationSuite) Test_UpdateApplicationName_expectSuccess() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) Test_UpdateApplicationDefaultPriority_expectSuccess() {
|
||||||
|
s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
|
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.withFormData("name=name&description=&defaultPriority=4")
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
expected := &model.Application{
|
||||||
|
ID: 2,
|
||||||
|
Token: "app-2",
|
||||||
|
UserID: 5,
|
||||||
|
Name: "name",
|
||||||
|
Description: "",
|
||||||
|
DefaultPriority: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
if app, err := s.db.GetApplicationByID(2); assert.NoError(s.T(), err) {
|
||||||
|
assert.Equal(s.T(), expected, app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ApplicationSuite) Test_UpdateApplication_preservesImage() {
|
func (s *ApplicationSuite) Test_UpdateApplication_preservesImage() {
|
||||||
app := s.db.User(5).NewAppWithToken(2, "app-2")
|
app := s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
app.Image = "existing.png"
|
app.Image = "existing.png"
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,11 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) {
|
||||||
if strings.TrimSpace(message.Title) == "" {
|
if strings.TrimSpace(message.Title) == "" {
|
||||||
message.Title = application.Name
|
message.Title = application.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if message.Priority == nil {
|
||||||
|
message.Priority = &application.DefaultPriority
|
||||||
|
}
|
||||||
|
|
||||||
message.Date = timeNow()
|
message.Date = timeNow()
|
||||||
message.ID = 0
|
message.ID = 0
|
||||||
msgInternal := toInternalMessage(&message)
|
msgInternal := toInternalMessage(&message)
|
||||||
|
|
@ -388,9 +393,12 @@ func toInternalMessage(msg *model.MessageExternal) *model.Message {
|
||||||
ApplicationID: msg.ApplicationID,
|
ApplicationID: msg.ApplicationID,
|
||||||
Message: msg.Message,
|
Message: msg.Message,
|
||||||
Title: msg.Title,
|
Title: msg.Title,
|
||||||
Priority: msg.Priority,
|
|
||||||
Date: msg.Date,
|
Date: msg.Date,
|
||||||
}
|
}
|
||||||
|
if msg.Priority != nil {
|
||||||
|
res.Priority = *msg.Priority
|
||||||
|
}
|
||||||
|
|
||||||
if msg.Extras != nil {
|
if msg.Extras != nil {
|
||||||
res.Extras, _ = json.Marshal(msg.Extras)
|
res.Extras, _ = json.Marshal(msg.Extras)
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +411,7 @@ func toExternalMessage(msg *model.Message) *model.MessageExternal {
|
||||||
ApplicationID: msg.ApplicationID,
|
ApplicationID: msg.ApplicationID,
|
||||||
Message: msg.Message,
|
Message: msg.Message,
|
||||||
Title: msg.Title,
|
Title: msg.Title,
|
||||||
Priority: msg.Priority,
|
Priority: &msg.Priority,
|
||||||
Date: msg.Date,
|
Date: msg.Date,
|
||||||
}
|
}
|
||||||
if len(msg.Extras) != 0 {
|
if len(msg.Extras) != 0 {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ func (s *MessageSuite) Test_ensureCorrectJsonRepresentation() {
|
||||||
|
|
||||||
actual := &model.PagedMessages{
|
actual := &model.PagedMessages{
|
||||||
Paging: model.Paging{Limit: 5, Since: 122, Size: 5, Next: "http://example.com/message?limit=5&since=122"},
|
Paging: model.Paging{Limit: 5, Since: 122, Size: 5, Next: "http://example.com/message?limit=5&since=122"},
|
||||||
Messages: []*model.MessageExternal{{ID: 55, ApplicationID: 2, Message: "hi", Title: "hi", Date: t, Priority: 4, Extras: map[string]interface{}{
|
Messages: []*model.MessageExternal{{ID: 55, ApplicationID: 2, Message: "hi", Title: "hi", Date: t, Priority: intPtr(4), Extras: map[string]interface{}{
|
||||||
"test::string": "string",
|
"test::string": "string",
|
||||||
"test::array": []interface{}{1, 2, 3},
|
"test::array": []interface{}{1, 2, 3},
|
||||||
"test::int": 1,
|
"test::int": 1,
|
||||||
|
|
@ -331,7 +331,29 @@ func (s *MessageSuite) Test_CreateMessage_onJson_allParams() {
|
||||||
|
|
||||||
msgs, err := s.db.GetMessagesByApplication(7)
|
msgs, err := s.db.GetMessagesByApplication(7)
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 7, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
expected := &model.MessageExternal{ID: 1, ApplicationID: 7, Title: "mytitle", Message: "mymessage", Priority: intPtr(1), Date: t}
|
||||||
|
assert.Len(s.T(), msgs, 1)
|
||||||
|
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
assert.Equal(s.T(), expected, s.notifiedMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MessageSuite) Test_CreateMessage_WithDefaultPriority() {
|
||||||
|
t, _ := time.Parse("2006/01/02", "2017/01/02")
|
||||||
|
|
||||||
|
timeNow = func() time.Time { return t }
|
||||||
|
defer func() { timeNow = time.Now }()
|
||||||
|
|
||||||
|
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||||
|
s.db.User(4).AppWithTokenAndDefaultPriority(8, "app-token", 5)
|
||||||
|
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"title": "mytitle", "message": "mymessage"}`))
|
||||||
|
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
s.a.CreateMessage(s.ctx)
|
||||||
|
|
||||||
|
msgs, err := s.db.GetMessagesByApplication(8)
|
||||||
|
assert.NoError(s.T(), err)
|
||||||
|
expected := &model.MessageExternal{ID: 1, ApplicationID: 8, Title: "mytitle", Message: "mymessage", Priority: intPtr(5), Date: t}
|
||||||
assert.Len(s.T(), msgs, 1)
|
assert.Len(s.T(), msgs, 1)
|
||||||
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
|
@ -352,7 +374,7 @@ func (s *MessageSuite) Test_CreateMessage_WithTitle() {
|
||||||
|
|
||||||
msgs, err := s.db.GetMessagesByApplication(5)
|
msgs, err := s.db.GetMessagesByApplication(5)
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 5, Title: "mytitle", Message: "mymessage", Date: t}
|
expected := &model.MessageExternal{ID: 1, ApplicationID: 5, Title: "mytitle", Message: "mymessage", Date: t, Priority: intPtr(0)}
|
||||||
assert.Len(s.T(), msgs, 1)
|
assert.Len(s.T(), msgs, 1)
|
||||||
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
|
@ -446,6 +468,7 @@ func (s *MessageSuite) Test_CreateMessage_WithExtras() {
|
||||||
Message: "mymessage",
|
Message: "mymessage",
|
||||||
Title: "msg with extras",
|
Title: "msg with extras",
|
||||||
Date: t,
|
Date: t,
|
||||||
|
Priority: intPtr(0),
|
||||||
Extras: map[string]interface{}{
|
Extras: map[string]interface{}{
|
||||||
"gotify::test": map[string]interface{}{
|
"gotify::test": map[string]interface{}{
|
||||||
"string": "test",
|
"string": "test",
|
||||||
|
|
@ -492,7 +515,7 @@ func (s *MessageSuite) Test_CreateMessage_onQueryData() {
|
||||||
|
|
||||||
s.a.CreateMessage(s.ctx)
|
s.a.CreateMessage(s.ctx)
|
||||||
|
|
||||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 2, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
expected := &model.MessageExternal{ID: 1, ApplicationID: 2, Title: "mytitle", Message: "mymessage", Priority: intPtr(1), Date: t}
|
||||||
|
|
||||||
msgs, err := s.db.GetMessagesByApplication(2)
|
msgs, err := s.db.GetMessagesByApplication(2)
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
|
|
@ -515,7 +538,7 @@ func (s *MessageSuite) Test_CreateMessage_onFormData() {
|
||||||
|
|
||||||
s.a.CreateMessage(s.ctx)
|
s.a.CreateMessage(s.ctx)
|
||||||
|
|
||||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 99, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
expected := &model.MessageExternal{ID: 1, ApplicationID: 99, Title: "mytitle", Message: "mymessage", Priority: intPtr(1), Date: t}
|
||||||
msgs, err := s.db.GetMessagesByApplication(99)
|
msgs, err := s.db.GetMessagesByApplication(99)
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
assert.Len(s.T(), msgs, 1)
|
assert.Len(s.T(), msgs, 1)
|
||||||
|
|
@ -528,3 +551,7 @@ func (s *MessageSuite) withURL(scheme, host, path, query string) {
|
||||||
s.ctx.Request.URL = &url.URL{Path: path, RawQuery: query}
|
s.ctx.Request.URL = &url.URL{Path: path, RawQuery: query}
|
||||||
s.ctx.Set("location", &url.URL{Scheme: scheme, Host: host})
|
s.ctx.Set("location", &url.URL{Scheme: scheme, Host: host})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func intPtr(x int) *int {
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2063,6 +2063,13 @@
|
||||||
"image"
|
"image"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"defaultPriority": {
|
||||||
|
"description": "The default priority of messages sent by this application. Defaults to 0.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "DefaultPriority",
|
||||||
|
"example": 4
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"description": "The description of the application.",
|
"description": "The description of the application.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -2115,6 +2122,13 @@
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"defaultPriority": {
|
||||||
|
"description": "The default priority of messages sent by this application. Defaults to 0.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "DefaultPriority",
|
||||||
|
"example": 5
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"description": "The description of the application.",
|
"description": "The description of the application.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -2326,7 +2340,7 @@
|
||||||
"example": "**Backup** was successfully finished."
|
"example": "**Backup** was successfully finished."
|
||||||
},
|
},
|
||||||
"priority": {
|
"priority": {
|
||||||
"description": "The priority of the message.",
|
"description": "The priority of the message. If unset, then the default priority of the\napplication will be used.",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
"x-go-name": "Priority",
|
"x-go-name": "Priority",
|
||||||
|
|
|
||||||
|
|
@ -42,4 +42,9 @@ type Application struct {
|
||||||
// example: image/image.jpeg
|
// example: image/image.jpeg
|
||||||
Image string `gorm:"type:text" json:"image"`
|
Image string `gorm:"type:text" json:"image"`
|
||||||
Messages []MessageExternal `json:"-"`
|
Messages []MessageExternal `json:"-"`
|
||||||
|
// The default priority of messages sent by this application. Defaults to 0.
|
||||||
|
//
|
||||||
|
// required: false
|
||||||
|
// example: 4
|
||||||
|
DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,11 @@ type MessageExternal struct {
|
||||||
//
|
//
|
||||||
// example: Backup
|
// example: Backup
|
||||||
Title string `form:"title" query:"title" json:"title"`
|
Title string `form:"title" query:"title" json:"title"`
|
||||||
// The priority of the message.
|
// The priority of the message. If unset, then the default priority of the
|
||||||
|
// application will be used.
|
||||||
//
|
//
|
||||||
// example: 2
|
// example: 2
|
||||||
Priority int `form:"priority" query:"priority" json:"priority"`
|
Priority *int `form:"priority" query:"priority" json:"priority"`
|
||||||
// The extra data sent along the message.
|
// The extra data sent along the message.
|
||||||
//
|
//
|
||||||
// The extra fields are stored in a key-value scheme. Only accepted in CreateMessage requests with application/json content-type.
|
// The extra fields are stored in a key-value scheme. Only accepted in CreateMessage requests with application/json content-type.
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ func NewManager(db Database, directory string, mux *gin.RouterGroup, notifier No
|
||||||
internalMsg := &model.Message{
|
internalMsg := &model.Message{
|
||||||
ApplicationID: message.Message.ApplicationID,
|
ApplicationID: message.Message.ApplicationID,
|
||||||
Title: message.Message.Title,
|
Title: message.Message.Title,
|
||||||
Priority: message.Message.Priority,
|
Priority: *message.Message.Priority,
|
||||||
Date: message.Message.Date,
|
Date: message.Message.Date,
|
||||||
Message: message.Message.Message,
|
Message: message.Message.Message,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func (c redirectToChannel) SendMessage(msg compat.Message) error {
|
||||||
ApplicationID: c.ApplicationID,
|
ApplicationID: c.ApplicationID,
|
||||||
Message: msg.Message,
|
Message: msg.Message,
|
||||||
Title: msg.Title,
|
Title: msg.Title,
|
||||||
Priority: msg.Priority,
|
Priority: &msg.Priority,
|
||||||
Date: time.Now(),
|
Date: time.Now(),
|
||||||
Extras: msg.Extras,
|
Extras: msg.Extras,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,13 @@ func (ab *AppClientBuilder) newAppWithTokenAndName(id uint, token, name string,
|
||||||
return application
|
return application
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppWithTokenAndDefaultPriority creates an application with a token and defaultPriority and returns a message builder.
|
||||||
|
func (ab *AppClientBuilder) AppWithTokenAndDefaultPriority(id uint, token string, defaultPriority int) *MessageBuilder {
|
||||||
|
application := &model.Application{ID: id, UserID: ab.userID, Token: token, DefaultPriority: defaultPriority}
|
||||||
|
ab.db.CreateApplication(application)
|
||||||
|
return &MessageBuilder{db: ab.db, appID: id}
|
||||||
|
}
|
||||||
|
|
||||||
// Client creates a client and returns itself.
|
// Client creates a client and returns itself.
|
||||||
func (ab *AppClientBuilder) Client(id uint) *AppClientBuilder {
|
func (ab *AppClientBuilder) Client(id uint) *AppClientBuilder {
|
||||||
return ab.ClientWithToken(id, "client"+fmt.Sprint(id))
|
return ab.ClientWithToken(id, "client"+fmt.Sprint(id))
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ func (s *DatabaseSuite) Test_Apps() {
|
||||||
userBuilder.InternalAppWithTokenAndName(10, "test-tokeni-2", "app name")
|
userBuilder.InternalAppWithTokenAndName(10, "test-tokeni-2", "app name")
|
||||||
userBuilder.AppWithToken(11, "test-token-3")
|
userBuilder.AppWithToken(11, "test-token-3")
|
||||||
userBuilder.InternalAppWithToken(12, "test-tokeni-3")
|
userBuilder.InternalAppWithToken(12, "test-tokeni-3")
|
||||||
|
userBuilder.AppWithTokenAndDefaultPriority(13, "test-tokeni-4", 4)
|
||||||
|
|
||||||
s.db.AssertAppExist(1)
|
s.db.AssertAppExist(1)
|
||||||
s.db.AssertAppExist(2)
|
s.db.AssertAppExist(2)
|
||||||
|
|
@ -140,6 +141,7 @@ func (s *DatabaseSuite) Test_Apps() {
|
||||||
s.db.AssertAppExist(10)
|
s.db.AssertAppExist(10)
|
||||||
s.db.AssertAppExist(11)
|
s.db.AssertAppExist(11)
|
||||||
s.db.AssertAppExist(12)
|
s.db.AssertAppExist(12)
|
||||||
|
s.db.AssertAppExist(13)
|
||||||
|
|
||||||
s.db.DeleteApplicationByID(2)
|
s.db.DeleteApplicationByID(2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,29 @@ import DialogContentText from '@material-ui/core/DialogContentText';
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
import {NumberField} from '../common/NumberField';
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
fClose: VoidFunction;
|
fClose: VoidFunction;
|
||||||
fOnSubmit: (name: string, description: string) => void;
|
fOnSubmit: (name: string, description: string, defaultPriority: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
defaultPriority: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AddDialog extends Component<IProps, IState> {
|
export default class AddDialog extends Component<IProps, IState> {
|
||||||
public state = {name: '', description: ''};
|
public state = {name: '', description: '', defaultPriority: 0};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {fClose, fOnSubmit} = this.props;
|
const {fClose, fOnSubmit} = this.props;
|
||||||
const {name, description} = this.state;
|
const {name, description, defaultPriority} = this.state;
|
||||||
const submitEnabled = this.state.name.length !== 0;
|
const submitEnabled = this.state.name.length !== 0;
|
||||||
const submitAndClose = () => {
|
const submitAndClose = () => {
|
||||||
fOnSubmit(name, description);
|
fOnSubmit(name, description, defaultPriority);
|
||||||
fClose();
|
fClose();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
@ -59,6 +61,14 @@ export default class AddDialog extends Component<IProps, IState> {
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
|
<NumberField
|
||||||
|
margin="dense"
|
||||||
|
className="priority"
|
||||||
|
label="Default Priority"
|
||||||
|
value={defaultPriority}
|
||||||
|
onChange={(value) => this.setState({defaultPriority: value})}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={fClose}>Cancel</Button>
|
<Button onClick={fClose}>Cancel</Button>
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,32 @@ export class AppStore extends BaseStore<IApplication> {
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
public update = async (id: number, name: string, description: string): Promise<void> => {
|
public update = async (
|
||||||
await axios.put(`${config.get('url')}application/${id}`, {name, description});
|
id: number,
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
defaultPriority: number
|
||||||
|
): Promise<void> => {
|
||||||
|
await axios.put(`${config.get('url')}application/${id}`, {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
defaultPriority,
|
||||||
|
});
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
this.snack('Application updated');
|
this.snack('Application updated');
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
public create = async (name: string, description: string): Promise<void> => {
|
public create = async (
|
||||||
await axios.post(`${config.get('url')}application`, {name, description});
|
name: string,
|
||||||
|
description: string,
|
||||||
|
defaultPriority: number
|
||||||
|
): Promise<void> => {
|
||||||
|
await axios.post(`${config.get('url')}application`, {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
defaultPriority,
|
||||||
|
});
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
this.snack('Application created');
|
this.snack('Application created');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||||
<TableCell>Name</TableCell>
|
<TableCell>Name</TableCell>
|
||||||
<TableCell>Token</TableCell>
|
<TableCell>Token</TableCell>
|
||||||
<TableCell>Description</TableCell>
|
<TableCell>Description</TableCell>
|
||||||
|
<TableCell>Priority</TableCell>
|
||||||
<TableCell />
|
<TableCell />
|
||||||
<TableCell />
|
<TableCell />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
@ -75,6 +76,7 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||||
<Row
|
<Row
|
||||||
key={app.id}
|
key={app.id}
|
||||||
description={app.description}
|
description={app.description}
|
||||||
|
defaultPriority={app.defaultPriority}
|
||||||
image={app.image}
|
image={app.image}
|
||||||
name={app.name}
|
name={app.name}
|
||||||
value={app.token}
|
value={app.token}
|
||||||
|
|
@ -103,11 +105,12 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||||
{updateId !== false && (
|
{updateId !== false && (
|
||||||
<UpdateDialog
|
<UpdateDialog
|
||||||
fClose={() => (this.updateId = false)}
|
fClose={() => (this.updateId = false)}
|
||||||
fOnSubmit={(name, description) =>
|
fOnSubmit={(name, description, defaultPriority) =>
|
||||||
appStore.update(updateId, name, description)
|
appStore.update(updateId, name, description, defaultPriority)
|
||||||
}
|
}
|
||||||
initialDescription={appStore.getByID(updateId).description}
|
initialDescription={appStore.getByID(updateId).description}
|
||||||
initialName={appStore.getByID(updateId).name}
|
initialName={appStore.getByID(updateId).name}
|
||||||
|
initialDefaultPriority={appStore.getByID(updateId).defaultPriority}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{deleteId !== false && (
|
{deleteId !== false && (
|
||||||
|
|
@ -147,6 +150,7 @@ interface IRowProps {
|
||||||
value: string;
|
value: string;
|
||||||
noDelete: boolean;
|
noDelete: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
|
defaultPriority: number;
|
||||||
fUpload: VoidFunction;
|
fUpload: VoidFunction;
|
||||||
image: string;
|
image: string;
|
||||||
fDelete: VoidFunction;
|
fDelete: VoidFunction;
|
||||||
|
|
@ -154,7 +158,7 @@ interface IRowProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Row: SFC<IRowProps> = observer(
|
const Row: SFC<IRowProps> = observer(
|
||||||
({name, value, noDelete, description, fDelete, fUpload, image, fEdit}) => (
|
({name, value, noDelete, description, defaultPriority, fDelete, fUpload, image, fEdit}) => (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell padding="default">
|
<TableCell padding="default">
|
||||||
<div style={{display: 'flex'}}>
|
<div style={{display: 'flex'}}>
|
||||||
|
|
@ -169,6 +173,7 @@ const Row: SFC<IRowProps> = observer(
|
||||||
<CopyableSecret value={value} style={{display: 'flex', alignItems: 'center'}} />
|
<CopyableSecret value={value} style={{display: 'flex', alignItems: 'center'}} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{description}</TableCell>
|
<TableCell>{description}</TableCell>
|
||||||
|
<TableCell>{defaultPriority}</TableCell>
|
||||||
<TableCell align="right" padding="none">
|
<TableCell align="right" padding="none">
|
||||||
<IconButton onClick={fEdit} className="edit">
|
<IconButton onClick={fEdit} className="edit">
|
||||||
<Edit />
|
<Edit />
|
||||||
|
|
|
||||||
|
|
@ -6,37 +6,41 @@ import DialogContentText from '@material-ui/core/DialogContentText';
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
import {NumberField} from '../common/NumberField';
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
fClose: VoidFunction;
|
fClose: VoidFunction;
|
||||||
fOnSubmit: (name: string, description: string) => void;
|
fOnSubmit: (name: string, description: string, defaultPriority: number) => void;
|
||||||
initialName: string;
|
initialName: string;
|
||||||
initialDescription: string;
|
initialDescription: string;
|
||||||
|
initialDefaultPriority: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
defaultPriority: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UpdateDialog extends Component<IProps, IState> {
|
export default class UpdateDialog extends Component<IProps, IState> {
|
||||||
public state = {name: '', description: ''};
|
public state = {name: '', description: '', defaultPriority: 0};
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
name: props.initialName,
|
name: props.initialName,
|
||||||
description: props.initialDescription,
|
description: props.initialDescription,
|
||||||
|
defaultPriority: props.initialDefaultPriority,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {fClose, fOnSubmit} = this.props;
|
const {fClose, fOnSubmit} = this.props;
|
||||||
const {name, description} = this.state;
|
const {name, description, defaultPriority} = this.state;
|
||||||
const submitEnabled = this.state.name.length !== 0;
|
const submitEnabled = this.state.name.length !== 0;
|
||||||
const submitAndClose = () => {
|
const submitAndClose = () => {
|
||||||
fOnSubmit(name, description);
|
fOnSubmit(name, description, defaultPriority);
|
||||||
fClose();
|
fClose();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
@ -69,6 +73,14 @@ export default class UpdateDialog extends Component<IProps, IState> {
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
|
<NumberField
|
||||||
|
margin="dense"
|
||||||
|
className="priority"
|
||||||
|
label="Default Priority"
|
||||||
|
value={defaultPriority}
|
||||||
|
onChange={(value) => this.setState({defaultPriority: value})}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={fClose}>Cancel</Button>
|
<Button onClick={fClose}>Cancel</Button>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {TextField, TextFieldProps} from '@material-ui/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface NumberFieldProps {
|
||||||
|
value: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NumberField = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
...props
|
||||||
|
}: NumberFieldProps & Omit<TextFieldProps, 'value' | 'onChange'>) => {
|
||||||
|
const [stringValue, setStringValue] = React.useState<string>(value.toString());
|
||||||
|
const [error, setError] = React.useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
value={stringValue}
|
||||||
|
type="number"
|
||||||
|
helperText={error}
|
||||||
|
error={error !== ''}
|
||||||
|
onChange={(event) => {
|
||||||
|
setStringValue(event.target.value);
|
||||||
|
const i = parseInt(event.target.value, 10);
|
||||||
|
if (!Number.isNaN(i)) {
|
||||||
|
onChange(i);
|
||||||
|
setError('');
|
||||||
|
} else {
|
||||||
|
setError('Invalid number');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -17,8 +17,9 @@ enum Col {
|
||||||
Name = 2,
|
Name = 2,
|
||||||
Token = 3,
|
Token = 3,
|
||||||
Description = 4,
|
Description = 4,
|
||||||
EditUpdate = 5,
|
DefaultPriority = 5,
|
||||||
EditDelete = 6,
|
EditUpdate = 6,
|
||||||
|
EditDelete = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
const hiddenToken = '•••••••••••••••';
|
const hiddenToken = '•••••••••••••••';
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export interface IApplication {
|
||||||
description: string;
|
description: string;
|
||||||
image: string;
|
image: string;
|
||||||
internal: boolean;
|
internal: boolean;
|
||||||
|
defaultPriority: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IClient {
|
export interface IClient {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue