statistics: Better display on the front page

This commit is contained in:
Tom Wiesing 2022-11-16 19:51:17 +01:00
parent bc1bf0db1c
commit 9a1cf4e53b
No known key found for this signature in database
12 changed files with 181 additions and 59 deletions

View file

@ -1,7 +1,7 @@
package cli
// ===========================================================================================================
// This file was generated automatically at 16-11-2022 12:32:57 using gogenlicense.
// This file was generated automatically at 16-11-2022 18:40:47 using gogenlicense.
// Do not edit manually, as changes may be overwritten.
// ===========================================================================================================
@ -1631,7 +1631,7 @@ package cli
// # Generation
//
// This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool.
// It was last updated at 16-11-2022 12:32:57.
// It was last updated at 16-11-2022 18:40:47.
var LegalNotices string
func init() {

View file

@ -27,6 +27,12 @@
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
<small>
{{ .Statistics.Bundles.Summary }}
{{ $edit := .Statistics.Bundles.LastEdit }}
{{ if $edit.Valid }}
<br />
last edited {{ $edit.Time.Format "2006-01-02T15:04:05Z07:00" }}
{{ end }}
</small>
</p>
</div>

View file

@ -310,7 +310,7 @@
<code>{{ .Count }}</code>
</td>
<td>
<code>{{ .LastEdit }}</code>
<code class="date">{{ .LastEdit.Time.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
<code>{{ .MainBundle }}</code>

View file

@ -16,13 +16,13 @@ var AssetsComponentsIndex = Assets{
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
var AssetsControlIndex = Assets{
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script src="/static/ControlIndex.613b02c2.js" nomodule="" defer></script>`,
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.6d1d8ee0.js"></script><script src="/static/ControlIndex.03d7b00f.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css">`,
}
// AssetsControlInstance contains assets for the 'ControlInstance' entrypoint.
var AssetsControlInstance = Assets{
Scripts: `<script nomodule="" defer src="/static/ControlIndex.613b02c2.js"></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
Scripts: `<script nomodule="" defer src="/static/ControlIndex.03d7b00f.js"></script><script type="module" src="/static/ControlIndex.6d1d8ee0.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css"><link rel="stylesheet" href="/static/ControlInstance.38d394c2.css">`,
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,17 @@
import dayjs from "dayjs"
const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
"date": (element) => {
return dayjs(element.innerText).format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
const value = dayjs(element.innerText);
const text = value.format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
// if the date is the zero date, then it is assumed to be invalid
if (value.unix() === 0) {
const code = document.createElement('code')
code.style.color = 'gray'
code.append(text)
return code
}
return text
},
"path": (element) => {
const text = element.innerText.split("/");

View file

@ -1,49 +0,0 @@
package phpx
import "encoding/json"
// BooleanIsh represents a boolean php value.
//
// The value can be serialized to and from php and will behave accordingly.
//
// The value will always be Marshaled as "true" or "false".
//
// When Unmarshaled, it behaves as described on https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting.
type BooleanIsh bool
func (bi BooleanIsh) MarshalJSON() ([]byte, error) {
if bi {
return []byte("true"), nil
}
return []byte("false"), nil
}
func (bi *BooleanIsh) UnmarshalJSON(data []byte) (err error) {
// unmarshal into a generic value
var value any
err = json.Unmarshal(data, &value)
if err != nil {
return err
}
// check if it is false ish
var isFalseIsh bool
switch d := value.(type) {
case bool:
isFalseIsh = !d
case int:
isFalseIsh = d == 0
case float64:
isFalseIsh = d == 0
case string:
isFalseIsh = d == "" || d == "0"
case []any:
isFalseIsh = len(d) == 0
case map[string]any:
isFalseIsh = len(d) == 0
case nil:
isFalseIsh = true
}
*bi = BooleanIsh(!isFalseIsh)
return nil
}

138
internal/phpx/types.go Normal file
View file

@ -0,0 +1,138 @@
package phpx
import (
"encoding/json"
"strconv"
"time"
)
// PHPBoolean represents a boolean php value.
//
// The value can be marshaled to and from php and will behave as a PHP would behave.
//
// The value will always be marshaled as "true" or "false".
// Unmarshaling uses [Boolean].
type PHPBoolean bool
func (bi PHPBoolean) MarshalJSON() ([]byte, error) {
if bi {
return []byte("true"), nil
}
return []byte("false"), nil
}
func (bi *PHPBoolean) UnmarshalJSON(data []byte) (err error) {
// unmarshal into a generic value
var value any
err = json.Unmarshal(data, &value)
if err != nil {
return err
}
// cast into a boolean
cast, ok := Boolean(value)
if !ok {
value = false
}
*bi = PHPBoolean(cast)
return nil
}
// Boolean tries to cast the given value to a boolean.
//
// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
// Value treates all values as the boolean true, except for the ones listed at [doc].
//
// [doc]: https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting
func Boolean(value any) (b bool, ok bool) {
switch d := value.(type) {
case bool:
return d, true
case float64:
return d != 0, true
case string:
return (d != "" && d != "0"), true
case []any:
return len(d) != 0, true
case map[string]any:
return len(d) != 0, true
case nil:
return true, true
}
return true, false
}
// String tries to cast the given value to a string.
//
// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
// Value casting is described at [doc].
//
// [doc]: https://www.php.net/manual/en/language.types.string.php#language.types.string.casting
func String(value any) (s string, ok bool) {
switch d := value.(type) {
case bool:
if d {
return "1", true
}
return "", true
case float64:
if d == float64(int64(d)) {
return strconv.FormatInt(int64(d), 10), true
}
// TODO: not sure this is entirely correct
// and we should handle ints here!
return strconv.FormatFloat(d, 'E', 1, 64), true
case string:
return d, true
case []any, map[string]any:
return "Array", true
case nil:
return "", true
}
return "", false
}
// Integer tries to cast the given value to an integer.
//
// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
// Value casting is described at [doc].
//
// [doc]: https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting
func Integer(value any) (i int64, ok bool) {
str, ok := String(value)
if !ok {
return 0, false
}
// try to parse the "leading" string, by successively cutting off parts of the tail
// once we have a valid number, return it.
for l := 0; l < len(str); l++ {
i64, err := strconv.ParseInt(str[:len(str)-l], 10, 64)
if err != nil {
continue
}
return i64, true
}
return 0, true
}
// TimeInt represents a time value in PHP, represented as an integer
type TimeInt time.Time
func (ts TimeInt) Time() time.Time {
return time.Time(ts)
}
func (ts TimeInt) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(ts.Time().Unix(), 10)), nil
}
func (ts *TimeInt) UnmarshalJSON(data []byte) (err error) {
var value any
if err := json.Unmarshal(data, &value); err != nil {
return err
}
unix, _ := Integer(value)
*ts = TimeInt(time.Unix(unix, 0))
return nil
}

View file

@ -84,14 +84,31 @@ type BundleStatistics struct {
Count int `json:"entities"`
LastEdit int `json:"lastEdit"`
LastEdit phpx.TimeInt `json:"lastEdit"`
MainBundle phpx.BooleanIsh `json:"mainBundle"`
MainBundle phpx.PHPBoolean `json:"mainBundle"`
} `json:"bundleStatistics"`
TotalBundles int `json:"totalBundles"`
TotalMainBundles int `json:"totalMainBundles"`
}
type LastEdit struct {
Time time.Time
Valid bool
}
// LastEdit returns the last time any bundle was edited, and if any edit was bigger than the reference time
func (bs BundleStatistics) LastEdit() (le LastEdit) {
for _, bundle := range bs.Bundles {
time := bundle.LastEdit.Time()
if time.After(le.Time) {
le.Valid = true
le.Time = time
}
}
return
}
func (bs BundleStatistics) Summary() string {
var totalCount int
for _, bundle := range bs.Bundles {