diff --git a/api/application.go b/api/application.go index 805ae2b1..e22bb197 100644 --- a/api/application.go +++ b/api/application.go @@ -48,6 +48,10 @@ type ApplicationParams struct { // // example: 5 DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"` + // The order in which this application should appear in the UI. Defaults to 0. + // + // example: 7 + SortOrder int `form:"sortOrder" query:"sortOrder" json:"sortOrder"` } // CreateApplication creates an application and returns the access token. @@ -90,6 +94,7 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) { Name: applicationParams.Name, Description: applicationParams.Description, DefaultPriority: applicationParams.DefaultPriority, + SortOrder: applicationParams.SortOrder, Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists), UserID: auth.GetUserID(ctx), Internal: false, @@ -251,6 +256,7 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) { app.Description = applicationParams.Description app.Name = applicationParams.Name app.DefaultPriority = applicationParams.DefaultPriority + app.SortOrder = applicationParams.SortOrder if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success { return diff --git a/api/application_test.go b/api/application_test.go index 9e6efe74..e11bc8fe 100644 --- a/api/application_test.go +++ b/api/application_test.go @@ -91,8 +91,9 @@ func (s *ApplicationSuite) Test_ensureApplicationHasCorrectJsonRepresentation() Image: "asd", Internal: true, LastUsed: nil, + SortOrder: 7, } - test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true, "defaultPriority":0, "lastUsed":null}`) + test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true, "defaultPriority":0, "lastUsed":null, "sortOrder":7}`) } func (s *ApplicationSuite) Test_CreateApplication_expectBadRequestOnEmptyName() { diff --git a/database/application.go b/database/application.go index f62f4688..b41c48ca 100644 --- a/database/application.go +++ b/database/application.go @@ -47,7 +47,7 @@ func (d *GormDatabase) DeleteApplicationByID(id uint) error { // GetApplicationsByUser returns all applications from a user. func (d *GormDatabase) GetApplicationsByUser(userID uint) ([]*model.Application, error) { var apps []*model.Application - err := d.DB.Where("user_id = ?", userID).Order("id ASC").Find(&apps).Error + err := d.DB.Where("user_id = ?", userID).Order("sort_order ASC").Find(&apps).Error if err == gorm.ErrRecordNotFound { err = nil } diff --git a/model/application.go b/model/application.go index 0a45f2e4..c6b2ce4f 100644 --- a/model/application.go +++ b/model/application.go @@ -54,4 +54,8 @@ type Application struct { // read only: true // example: 2019-01-01T00:00:00Z LastUsed *time.Time `json:"lastUsed"` + // The order in which the application should appear in the UI. + // + // example: 7 + SortOrder int `json:"sortOrder" query:"sortOrder" form: "sortOrder"` } diff --git a/ui/src/application/AddApplicationDialog.tsx b/ui/src/application/AddApplicationDialog.tsx index dce6b929..4312cc7d 100644 --- a/ui/src/application/AddApplicationDialog.tsx +++ b/ui/src/application/AddApplicationDialog.tsx @@ -11,24 +11,30 @@ import React, {Component} from 'react'; interface IProps { fClose: VoidFunction; - fOnSubmit: (name: string, description: string, defaultPriority: number) => void; + fOnSubmit: ( + name: string, + description: string, + defaultPriority: number, + sortOrder: number + ) => void; } interface IState { name: string; description: string; defaultPriority: number; + sortOrder: number; } export default class AddDialog extends Component { - public state = {name: '', description: '', defaultPriority: 0}; + public state = {name: '', description: '', defaultPriority: 0, sortOrder: 0}; public render() { const {fClose, fOnSubmit} = this.props; - const {name, description, defaultPriority} = this.state; + const {name, description, defaultPriority, sortOrder} = this.state; const submitEnabled = this.state.name.length !== 0; const submitAndClose = () => { - fOnSubmit(name, description, defaultPriority); + fOnSubmit(name, description, defaultPriority, sortOrder); fClose(); }; return ( @@ -69,6 +75,14 @@ export default class AddDialog extends Component { onChange={(value) => this.setState({defaultPriority: value})} fullWidth /> + this.setState({sortOrder: value})} + fullWidth + /> @@ -90,7 +104,7 @@ export default class AddDialog extends Component { } private handleChange(propertyName: string, event: React.ChangeEvent) { - const state = this.state; + const state: any = this.state; state[propertyName] = event.target.value; this.setState(state); } diff --git a/ui/src/application/AppStore.ts b/ui/src/application/AppStore.ts index dc5d5f2f..558829af 100644 --- a/ui/src/application/AppStore.ts +++ b/ui/src/application/AppStore.ts @@ -39,12 +39,14 @@ export class AppStore extends BaseStore { id: number, name: string, description: string, - defaultPriority: number + defaultPriority: number, + sortOrder: number ): Promise => { await axios.put(`${config.get('url')}application/${id}`, { name, description, defaultPriority, + sortOrder, }); await this.refresh(); this.snack('Application updated'); @@ -54,12 +56,14 @@ export class AppStore extends BaseStore { public create = async ( name: string, description: string, - defaultPriority: number + defaultPriority: number, + sortOrder: number ): Promise => { await axios.post(`${config.get('url')}application`, { name, description, defaultPriority, + sortOrder, }); await this.refresh(); this.snack('Application created'); diff --git a/ui/src/application/Applications.tsx b/ui/src/application/Applications.tsx index 7f3836c4..81802437 100644 --- a/ui/src/application/Applications.tsx +++ b/ui/src/application/Applications.tsx @@ -68,6 +68,7 @@ class Applications extends Component> { Token Description Priority + Sort Order Last Used @@ -79,6 +80,7 @@ class Applications extends Component> { key={app.id} description={app.description} defaultPriority={app.defaultPriority} + sortOrder={app.sortOrder} image={app.image} name={app.name} value={app.token} @@ -108,12 +110,13 @@ class Applications extends Component> { {updateId !== false && ( (this.updateId = false)} - fOnSubmit={(name, description, defaultPriority) => - appStore.update(updateId, name, description, defaultPriority) + fOnSubmit={(name, description, defaultPriority, sortOrder) => + appStore.update(updateId, name, description, defaultPriority, sortOrder) } initialDescription={appStore.getByID(updateId).description} initialName={appStore.getByID(updateId).name} initialDefaultPriority={appStore.getByID(updateId).defaultPriority} + initialSortOrder={appStore.getByID(updateId).sortOrder} /> )} {deleteId !== false && ( @@ -155,6 +158,7 @@ interface IRowProps { description: string; defaultPriority: number; lastUsed: string | null; + sortOrder: number; fUpload: VoidFunction; image: string; fDelete: VoidFunction; @@ -169,6 +173,7 @@ const Row: SFC = observer( description, defaultPriority, lastUsed, + sortOrder, fDelete, fUpload, image, @@ -189,6 +194,7 @@ const Row: SFC = observer( {description} {defaultPriority} + {sortOrder} diff --git a/ui/src/application/UpdateApplicationDialog.tsx b/ui/src/application/UpdateApplicationDialog.tsx index ed040224..063dd297 100644 --- a/ui/src/application/UpdateApplicationDialog.tsx +++ b/ui/src/application/UpdateApplicationDialog.tsx @@ -11,20 +11,27 @@ import React, {Component} from 'react'; interface IProps { fClose: VoidFunction; - fOnSubmit: (name: string, description: string, defaultPriority: number) => void; + fOnSubmit: ( + name: string, + description: string, + defaultPriority: number, + sortOrder: number + ) => void; initialName: string; initialDescription: string; initialDefaultPriority: number; + initialSortOrder: number; } interface IState { name: string; description: string; defaultPriority: number; + sortOrder: number; } export default class UpdateDialog extends Component { - public state = {name: '', description: '', defaultPriority: 0}; + public state = {name: '', description: '', defaultPriority: 0, sortOrder: 0}; constructor(props: IProps) { super(props); @@ -32,15 +39,16 @@ export default class UpdateDialog extends Component { name: props.initialName, description: props.initialDescription, defaultPriority: props.initialDefaultPriority, + sortOrder: props.initialSortOrder, }; } public render() { const {fClose, fOnSubmit} = this.props; - const {name, description, defaultPriority} = this.state; + const {name, description, defaultPriority, sortOrder} = this.state; const submitEnabled = this.state.name.length !== 0; const submitAndClose = () => { - fOnSubmit(name, description, defaultPriority); + fOnSubmit(name, description, defaultPriority, sortOrder); fClose(); }; return ( @@ -81,6 +89,14 @@ export default class UpdateDialog extends Component { onChange={(value) => this.setState({defaultPriority: value})} fullWidth /> + this.setState({sortOrder: value})} + fullWidth + /> @@ -102,7 +118,7 @@ export default class UpdateDialog extends Component { } private handleChange(propertyName: string, event: React.ChangeEvent) { - const state = this.state; + const state: any = this.state; state[propertyName] = event.target.value; this.setState(state); } diff --git a/ui/src/types.ts b/ui/src/types.ts index fbc592c2..360ee946 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -7,6 +7,7 @@ export interface IApplication { internal: boolean; defaultPriority: number; lastUsed: string | null; + sortOrder: number; } export interface IClient {