1
0

Improve pagination (#105)

This commit is contained in:
konrad
2019-10-23 21:11:40 +00:00
parent 89f385a53d
commit 8948a5f219
97 changed files with 2137 additions and 529 deletions

36
vendor/code.vikunja.io/web/Readme.md generated vendored
View File

@ -60,7 +60,7 @@ This interface defines methods to Create/Read/ReadAll/Update/Delete something. I
type CRUDable interface {
Create(Auth) error
ReadOne() error
ReadAll(string, Auth, int) (interface{}, error)
ReadAll(auth Auth, search string, page int64, perPage int64) (result interface{}, resultCount int64, numberOfPages int64, err error)
Update() error
Delete() error
}
@ -122,6 +122,13 @@ You can provide your own instance of `logger.Logger` (using [go-logging](https:/
It will use this instance to log errors which are not better specified or things like users trying to do something they're
not allowed to do and so on.
#### MaxItemsPerPage
Contains the maximum number of items per page.
If the client requests more items than this, the number of items requested is set to this value.
See [pagination](#pagination) for more.
#### Full Example
```go
@ -137,18 +144,35 @@ handler.SetLoggingProvider(&log.Log)
### Pagination
When using the `ReadAll`-method, the third parameter contains the requested page.
Your function should return only the number of results corresponding to that page.
The number of items per page should be set by your application elewhere, most likely in its config.
The `ReadAll`-method has a number of parameters:
The number of items to return is then usually calculated with some method like `page_number * items_per_page`.
```go
ReadAll(auth Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfItems int64, err error)
```
The third parameter contains the requested page, the fourth parameter contains the number of items per page.
You should calculate the limits accordingly.
If the number of items per page are not set by the client, the web handler will pass the maximum number of items per page instead.
This makes items per page optional for clients.
Take a look at [the config section](#handler-config) for information on how to set that value.
You need to return a number of things:
* The result itself, usually a slice
* The number of items you return in `result`. Most of the time, this is just `len(result)`. You need to return this value to make the clients aware if they requested a number of items > max items per page.
* The total number of items available. We use the total number of items here and not the number pages so the implementations don't have to deal with calculating the number of pages from that. The total number of clients is then calculated and returned to the client, ite can then be used by the clients to build client-side pagination or similar.
* An error.
The number of items and the total number of pages available will be returned in the `x-pagination-total-pages` and `x-pagination-result-count` response headers.
_You should put this in your api documentation._
### Search
When using the `ReadAll`-method, the first parameter is a search term which should be used to search items of your struct.
You define the critera inside of that function.
Users can then pass the `?s=something` parameter to the url to search, thats something you should put in your api documentation.
Users can then pass the `?s=something` parameter to the url to search, _thats something you should put in your api documentation_.
As the logic for "give me everything" and "give me everything where the name contains 'something'" is mostly the same, we made
the decision to design the function like this, in order to keep the places with mostly the same logic as few as possible.

View File

@ -21,9 +21,11 @@ import (
"github.com/op/go-logging"
)
// Config contains the config for the web handler
type Config struct {
AuthProvider *web.Auths
LoggingProvider *logging.Logger
MaxItemsPerPage int
}
var config *Config
@ -32,10 +34,17 @@ func init() {
config = &Config{}
}
// SetAuthProvider sets the auth provider in config
func SetAuthProvider(provider *web.Auths) {
config.AuthProvider = provider
}
// SetLoggingProvider sets the logging provider in the config
func SetLoggingProvider(logger *logging.Logger) {
config.LoggingProvider = logger
}
// SetMaxItemsPerPage sets the max number of items per page in the config
func SetMaxItemsPerPage(maxItemsPerPage int) {
config.MaxItemsPerPage = maxItemsPerPage
}

View File

@ -17,6 +17,7 @@ package handler
import (
"github.com/labstack/echo/v4"
"math"
"net/http"
"strconv"
)
@ -47,16 +48,56 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
}
if pageNumber < 0 {
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
return echo.NewHTTPError(http.StatusBadRequest, "Page number cannot be negative.")
}
// Items per page
var perPageNumber int
perPage := ctx.QueryParam("per_page")
// If we dont have an "items per page" parameter, we want to use the default.
// To prevent Atoi from failing, we check this here.
if perPage != "" {
perPageNumber, err = strconv.Atoi(perPage)
if err != nil {
config.LoggingProvider.Error(err.Error())
return echo.NewHTTPError(http.StatusBadRequest, "Bad per page amount requested.")
}
}
// Set default page count
if perPageNumber == 0 {
perPageNumber = config.MaxItemsPerPage
}
if perPageNumber < 1 {
return echo.NewHTTPError(http.StatusBadRequest, "Per page amount cannot be negative.")
}
if perPageNumber > config.MaxItemsPerPage {
perPageNumber = config.MaxItemsPerPage
}
// Search
search := ctx.QueryParam("s")
lists, err := currentStruct.ReadAll(search, currentAuth, pageNumber)
result, resultCount, numberOfItems, err := currentStruct.ReadAll(currentAuth, search, pageNumber, perPageNumber)
if err != nil {
return HandleHTTPError(err, ctx)
}
return ctx.JSON(http.StatusOK, lists)
// Calculate the number of pages from the number of items
// We always round up, because if we don't have a number of items which is exactly dividable by the number of items per page,
// we would get a result that is one page off.
var numberOfPages = math.Ceil(float64(numberOfItems) / float64(perPageNumber))
// If we return all results, we only have one page
if pageNumber < 0 {
numberOfPages = 1
}
// If we don't have results, we don't have a page
if resultCount == 0 {
numberOfPages = 0
}
ctx.Response().Header().Set("x-pagination-total-pages", strconv.FormatFloat(numberOfPages, 'f', 0, 64))
ctx.Response().Header().Set("x-pagination-result-count", strconv.FormatInt(int64(resultCount), 10))
ctx.Response().Header().Set("Access-Control-Expose-Headers", "x-pagination-total-pages, x-pagination-result-count")
return ctx.JSON(http.StatusOK, result)
}

2
vendor/code.vikunja.io/web/web.go generated vendored
View File

@ -31,7 +31,7 @@ type Rights interface {
type CRUDable interface {
Create(Auth) error
ReadOne() error
ReadAll(string, Auth, int) (interface{}, error)
ReadAll(auth Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error)
Update() error
Delete() error
}