From f852ef9f96364abc369be7da6536dedabefa8186 Mon Sep 17 00:00:00 2001 From: Chris Pruitt Date: Sat, 8 Jul 2023 21:51:22 -0400 Subject: [PATCH 1/8] Add application message priority default --- api/application.go | 16 +++++++--- api/application_test.go | 2 +- api/message.go | 3 ++ api/message_test.go | 22 +++++++++++++ model/application.go | 6 ++++ test/testdb/database.go | 16 ++++++++++ ui/src/application/AddApplicationDialog.tsx | 28 ++++++++++++++--- ui/src/application/AppStore.ts | 8 ++--- ui/src/application/Applications.tsx | 11 +++++-- .../application/UpdateApplicationDialog.tsx | 31 ++++++++++++++++--- ui/src/tests/application.test.ts | 5 +-- ui/src/types.ts | 1 + 12 files changed, 126 insertions(+), 23 deletions(-) diff --git a/api/application.go b/api/application.go index 6fa0f956..3a06d981 100644 --- a/api/application.go +++ b/api/application.go @@ -44,6 +44,10 @@ type ApplicationParams struct { // // example: Backup server for the interwebs Description string `form:"description" query:"description" json:"description"` + // The description of the application. + // + // example: Backup server for the interwebs + PriorityDefault int `form:"priorityDefault" query:"priorityDefault" json:"priorityDefault"` } // CreateApplication creates an application and returns the access token. @@ -83,11 +87,12 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) { applicationParams := ApplicationParams{} if err := ctx.Bind(&applicationParams); err == nil { app := model.Application{ - Name: applicationParams.Name, - Description: applicationParams.Description, - Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists), - UserID: auth.GetUserID(ctx), - Internal: false, + Name: applicationParams.Name, + Description: applicationParams.Description, + PriorityDefault: applicationParams.PriorityDefault, + Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists), + UserID: auth.GetUserID(ctx), + Internal: false, } if success := successOrAbort(ctx, 500, a.DB.CreateApplication(&app)); !success { @@ -245,6 +250,7 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) { if err := ctx.Bind(&applicationParams); err == nil { app.Description = applicationParams.Description app.Name = applicationParams.Name + app.PriorityDefault = applicationParams.PriorityDefault if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success { return diff --git a/api/application_test.go b/api/application_test.go index 6312a0a2..261e74d5 100644 --- a/api/application_test.go +++ b/api/application_test.go @@ -92,7 +92,7 @@ func (s *ApplicationSuite) Test_ensureApplicationHasCorrectJsonRepresentation() Image: "asd", 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, "priorityDefault":0}`) } func (s *ApplicationSuite) Test_CreateApplication_expectBadRequestOnEmptyName() { diff --git a/api/message.go b/api/message.go index 4b3c37d6..51dc6b31 100644 --- a/api/message.go +++ b/api/message.go @@ -371,6 +371,9 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) { if strings.TrimSpace(message.Title) == "" { message.Title = application.Name } + if message.Priority <= 0 { + message.Priority = application.PriorityDefault + } message.Date = timeNow() message.ID = 0 msgInternal := toInternalMessage(&message) diff --git a/api/message_test.go b/api/message_test.go index 0f5db8fb..4c459450 100644 --- a/api/message_test.go +++ b/api/message_test.go @@ -338,6 +338,28 @@ func (s *MessageSuite) Test_CreateMessage_onJson_allParams() { 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).AppWithTokenAndPriorityDefault(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: 5, 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_WithTitle() { t, _ := time.Parse("2006/01/02", "2017/01/02") timeNow = func() time.Time { return t } diff --git a/model/application.go b/model/application.go index d8c9e12c..f3039bde 100644 --- a/model/application.go +++ b/model/application.go @@ -42,4 +42,10 @@ type Application struct { // example: image/image.jpeg Image string `gorm:"type:text" json:"image"` Messages []MessageExternal `json:"-"` + // The priority default of a message for an application. If the sender does not provide a priority, then + // the message priority will be set to this value. Defaults to 0. + // + // required: false + // example: 4 + PriorityDefault int `gorm:"default:0" form:"priorityDefault" query:"priorityDefault" json:"priorityDefault"` } diff --git a/test/testdb/database.go b/test/testdb/database.go index 2abe254b..0d019542 100644 --- a/test/testdb/database.go +++ b/test/testdb/database.go @@ -138,6 +138,22 @@ func (ab *AppClientBuilder) newAppWithTokenAndName(id uint, token, name string, return application } +// AppWithTokenAndPriorityDefault creates an application with a token and priorityDefault and returns a message builder. +func (ab *AppClientBuilder) AppWithTokenAndPriorityDefault(id uint, token string, priorityDefault int) *MessageBuilder { + return ab.appWithTokenAndPriorityDefault(id, token, priorityDefault, false) +} + +func (ab *AppClientBuilder) appWithTokenAndPriorityDefault(id uint, token string, priorityDefault int, internal bool) *MessageBuilder { + ab.newAppWithTokenAndPriorityDefault(id, token, priorityDefault, internal) + return &MessageBuilder{db: ab.db, appID: id} +} + +func (ab *AppClientBuilder) newAppWithTokenAndPriorityDefault(id uint, token string, priorityDefault int, internal bool) *model.Application { + application := &model.Application{ID: id, UserID: ab.userID, Token: token, PriorityDefault: priorityDefault, Internal: internal} + ab.db.CreateApplication(application) + return application +} + // Client creates a client and returns itself. func (ab *AppClientBuilder) Client(id uint) *AppClientBuilder { return ab.ClientWithToken(id, "client"+fmt.Sprint(id)) diff --git a/ui/src/application/AddApplicationDialog.tsx b/ui/src/application/AddApplicationDialog.tsx index 8b73b85f..81515a04 100644 --- a/ui/src/application/AddApplicationDialog.tsx +++ b/ui/src/application/AddApplicationDialog.tsx @@ -10,23 +10,24 @@ import React, {Component} from 'react'; interface IProps { fClose: VoidFunction; - fOnSubmit: (name: string, description: string) => void; + fOnSubmit: (name: string, description: string, priorityDefault: number) => void; } interface IState { name: string; description: string; + priorityDefault: number; } export default class AddDialog extends Component { - public state = {name: '', description: ''}; + public state = {name: '', description: '', priorityDefault: 0}; public render() { const {fClose, fOnSubmit} = this.props; - const {name, description} = this.state; + const {name, description, priorityDefault} = this.state; const submitEnabled = this.state.name.length !== 0; const submitAndClose = () => { - fOnSubmit(name, description); + fOnSubmit(name, description, priorityDefault); fClose(); }; return ( @@ -59,6 +60,15 @@ export default class AddDialog extends Component { fullWidth multiline /> + @@ -84,4 +94,14 @@ export default class AddDialog extends Component { state[propertyName] = event.target.value; this.setState(state); } + + private handleChangeNumeric(propertyName: string, event: React.ChangeEvent) { + const state = this.state; + + const regex = /^[0-9\b]+$/; + if (event.target.value === "" || regex.test(event.target.value)) { + state[propertyName] = event.target.value; + this.setState(state); + } + } } diff --git a/ui/src/application/AppStore.ts b/ui/src/application/AppStore.ts index 73549071..b9b192a6 100644 --- a/ui/src/application/AppStore.ts +++ b/ui/src/application/AppStore.ts @@ -35,15 +35,15 @@ export class AppStore extends BaseStore { }; @action - public update = async (id: number, name: string, description: string): Promise => { - await axios.put(`${config.get('url')}application/${id}`, {name, description}); + public update = async (id: number, name: string, description: string, priorityDefault: number): Promise => { + await axios.put(`${config.get('url')}application/${id}`, {name, description, priorityDefault: Number(priorityDefault)}); await this.refresh(); this.snack('Application updated'); }; @action - public create = async (name: string, description: string): Promise => { - await axios.post(`${config.get('url')}application`, {name, description}); + public create = async (name: string, description: string, priorityDefault: number): Promise => { + await axios.post(`${config.get('url')}application`, {name, description, priorityDefault: Number(priorityDefault)}); await this.refresh(); this.snack('Application created'); }; diff --git a/ui/src/application/Applications.tsx b/ui/src/application/Applications.tsx index 0718b306..336037af 100644 --- a/ui/src/application/Applications.tsx +++ b/ui/src/application/Applications.tsx @@ -66,6 +66,7 @@ class Applications extends Component> { Name Token Description + Priority @@ -75,6 +76,7 @@ class Applications extends Component> { > { {updateId !== false && ( (this.updateId = false)} - fOnSubmit={(name, description) => - appStore.update(updateId, name, description) + fOnSubmit={(name, description, priorityDefault) => + appStore.update(updateId, name, description, priorityDefault) } initialDescription={appStore.getByID(updateId).description} initialName={appStore.getByID(updateId).name} + initialPriorityDefault={appStore.getByID(updateId).priorityDefault} /> )} {deleteId !== false && ( @@ -147,6 +150,7 @@ interface IRowProps { value: string; noDelete: boolean; description: string; + priorityDefault: number; fUpload: VoidFunction; image: string; fDelete: VoidFunction; @@ -154,7 +158,7 @@ interface IRowProps { } const Row: SFC = observer( - ({name, value, noDelete, description, fDelete, fUpload, image, fEdit}) => ( + ({ name, value, noDelete, description, priorityDefault, fDelete, fUpload, image, fEdit }) => (
@@ -169,6 +173,7 @@ const Row: SFC = observer( {description} + {priorityDefault} diff --git a/ui/src/application/UpdateApplicationDialog.tsx b/ui/src/application/UpdateApplicationDialog.tsx index a4038e0a..84d22b50 100644 --- a/ui/src/application/UpdateApplicationDialog.tsx +++ b/ui/src/application/UpdateApplicationDialog.tsx @@ -10,33 +10,36 @@ import React, {Component} from 'react'; interface IProps { fClose: VoidFunction; - fOnSubmit: (name: string, description: string) => void; + fOnSubmit: (name: string, description: string, priorityDefault: number) => void; initialName: string; initialDescription: string; + initialPriorityDefault: number; } interface IState { name: string; description: string; + priorityDefault: number; } export default class UpdateDialog extends Component { - public state = {name: '', description: ''}; + public state = {name: '', description: 'some', priorityDefault: 0}; constructor(props: IProps) { super(props); this.state = { name: props.initialName, description: props.initialDescription, + priorityDefault: props.initialPriorityDefault, }; } public render() { const {fClose, fOnSubmit} = this.props; - const {name, description} = this.state; + const {name, description, priorityDefault} = this.state; const submitEnabled = this.state.name.length !== 0; const submitAndClose = () => { - fOnSubmit(name, description); + fOnSubmit(name, description, priorityDefault); fClose(); }; return ( @@ -69,6 +72,16 @@ export default class UpdateDialog extends Component { fullWidth multiline /> + @@ -94,4 +107,14 @@ export default class UpdateDialog extends Component { state[propertyName] = event.target.value; this.setState(state); } + + private handleChangeNumeric(propertyName: string, event: React.ChangeEvent) { + const state = this.state; + + const regex = /^[0-9\b]+$/; + if (event.target.value === "" || regex.test(event.target.value)) { + state[propertyName] = event.target.value; + this.setState(state); + } + } } diff --git a/ui/src/tests/application.test.ts b/ui/src/tests/application.test.ts index f64a7981..26eab02a 100644 --- a/ui/src/tests/application.test.ts +++ b/ui/src/tests/application.test.ts @@ -17,8 +17,9 @@ enum Col { Name = 2, Token = 3, Description = 4, - EditUpdate = 5, - EditDelete = 6, + PriorityDefault = 5, + EditUpdate = 6, + EditDelete = 7, } const hiddenToken = '•••••••••••••••'; diff --git a/ui/src/types.ts b/ui/src/types.ts index ad22775d..f7039d3f 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -5,6 +5,7 @@ export interface IApplication { description: string; image: string; internal: boolean; + priorityDefault: number; } export interface IClient { From 06b0b4a909db6e1d15cf047e47c5f2becc28f525 Mon Sep 17 00:00:00 2001 From: Chris Pruitt Date: Sun, 9 Jul 2023 15:38:16 -0400 Subject: [PATCH 2/8] allow priority of 0 on create message; rename defaultPriority; application update priority test --- api/application.go | 10 ++++---- api/application_test.go | 25 ++++++++++++++++++- api/message.go | 12 ++++++--- api/message_test.go | 19 ++++++++------ docs/spec.json | 14 +++++++++++ model/application.go | 2 +- model/message.go | 2 +- plugin/manager.go | 2 +- plugin/messagehandler.go | 2 +- test/testdb/database.go | 17 +++---------- ui/src/application/AddApplicationDialog.tsx | 20 +++++++-------- ui/src/application/AppStore.ts | 8 +++--- ui/src/application/Applications.tsx | 14 +++++------ .../application/UpdateApplicationDialog.tsx | 25 +++++++++---------- ui/src/tests/application.test.ts | 2 +- ui/src/types.ts | 2 +- 16 files changed, 106 insertions(+), 70 deletions(-) diff --git a/api/application.go b/api/application.go index 3a06d981..66a22558 100644 --- a/api/application.go +++ b/api/application.go @@ -44,10 +44,10 @@ type ApplicationParams struct { // // example: Backup server for the interwebs Description string `form:"description" query:"description" json:"description"` - // The description of the application. + // The message priority default for the application. // - // example: Backup server for the interwebs - PriorityDefault int `form:"priorityDefault" query:"priorityDefault" json:"priorityDefault"` + // example: 5 + DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"` } // CreateApplication creates an application and returns the access token. @@ -89,7 +89,7 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) { app := model.Application{ Name: applicationParams.Name, Description: applicationParams.Description, - PriorityDefault: applicationParams.PriorityDefault, + DefaultPriority: applicationParams.DefaultPriority, Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists), UserID: auth.GetUserID(ctx), Internal: false, @@ -250,7 +250,7 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) { if err := ctx.Bind(&applicationParams); err == nil { app.Description = applicationParams.Description app.Name = applicationParams.Name - app.PriorityDefault = applicationParams.PriorityDefault + app.DefaultPriority = applicationParams.DefaultPriority if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success { return diff --git a/api/application_test.go b/api/application_test.go index 261e74d5..3c4bc960 100644 --- a/api/application_test.go +++ b/api/application_test.go @@ -92,7 +92,7 @@ func (s *ApplicationSuite) Test_ensureApplicationHasCorrectJsonRepresentation() Image: "asd", Internal: true, } - test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true, "priorityDefault":0}`) + 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() { @@ -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() { app := s.db.User(5).NewAppWithToken(2, "app-2") app.Image = "existing.png" diff --git a/api/message.go b/api/message.go index 51dc6b31..bbf0a4d8 100644 --- a/api/message.go +++ b/api/message.go @@ -371,8 +371,9 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) { if strings.TrimSpace(message.Title) == "" { message.Title = application.Name } - if message.Priority <= 0 { - message.Priority = application.PriorityDefault + // if client did not include a priority, then use application default priority + if message.Priority == nil { + message.Priority = &application.DefaultPriority } message.Date = timeNow() message.ID = 0 @@ -391,9 +392,12 @@ func toInternalMessage(msg *model.MessageExternal) *model.Message { ApplicationID: msg.ApplicationID, Message: msg.Message, Title: msg.Title, - Priority: msg.Priority, Date: msg.Date, } + if msg.Priority != nil { + res.Priority = *msg.Priority + } + if msg.Extras != nil { res.Extras, _ = json.Marshal(msg.Extras) } @@ -406,7 +410,7 @@ func toExternalMessage(msg *model.Message) *model.MessageExternal { ApplicationID: msg.ApplicationID, Message: msg.Message, Title: msg.Title, - Priority: msg.Priority, + Priority: &msg.Priority, Date: msg.Date, } if len(msg.Extras) != 0 { diff --git a/api/message_test.go b/api/message_test.go index 4c459450..358732e2 100644 --- a/api/message_test.go +++ b/api/message_test.go @@ -53,7 +53,7 @@ func (s *MessageSuite) Test_ensureCorrectJsonRepresentation() { actual := &model.PagedMessages{ 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::array": []interface{}{1, 2, 3}, "test::int": 1, @@ -331,7 +331,7 @@ func (s *MessageSuite) Test_CreateMessage_onJson_allParams() { msgs, err := s.db.GetMessagesByApplication(7) 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) @@ -345,7 +345,7 @@ func (s *MessageSuite) Test_CreateMessage_WithDefaultPriority() { defer func() { timeNow = time.Now }() auth.RegisterAuthentication(s.ctx, nil, 4, "app-token") - s.db.User(4).AppWithTokenAndPriorityDefault(8, "app-token", 5) + 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") @@ -353,7 +353,7 @@ func (s *MessageSuite) Test_CreateMessage_WithDefaultPriority() { msgs, err := s.db.GetMessagesByApplication(8) assert.NoError(s.T(), err) - expected := &model.MessageExternal{ID: 1, ApplicationID: 8, Title: "mytitle", Message: "mymessage", Priority: 5, Date: t} + expected := &model.MessageExternal{ID: 1, ApplicationID: 8, Title: "mytitle", Message: "mymessage", Priority: intPtr(5), Date: t} assert.Len(s.T(), msgs, 1) assert.Equal(s.T(), expected, toExternalMessage(msgs[0])) assert.Equal(s.T(), 200, s.recorder.Code) @@ -374,7 +374,7 @@ func (s *MessageSuite) Test_CreateMessage_WithTitle() { msgs, err := s.db.GetMessagesByApplication(5) 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.Equal(s.T(), expected, toExternalMessage(msgs[0])) assert.Equal(s.T(), 200, s.recorder.Code) @@ -468,6 +468,7 @@ func (s *MessageSuite) Test_CreateMessage_WithExtras() { Message: "mymessage", Title: "msg with extras", Date: t, + Priority: intPtr(0), Extras: map[string]interface{}{ "gotify::test": map[string]interface{}{ "string": "test", @@ -514,7 +515,7 @@ func (s *MessageSuite) Test_CreateMessage_onQueryData() { 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) assert.NoError(s.T(), err) @@ -537,7 +538,7 @@ func (s *MessageSuite) Test_CreateMessage_onFormData() { 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) assert.NoError(s.T(), err) assert.Len(s.T(), msgs, 1) @@ -550,3 +551,7 @@ func (s *MessageSuite) withURL(scheme, host, path, query string) { s.ctx.Request.URL = &url.URL{Path: path, RawQuery: query} s.ctx.Set("location", &url.URL{Scheme: scheme, Host: host}) } + +func intPtr(x int) *int { + return &x +} diff --git a/docs/spec.json b/docs/spec.json index 6bb5aafb..91560249 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -2063,6 +2063,13 @@ "image" ], "properties": { + "defaultPriority": { + "description": "The priority default of a message for an application. If the sender does not provide a priority, then\nthe message priority will be set to this value. Defaults to 0.", + "type": "integer", + "format": "int64", + "x-go-name": "DefaultPriority", + "example": 4 + }, "description": { "description": "The description of the application.", "type": "string", @@ -2115,6 +2122,13 @@ "name" ], "properties": { + "defaultPriority": { + "description": "The message priority default for the application.", + "type": "integer", + "format": "int64", + "x-go-name": "DefaultPriority", + "example": 5 + }, "description": { "description": "The description of the application.", "type": "string", diff --git a/model/application.go b/model/application.go index f3039bde..efba3f1c 100644 --- a/model/application.go +++ b/model/application.go @@ -47,5 +47,5 @@ type Application struct { // // required: false // example: 4 - PriorityDefault int `gorm:"default:0" form:"priorityDefault" query:"priorityDefault" json:"priorityDefault"` + DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"` } diff --git a/model/message.go b/model/message.go index 164ad70e..8aedf990 100644 --- a/model/message.go +++ b/model/message.go @@ -45,7 +45,7 @@ type MessageExternal struct { // The priority of the message. // // 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 fields are stored in a key-value scheme. Only accepted in CreateMessage requests with application/json content-type. diff --git a/plugin/manager.go b/plugin/manager.go index 1df99ed4..e1f90b88 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -70,7 +70,7 @@ func NewManager(db Database, directory string, mux *gin.RouterGroup, notifier No internalMsg := &model.Message{ ApplicationID: message.Message.ApplicationID, Title: message.Message.Title, - Priority: message.Message.Priority, + Priority: *message.Message.Priority, Date: message.Message.Date, Message: message.Message.Message, } diff --git a/plugin/messagehandler.go b/plugin/messagehandler.go index a703bfd7..01f01c5d 100644 --- a/plugin/messagehandler.go +++ b/plugin/messagehandler.go @@ -26,7 +26,7 @@ func (c redirectToChannel) SendMessage(msg compat.Message) error { ApplicationID: c.ApplicationID, Message: msg.Message, Title: msg.Title, - Priority: msg.Priority, + Priority: &msg.Priority, Date: time.Now(), Extras: msg.Extras, }, diff --git a/test/testdb/database.go b/test/testdb/database.go index 0d019542..e12d0122 100644 --- a/test/testdb/database.go +++ b/test/testdb/database.go @@ -138,20 +138,11 @@ func (ab *AppClientBuilder) newAppWithTokenAndName(id uint, token, name string, return application } -// AppWithTokenAndPriorityDefault creates an application with a token and priorityDefault and returns a message builder. -func (ab *AppClientBuilder) AppWithTokenAndPriorityDefault(id uint, token string, priorityDefault int) *MessageBuilder { - return ab.appWithTokenAndPriorityDefault(id, token, priorityDefault, false) -} - -func (ab *AppClientBuilder) appWithTokenAndPriorityDefault(id uint, token string, priorityDefault int, internal bool) *MessageBuilder { - ab.newAppWithTokenAndPriorityDefault(id, token, priorityDefault, internal) - return &MessageBuilder{db: ab.db, appID: id} -} - -func (ab *AppClientBuilder) newAppWithTokenAndPriorityDefault(id uint, token string, priorityDefault int, internal bool) *model.Application { - application := &model.Application{ID: id, UserID: ab.userID, Token: token, PriorityDefault: priorityDefault, Internal: internal} +// 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 application + return &MessageBuilder{db: ab.db, appID: id} } // Client creates a client and returns itself. diff --git a/ui/src/application/AddApplicationDialog.tsx b/ui/src/application/AddApplicationDialog.tsx index 81515a04..a4aa3676 100644 --- a/ui/src/application/AddApplicationDialog.tsx +++ b/ui/src/application/AddApplicationDialog.tsx @@ -10,24 +10,24 @@ import React, {Component} from 'react'; interface IProps { fClose: VoidFunction; - fOnSubmit: (name: string, description: string, priorityDefault: number) => void; + fOnSubmit: (name: string, description: string, defaultPriority: number) => void; } interface IState { name: string; description: string; - priorityDefault: number; + defaultPriority: number; } export default class AddDialog extends Component { - public state = {name: '', description: '', priorityDefault: 0}; + public state = {name: '', description: '', defaultPriority: 0}; public render() { const {fClose, fOnSubmit} = this.props; - const {name, description, priorityDefault} = this.state; + const {name, description, defaultPriority} = this.state; const submitEnabled = this.state.name.length !== 0; const submitAndClose = () => { - fOnSubmit(name, description, priorityDefault); + fOnSubmit(name, description, defaultPriority); fClose(); }; return ( @@ -62,12 +62,12 @@ export default class AddDialog extends Component { /> diff --git a/ui/src/application/AppStore.ts b/ui/src/application/AppStore.ts index b9b192a6..d367fc0c 100644 --- a/ui/src/application/AppStore.ts +++ b/ui/src/application/AppStore.ts @@ -35,15 +35,15 @@ export class AppStore extends BaseStore { }; @action - public update = async (id: number, name: string, description: string, priorityDefault: number): Promise => { - await axios.put(`${config.get('url')}application/${id}`, {name, description, priorityDefault: Number(priorityDefault)}); + public update = async (id: number, name: string, description: string, defaultPriority: number): Promise => { + await axios.put(`${config.get('url')}application/${id}`, {name, description, defaultPriority: Number(defaultPriority)}); await this.refresh(); this.snack('Application updated'); }; @action - public create = async (name: string, description: string, priorityDefault: number): Promise => { - await axios.post(`${config.get('url')}application`, {name, description, priorityDefault: Number(priorityDefault)}); + public create = async (name: string, description: string, defaultPriority: number): Promise => { + await axios.post(`${config.get('url')}application`, {name, description, defaultPriority: Number(defaultPriority)}); await this.refresh(); this.snack('Application created'); }; diff --git a/ui/src/application/Applications.tsx b/ui/src/application/Applications.tsx index 336037af..635dfef7 100644 --- a/ui/src/application/Applications.tsx +++ b/ui/src/application/Applications.tsx @@ -76,7 +76,7 @@ class Applications extends Component> { > { {updateId !== false && ( (this.updateId = false)} - fOnSubmit={(name, description, priorityDefault) => - appStore.update(updateId, name, description, priorityDefault) + fOnSubmit={(name, description, defaultPriority) => + appStore.update(updateId, name, description, defaultPriority) } initialDescription={appStore.getByID(updateId).description} initialName={appStore.getByID(updateId).name} - initialPriorityDefault={appStore.getByID(updateId).priorityDefault} + initialDefaultPriority={appStore.getByID(updateId).defaultPriority} /> )} {deleteId !== false && ( @@ -150,7 +150,7 @@ interface IRowProps { value: string; noDelete: boolean; description: string; - priorityDefault: number; + defaultPriority: number; fUpload: VoidFunction; image: string; fDelete: VoidFunction; @@ -158,7 +158,7 @@ interface IRowProps { } const Row: SFC = observer( - ({ name, value, noDelete, description, priorityDefault, fDelete, fUpload, image, fEdit }) => ( + ({ name, value, noDelete, description, defaultPriority, fDelete, fUpload, image, fEdit }) => (
@@ -173,7 +173,7 @@ const Row: SFC = observer( {description} - {priorityDefault} + {defaultPriority} diff --git a/ui/src/application/UpdateApplicationDialog.tsx b/ui/src/application/UpdateApplicationDialog.tsx index 84d22b50..7114c0f4 100644 --- a/ui/src/application/UpdateApplicationDialog.tsx +++ b/ui/src/application/UpdateApplicationDialog.tsx @@ -10,36 +10,36 @@ import React, {Component} from 'react'; interface IProps { fClose: VoidFunction; - fOnSubmit: (name: string, description: string, priorityDefault: number) => void; + fOnSubmit: (name: string, description: string, defaultPriority: number) => void; initialName: string; initialDescription: string; - initialPriorityDefault: number; + initialDefaultPriority: number; } interface IState { name: string; description: string; - priorityDefault: number; + defaultPriority: number; } export default class UpdateDialog extends Component { - public state = {name: '', description: 'some', priorityDefault: 0}; + public state = {name: '', description: 'some', defaultPriority: 0}; constructor(props: IProps) { super(props); this.state = { name: props.initialName, description: props.initialDescription, - priorityDefault: props.initialPriorityDefault, + defaultPriority: props.initialDefaultPriority, }; } public render() { const {fClose, fOnSubmit} = this.props; - const {name, description, priorityDefault} = this.state; + const {name, description, defaultPriority} = this.state; const submitEnabled = this.state.name.length !== 0; const submitAndClose = () => { - fOnSubmit(name, description, priorityDefault); + fOnSubmit(name, description, defaultPriority); fClose(); }; return ( @@ -74,13 +74,12 @@ export default class UpdateDialog extends Component { /> diff --git a/ui/src/tests/application.test.ts b/ui/src/tests/application.test.ts index 26eab02a..da95624d 100644 --- a/ui/src/tests/application.test.ts +++ b/ui/src/tests/application.test.ts @@ -17,7 +17,7 @@ enum Col { Name = 2, Token = 3, Description = 4, - PriorityDefault = 5, + DefaultPriority = 5, EditUpdate = 6, EditDelete = 7, } diff --git a/ui/src/types.ts b/ui/src/types.ts index f7039d3f..8e24d150 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -5,7 +5,7 @@ export interface IApplication { description: string; image: string; internal: boolean; - priorityDefault: number; + defaultPriority: number; } export interface IClient { From 2a262ca91d3ceeaf671259750441e6ffb39ab65d Mon Sep 17 00:00:00 2001 From: Chris Pruitt Date: Wed, 12 Jul 2023 09:21:30 -0400 Subject: [PATCH 3/8] formated tsx files --- ui/src/application/AddApplicationDialog.tsx | 2 +- ui/src/application/AppStore.ts | 25 ++++++++++++++++--- ui/src/application/Applications.tsx | 2 +- .../application/UpdateApplicationDialog.tsx | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ui/src/application/AddApplicationDialog.tsx b/ui/src/application/AddApplicationDialog.tsx index a4aa3676..15afb94f 100644 --- a/ui/src/application/AddApplicationDialog.tsx +++ b/ui/src/application/AddApplicationDialog.tsx @@ -99,7 +99,7 @@ export default class AddDialog extends Component { const state = this.state; const regex = /^[0-9\b]+$/; - if (event.target.value === "" || regex.test(event.target.value)) { + if (event.target.value === '' || regex.test(event.target.value)) { state[propertyName] = event.target.value; this.setState(state); } diff --git a/ui/src/application/AppStore.ts b/ui/src/application/AppStore.ts index d367fc0c..49bf23a3 100644 --- a/ui/src/application/AppStore.ts +++ b/ui/src/application/AppStore.ts @@ -35,15 +35,32 @@ export class AppStore extends BaseStore { }; @action - public update = async (id: number, name: string, description: string, defaultPriority: number): Promise => { - await axios.put(`${config.get('url')}application/${id}`, {name, description, defaultPriority: Number(defaultPriority)}); + public update = async ( + id: number, + name: string, + description: string, + defaultPriority: number + ): Promise => { + await axios.put(`${config.get('url')}application/${id}`, { + name, + description, + defaultPriority: Number(defaultPriority), + }); await this.refresh(); this.snack('Application updated'); }; @action - public create = async (name: string, description: string, defaultPriority: number): Promise => { - await axios.post(`${config.get('url')}application`, {name, description, defaultPriority: Number(defaultPriority)}); + public create = async ( + name: string, + description: string, + defaultPriority: number + ): Promise => { + await axios.post(`${config.get('url')}application`, { + name, + description, + defaultPriority: Number(defaultPriority), + }); await this.refresh(); this.snack('Application created'); }; diff --git a/ui/src/application/Applications.tsx b/ui/src/application/Applications.tsx index 635dfef7..a859b83d 100644 --- a/ui/src/application/Applications.tsx +++ b/ui/src/application/Applications.tsx @@ -158,7 +158,7 @@ interface IRowProps { } const Row: SFC = observer( - ({ name, value, noDelete, description, defaultPriority, fDelete, fUpload, image, fEdit }) => ( + ({name, value, noDelete, description, defaultPriority, fDelete, fUpload, image, fEdit}) => (
diff --git a/ui/src/application/UpdateApplicationDialog.tsx b/ui/src/application/UpdateApplicationDialog.tsx index 7114c0f4..9e6b291a 100644 --- a/ui/src/application/UpdateApplicationDialog.tsx +++ b/ui/src/application/UpdateApplicationDialog.tsx @@ -111,7 +111,7 @@ export default class UpdateDialog extends Component { const state = this.state; const regex = /^[0-9\b]+$/; - if (event.target.value === "" || regex.test(event.target.value)) { + if (event.target.value === '' || regex.test(event.target.value)) { state[propertyName] = event.target.value; this.setState(state); } From 8b89fe17f2e7aacf64ec1f35998e73dc27658f00 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sun, 16 Jul 2023 20:03:14 +0200 Subject: [PATCH 4/8] Simplify NumberField --- ui/src/application/AddApplicationDialog.tsx | 18 +++------- ui/src/application/AppStore.ts | 4 +-- .../application/UpdateApplicationDialog.tsx | 18 +++------- ui/src/common/NumberField.tsx | 36 +++++++++++++++++++ 4 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 ui/src/common/NumberField.tsx diff --git a/ui/src/application/AddApplicationDialog.tsx b/ui/src/application/AddApplicationDialog.tsx index 15afb94f..dce6b929 100644 --- a/ui/src/application/AddApplicationDialog.tsx +++ b/ui/src/application/AddApplicationDialog.tsx @@ -6,6 +6,7 @@ import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import TextField from '@material-ui/core/TextField'; import Tooltip from '@material-ui/core/Tooltip'; +import {NumberField} from '../common/NumberField'; import React, {Component} from 'react'; interface IProps { @@ -60,13 +61,12 @@ export default class AddDialog extends Component { fullWidth multiline /> - this.setState({defaultPriority: value})} fullWidth /> @@ -94,14 +94,4 @@ export default class AddDialog extends Component { state[propertyName] = event.target.value; this.setState(state); } - - private handleChangeNumeric(propertyName: string, event: React.ChangeEvent) { - const state = this.state; - - const regex = /^[0-9\b]+$/; - if (event.target.value === '' || regex.test(event.target.value)) { - state[propertyName] = event.target.value; - this.setState(state); - } - } } diff --git a/ui/src/application/AppStore.ts b/ui/src/application/AppStore.ts index 49bf23a3..dc5d5f2f 100644 --- a/ui/src/application/AppStore.ts +++ b/ui/src/application/AppStore.ts @@ -44,7 +44,7 @@ export class AppStore extends BaseStore { await axios.put(`${config.get('url')}application/${id}`, { name, description, - defaultPriority: Number(defaultPriority), + defaultPriority, }); await this.refresh(); this.snack('Application updated'); @@ -59,7 +59,7 @@ export class AppStore extends BaseStore { await axios.post(`${config.get('url')}application`, { name, description, - defaultPriority: Number(defaultPriority), + defaultPriority, }); await this.refresh(); this.snack('Application created'); diff --git a/ui/src/application/UpdateApplicationDialog.tsx b/ui/src/application/UpdateApplicationDialog.tsx index 9e6b291a..99d927ad 100644 --- a/ui/src/application/UpdateApplicationDialog.tsx +++ b/ui/src/application/UpdateApplicationDialog.tsx @@ -6,6 +6,7 @@ import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import TextField from '@material-ui/core/TextField'; import Tooltip from '@material-ui/core/Tooltip'; +import {NumberField} from '../common/NumberField'; import React, {Component} from 'react'; interface IProps { @@ -72,13 +73,12 @@ export default class UpdateDialog extends Component { fullWidth multiline /> - this.setState({defaultPriority: value})} fullWidth /> @@ -106,14 +106,4 @@ export default class UpdateDialog extends Component { state[propertyName] = event.target.value; this.setState(state); } - - private handleChangeNumeric(propertyName: string, event: React.ChangeEvent) { - const state = this.state; - - const regex = /^[0-9\b]+$/; - if (event.target.value === '' || regex.test(event.target.value)) { - state[propertyName] = event.target.value; - this.setState(state); - } - } } diff --git a/ui/src/common/NumberField.tsx b/ui/src/common/NumberField.tsx new file mode 100644 index 00000000..58952e05 --- /dev/null +++ b/ui/src/common/NumberField.tsx @@ -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) => { + const [stringValue, setStringValue] = React.useState(value.toString()); + const [error, setError] = React.useState(''); + + return ( + { + setStringValue(event.target.value); + const i = parseInt(event.target.value, 10); + if (!Number.isNaN(i)) { + onChange(i); + setError(''); + } else { + setError('Invalid number'); + } + }} + {...props} + /> + ); +}; From d92983a170eb9fe9d5ae4e3dc0d0f5ede2239de7 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sun, 16 Jul 2023 20:09:21 +0200 Subject: [PATCH 5/8] Adjust default priority description --- api/application.go | 2 +- docs/spec.json | 4 ++-- model/application.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/api/application.go b/api/application.go index 66a22558..c63d1d3b 100644 --- a/api/application.go +++ b/api/application.go @@ -44,7 +44,7 @@ type ApplicationParams struct { // // example: Backup server for the interwebs Description string `form:"description" query:"description" json:"description"` - // The message priority default for the application. + // The default priority of messages sent by this application. Defaults to 0. // // example: 5 DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"` diff --git a/docs/spec.json b/docs/spec.json index 91560249..87b73065 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -2064,7 +2064,7 @@ ], "properties": { "defaultPriority": { - "description": "The priority default of a message for an application. If the sender does not provide a priority, then\nthe message priority will be set to this value. Defaults to 0.", + "description": "The default priority of messages sent by this application. Defaults to 0.", "type": "integer", "format": "int64", "x-go-name": "DefaultPriority", @@ -2123,7 +2123,7 @@ ], "properties": { "defaultPriority": { - "description": "The message priority default for the application.", + "description": "The default priority of messages sent by this application. Defaults to 0.", "type": "integer", "format": "int64", "x-go-name": "DefaultPriority", diff --git a/model/application.go b/model/application.go index efba3f1c..3c69e7f0 100644 --- a/model/application.go +++ b/model/application.go @@ -42,8 +42,7 @@ type Application struct { // example: image/image.jpeg Image string `gorm:"type:text" json:"image"` Messages []MessageExternal `json:"-"` - // The priority default of a message for an application. If the sender does not provide a priority, then - // the message priority will be set to this value. Defaults to 0. + // The default priority of messages sent by this application. Defaults to 0. // // required: false // example: 4 From bcd4a37d7f0b70aa6ab87fa362e8e8b7903ed8a1 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sun, 16 Jul 2023 20:09:35 +0200 Subject: [PATCH 6/8] Revert unwanted changes --- ui/src/application/UpdateApplicationDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/application/UpdateApplicationDialog.tsx b/ui/src/application/UpdateApplicationDialog.tsx index 99d927ad..ed040224 100644 --- a/ui/src/application/UpdateApplicationDialog.tsx +++ b/ui/src/application/UpdateApplicationDialog.tsx @@ -24,7 +24,7 @@ interface IState { } export default class UpdateDialog extends Component { - public state = {name: '', description: 'some', defaultPriority: 0}; + public state = {name: '', description: '', defaultPriority: 0}; constructor(props: IProps) { super(props); From fb67a6058de178773a68e25642f3331db4c64a72 Mon Sep 17 00:00:00 2001 From: Chris Pruitt Date: Sun, 16 Jul 2023 15:55:58 -0400 Subject: [PATCH 7/8] add test coverage for AppWithTokenAndDefaultPriority --- test/testdb/database_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/testdb/database_test.go b/test/testdb/database_test.go index 17a80aeb..3dd4b336 100644 --- a/test/testdb/database_test.go +++ b/test/testdb/database_test.go @@ -127,6 +127,7 @@ func (s *DatabaseSuite) Test_Apps() { userBuilder.InternalAppWithTokenAndName(10, "test-tokeni-2", "app name") userBuilder.AppWithToken(11, "test-token-3") userBuilder.InternalAppWithToken(12, "test-tokeni-3") + userBuilder.AppWithTokenAndDefaultPriority(13, "test-tokeni-4", 4) s.db.AssertAppExist(1) s.db.AssertAppExist(2) @@ -140,6 +141,7 @@ func (s *DatabaseSuite) Test_Apps() { s.db.AssertAppExist(10) s.db.AssertAppExist(11) s.db.AssertAppExist(12) + s.db.AssertAppExist(13) s.db.DeleteApplicationByID(2) From 7bfba2f6dc3b9bedf7e8903e6dc41d9fa93a2a03 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Wed, 19 Jul 2023 20:05:35 +0200 Subject: [PATCH 8/8] docs --- api/message.go | 3 ++- docs/spec.json | 2 +- model/message.go | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/message.go b/api/message.go index bbf0a4d8..3a4b2420 100644 --- a/api/message.go +++ b/api/message.go @@ -371,10 +371,11 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) { if strings.TrimSpace(message.Title) == "" { message.Title = application.Name } - // if client did not include a priority, then use application default priority + if message.Priority == nil { message.Priority = &application.DefaultPriority } + message.Date = timeNow() message.ID = 0 msgInternal := toInternalMessage(&message) diff --git a/docs/spec.json b/docs/spec.json index 87b73065..fdbe4550 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -2340,7 +2340,7 @@ "example": "**Backup** was successfully finished." }, "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", "format": "int64", "x-go-name": "Priority", diff --git a/model/message.go b/model/message.go index 8aedf990..dbee2ec6 100644 --- a/model/message.go +++ b/model/message.go @@ -42,7 +42,8 @@ type MessageExternal struct { // // example: Backup 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 Priority *int `form:"priority" query:"priority" json:"priority"`