mirror of
https://github.com/gotify/server.git
synced 2024-01-28 15:20:56 +03:00
Add default message priority for applications
Co-authored-by: Jannis Mattheis <contact@jmattheis.de>
This commit is contained in:
@@ -44,6 +44,10 @@ type ApplicationParams struct {
|
||||
//
|
||||
// example: Backup server for the interwebs
|
||||
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.
|
||||
@@ -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,
|
||||
DefaultPriority: applicationParams.DefaultPriority,
|
||||
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.DefaultPriority = applicationParams.DefaultPriority
|
||||
|
||||
if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success {
|
||||
return
|
||||
|
||||
@@ -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, "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"
|
||||
|
||||
@@ -371,6 +371,11 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) {
|
||||
if strings.TrimSpace(message.Title) == "" {
|
||||
message.Title = application.Name
|
||||
}
|
||||
|
||||
if message.Priority == nil {
|
||||
message.Priority = &application.DefaultPriority
|
||||
}
|
||||
|
||||
message.Date = timeNow()
|
||||
message.ID = 0
|
||||
msgInternal := toInternalMessage(&message)
|
||||
@@ -388,9 +393,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)
|
||||
}
|
||||
@@ -403,7 +411,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 {
|
||||
|
||||
@@ -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,29 @@ 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)
|
||||
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.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
@@ -352,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)
|
||||
@@ -446,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",
|
||||
@@ -492,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)
|
||||
@@ -515,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)
|
||||
@@ -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.Set("location", &url.URL{Scheme: scheme, Host: host})
|
||||
}
|
||||
|
||||
func intPtr(x int) *int {
|
||||
return &x
|
||||
}
|
||||
|
||||
@@ -2063,6 +2063,13 @@
|
||||
"image"
|
||||
],
|
||||
"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": "The description of the application.",
|
||||
"type": "string",
|
||||
@@ -2115,6 +2122,13 @@
|
||||
"name"
|
||||
],
|
||||
"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": "The description of the application.",
|
||||
"type": "string",
|
||||
@@ -2326,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",
|
||||
|
||||
@@ -42,4 +42,9 @@ type Application struct {
|
||||
// example: image/image.jpeg
|
||||
Image string `gorm:"type:text" json:"image"`
|
||||
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
|
||||
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"`
|
||||
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.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -138,6 +138,13 @@ func (ab *AppClientBuilder) newAppWithTokenAndName(id uint, token, name string,
|
||||
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.
|
||||
func (ab *AppClientBuilder) Client(id uint) *AppClientBuilder {
|
||||
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.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)
|
||||
|
||||
|
||||
@@ -6,27 +6,29 @@ 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 {
|
||||
fClose: VoidFunction;
|
||||
fOnSubmit: (name: string, description: string) => void;
|
||||
fOnSubmit: (name: string, description: string, defaultPriority: number) => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
name: string;
|
||||
description: string;
|
||||
defaultPriority: number;
|
||||
}
|
||||
|
||||
export default class AddDialog extends Component<IProps, IState> {
|
||||
public state = {name: '', description: ''};
|
||||
public state = {name: '', description: '', defaultPriority: 0};
|
||||
|
||||
public render() {
|
||||
const {fClose, fOnSubmit} = this.props;
|
||||
const {name, description} = this.state;
|
||||
const {name, description, defaultPriority} = this.state;
|
||||
const submitEnabled = this.state.name.length !== 0;
|
||||
const submitAndClose = () => {
|
||||
fOnSubmit(name, description);
|
||||
fOnSubmit(name, description, defaultPriority);
|
||||
fClose();
|
||||
};
|
||||
return (
|
||||
@@ -59,6 +61,14 @@ export default class AddDialog extends Component<IProps, IState> {
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
<NumberField
|
||||
margin="dense"
|
||||
className="priority"
|
||||
label="Default Priority"
|
||||
value={defaultPriority}
|
||||
onChange={(value) => this.setState({defaultPriority: value})}
|
||||
fullWidth
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={fClose}>Cancel</Button>
|
||||
|
||||
@@ -35,15 +35,32 @@ export class AppStore extends BaseStore<IApplication> {
|
||||
};
|
||||
|
||||
@action
|
||||
public update = async (id: number, name: string, description: string): Promise<void> => {
|
||||
await axios.put(`${config.get('url')}application/${id}`, {name, description});
|
||||
public update = async (
|
||||
id: number,
|
||||
name: string,
|
||||
description: string,
|
||||
defaultPriority: number
|
||||
): Promise<void> => {
|
||||
await axios.put(`${config.get('url')}application/${id}`, {
|
||||
name,
|
||||
description,
|
||||
defaultPriority,
|
||||
});
|
||||
await this.refresh();
|
||||
this.snack('Application updated');
|
||||
};
|
||||
|
||||
@action
|
||||
public create = async (name: string, description: string): Promise<void> => {
|
||||
await axios.post(`${config.get('url')}application`, {name, description});
|
||||
public create = async (
|
||||
name: string,
|
||||
description: string,
|
||||
defaultPriority: number
|
||||
): Promise<void> => {
|
||||
await axios.post(`${config.get('url')}application`, {
|
||||
name,
|
||||
description,
|
||||
defaultPriority,
|
||||
});
|
||||
await this.refresh();
|
||||
this.snack('Application created');
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Token</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>Priority</TableCell>
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
</TableRow>
|
||||
@@ -75,6 +76,7 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||
<Row
|
||||
key={app.id}
|
||||
description={app.description}
|
||||
defaultPriority={app.defaultPriority}
|
||||
image={app.image}
|
||||
name={app.name}
|
||||
value={app.token}
|
||||
@@ -103,11 +105,12 @@ class Applications extends Component<Stores<'appStore'>> {
|
||||
{updateId !== false && (
|
||||
<UpdateDialog
|
||||
fClose={() => (this.updateId = false)}
|
||||
fOnSubmit={(name, description) =>
|
||||
appStore.update(updateId, name, description)
|
||||
fOnSubmit={(name, description, defaultPriority) =>
|
||||
appStore.update(updateId, name, description, defaultPriority)
|
||||
}
|
||||
initialDescription={appStore.getByID(updateId).description}
|
||||
initialName={appStore.getByID(updateId).name}
|
||||
initialDefaultPriority={appStore.getByID(updateId).defaultPriority}
|
||||
/>
|
||||
)}
|
||||
{deleteId !== false && (
|
||||
@@ -147,6 +150,7 @@ interface IRowProps {
|
||||
value: string;
|
||||
noDelete: boolean;
|
||||
description: string;
|
||||
defaultPriority: number;
|
||||
fUpload: VoidFunction;
|
||||
image: string;
|
||||
fDelete: VoidFunction;
|
||||
@@ -154,7 +158,7 @@ interface IRowProps {
|
||||
}
|
||||
|
||||
const Row: SFC<IRowProps> = observer(
|
||||
({name, value, noDelete, description, fDelete, fUpload, image, fEdit}) => (
|
||||
({name, value, noDelete, description, defaultPriority, fDelete, fUpload, image, fEdit}) => (
|
||||
<TableRow>
|
||||
<TableCell padding="default">
|
||||
<div style={{display: 'flex'}}>
|
||||
@@ -169,6 +173,7 @@ const Row: SFC<IRowProps> = observer(
|
||||
<CopyableSecret value={value} style={{display: 'flex', alignItems: 'center'}} />
|
||||
</TableCell>
|
||||
<TableCell>{description}</TableCell>
|
||||
<TableCell>{defaultPriority}</TableCell>
|
||||
<TableCell align="right" padding="none">
|
||||
<IconButton onClick={fEdit} className="edit">
|
||||
<Edit />
|
||||
|
||||
@@ -6,37 +6,41 @@ 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 {
|
||||
fClose: VoidFunction;
|
||||
fOnSubmit: (name: string, description: string) => void;
|
||||
fOnSubmit: (name: string, description: string, defaultPriority: number) => void;
|
||||
initialName: string;
|
||||
initialDescription: string;
|
||||
initialDefaultPriority: number;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
name: string;
|
||||
description: string;
|
||||
defaultPriority: number;
|
||||
}
|
||||
|
||||
export default class UpdateDialog extends Component<IProps, IState> {
|
||||
public state = {name: '', description: ''};
|
||||
public state = {name: '', description: '', defaultPriority: 0};
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: props.initialName,
|
||||
description: props.initialDescription,
|
||||
defaultPriority: props.initialDefaultPriority,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {fClose, fOnSubmit} = this.props;
|
||||
const {name, description} = this.state;
|
||||
const {name, description, defaultPriority} = this.state;
|
||||
const submitEnabled = this.state.name.length !== 0;
|
||||
const submitAndClose = () => {
|
||||
fOnSubmit(name, description);
|
||||
fOnSubmit(name, description, defaultPriority);
|
||||
fClose();
|
||||
};
|
||||
return (
|
||||
@@ -69,6 +73,14 @@ export default class UpdateDialog extends Component<IProps, IState> {
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
<NumberField
|
||||
margin="dense"
|
||||
className="priority"
|
||||
label="Default Priority"
|
||||
value={defaultPriority}
|
||||
onChange={(value) => this.setState({defaultPriority: value})}
|
||||
fullWidth
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={fClose}>Cancel</Button>
|
||||
|
||||
36
ui/src/common/NumberField.tsx
Normal file
36
ui/src/common/NumberField.tsx
Normal file
@@ -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,
|
||||
Token = 3,
|
||||
Description = 4,
|
||||
EditUpdate = 5,
|
||||
EditDelete = 6,
|
||||
DefaultPriority = 5,
|
||||
EditUpdate = 6,
|
||||
EditDelete = 7,
|
||||
}
|
||||
|
||||
const hiddenToken = '•••••••••••••••';
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface IApplication {
|
||||
description: string;
|
||||
image: string;
|
||||
internal: boolean;
|
||||
defaultPriority: number;
|
||||
}
|
||||
|
||||
export interface IClient {
|
||||
|
||||
Reference in New Issue
Block a user