bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
352 lines
7.7 KiB
Go
352 lines
7.7 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package apps
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type appsCellFormatter func(interface{}) string
|
|
|
|
type appsOutputColumn struct {
|
|
Key string
|
|
Label string
|
|
Value func(map[string]interface{}) interface{}
|
|
Format appsCellFormatter
|
|
}
|
|
|
|
type appsOutputSchema struct {
|
|
Columns []appsOutputColumn
|
|
Strict bool
|
|
}
|
|
|
|
func appsProjectRows(rows []map[string]interface{}, schema appsOutputSchema) []map[string]interface{} {
|
|
out := make([]map[string]interface{}, 0, len(rows))
|
|
for _, row := range rows {
|
|
out = append(out, appsProjectRow(row, schema))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appsProjectRow(row map[string]interface{}, schema appsOutputSchema) map[string]interface{} {
|
|
out := make(map[string]interface{}, len(schema.Columns))
|
|
declared := make(map[string]struct{}, len(schema.Columns))
|
|
for _, col := range schema.Columns {
|
|
if col.Key == "" {
|
|
continue
|
|
}
|
|
declared[col.Key] = struct{}{}
|
|
value := row[col.Key]
|
|
if col.Value != nil {
|
|
value = col.Value(row)
|
|
}
|
|
if value != nil {
|
|
out[col.Key] = value
|
|
}
|
|
}
|
|
if !schema.Strict {
|
|
for key, value := range row {
|
|
if _, ok := declared[key]; !ok {
|
|
out[key] = value
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appsPrintSchemaTable(w io.Writer, rows []map[string]interface{}, schema appsOutputSchema) {
|
|
if len(rows) == 0 {
|
|
fmt.Fprintln(w, "(no data)")
|
|
return
|
|
}
|
|
headers := make([]string, 0, len(schema.Columns))
|
|
for _, col := range schema.Columns {
|
|
if col.Key == "" {
|
|
continue
|
|
}
|
|
headers = append(headers, appsColumnLabel(col))
|
|
}
|
|
if len(headers) == 0 {
|
|
fmt.Fprintln(w, "(no data)")
|
|
return
|
|
}
|
|
matrix := make([][]string, 0, len(rows)+1)
|
|
matrix = append(matrix, headers)
|
|
for _, row := range rows {
|
|
line := make([]string, 0, len(schema.Columns))
|
|
for _, col := range schema.Columns {
|
|
if col.Key == "" {
|
|
continue
|
|
}
|
|
value := row[col.Key]
|
|
if col.Value != nil {
|
|
value = col.Value(row)
|
|
}
|
|
line = append(line, appsFormatCell(value, col.Format))
|
|
}
|
|
matrix = append(matrix, line)
|
|
}
|
|
widths := appsColumnWidths(matrix)
|
|
for i, row := range matrix {
|
|
cells := make([]string, len(row))
|
|
for j, cell := range row {
|
|
cells[j] = appsPad(cell, widths[j])
|
|
}
|
|
fmt.Fprintln(w, strings.TrimRight(strings.Join(cells, " "), " "))
|
|
if i == 0 {
|
|
sep := make([]string, len(widths))
|
|
for j, width := range widths {
|
|
sep[j] = strings.Repeat("─", width)
|
|
}
|
|
fmt.Fprintln(w, strings.Join(sep, " "))
|
|
}
|
|
}
|
|
}
|
|
|
|
func appsColumnLabel(col appsOutputColumn) string {
|
|
if col.Label != "" {
|
|
return col.Label
|
|
}
|
|
return col.Key
|
|
}
|
|
|
|
func appsFormatCell(value interface{}, formatter appsCellFormatter) string {
|
|
if formatter != nil {
|
|
return formatter(value)
|
|
}
|
|
return appsDefaultCell(value)
|
|
}
|
|
|
|
func appsDefaultCell(value interface{}) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
switch v := value.(type) {
|
|
case string:
|
|
return v
|
|
case json.Number:
|
|
return v.String()
|
|
case bool:
|
|
return strconv.FormatBool(v)
|
|
case int:
|
|
return strconv.Itoa(v)
|
|
case int8, int16, int32, int64:
|
|
return fmt.Sprintf("%d", v)
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
return fmt.Sprintf("%d", v)
|
|
case float32:
|
|
return appsFormatFloat(float64(v))
|
|
case float64:
|
|
return appsFormatFloat(v)
|
|
default:
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return fmt.Sprint(v)
|
|
}
|
|
return string(b)
|
|
}
|
|
}
|
|
|
|
func appsFormatFloat(value float64) string {
|
|
if math.Trunc(value) == value {
|
|
return strconv.FormatInt(int64(value), 10)
|
|
}
|
|
return strconv.FormatFloat(value, 'f', -1, 64)
|
|
}
|
|
|
|
func appsColumnWidths(matrix [][]string) []int {
|
|
if len(matrix) == 0 {
|
|
return nil
|
|
}
|
|
widths := make([]int, len(matrix[0]))
|
|
for _, row := range matrix {
|
|
for i, cell := range row {
|
|
if width := utf8.RuneCountInString(cell); width > widths[i] {
|
|
widths[i] = width
|
|
}
|
|
}
|
|
}
|
|
return widths
|
|
}
|
|
|
|
func appsPad(s string, width int) string {
|
|
delta := width - utf8.RuneCountInString(s)
|
|
if delta <= 0 {
|
|
return s
|
|
}
|
|
return s + strings.Repeat(" ", delta)
|
|
}
|
|
|
|
func appsFormatNS(layout string) appsCellFormatter {
|
|
return func(value interface{}) string {
|
|
ns, ok := appsInt64Value(value)
|
|
if !ok || ns <= 0 {
|
|
return appsDefaultCell(value)
|
|
}
|
|
return time.Unix(0, ns).Local().Format(layout)
|
|
}
|
|
}
|
|
|
|
func appsFormatSec(layout string) appsCellFormatter {
|
|
return func(value interface{}) string {
|
|
sec, ok := appsInt64Value(value)
|
|
if !ok || sec <= 0 {
|
|
return appsDefaultCell(value)
|
|
}
|
|
return time.Unix(sec, 0).Local().Format(layout)
|
|
}
|
|
}
|
|
|
|
func appsFormatDurationMS(value interface{}) string {
|
|
ms, ok := appsFloat64Value(value)
|
|
if !ok || ms < 0 {
|
|
return appsDefaultCell(value)
|
|
}
|
|
switch {
|
|
case ms < 1:
|
|
return fmt.Sprintf("%.2fms", ms)
|
|
case ms < 1000:
|
|
return fmt.Sprintf("%.0fms", ms)
|
|
case ms < 60000:
|
|
return fmt.Sprintf("%.2fs", ms/1000)
|
|
case ms < 3600000:
|
|
return fmt.Sprintf("%.1fm", ms/60000)
|
|
default:
|
|
return fmt.Sprintf("%.1fh", ms/3600000)
|
|
}
|
|
}
|
|
|
|
func appsInt64Value(value interface{}) (int64, bool) {
|
|
switch v := value.(type) {
|
|
case int:
|
|
return int64(v), true
|
|
case int8:
|
|
return int64(v), true
|
|
case int16:
|
|
return int64(v), true
|
|
case int32:
|
|
return int64(v), true
|
|
case int64:
|
|
return v, true
|
|
case uint:
|
|
return appsUint64ToInt64(uint64(v))
|
|
case uint8:
|
|
return int64(v), true
|
|
case uint16:
|
|
return int64(v), true
|
|
case uint32:
|
|
return int64(v), true
|
|
case uint64:
|
|
return appsUint64ToInt64(v)
|
|
case float32:
|
|
f := float64(v)
|
|
if math.Trunc(f) == f && f <= float64(math.MaxInt64) && f >= float64(math.MinInt64) {
|
|
return int64(f), true
|
|
}
|
|
case float64:
|
|
if math.Trunc(v) == v && v <= float64(math.MaxInt64) && v >= float64(math.MinInt64) {
|
|
return int64(v), true
|
|
}
|
|
case json.Number:
|
|
if n, err := v.Int64(); err == nil {
|
|
return n, true
|
|
}
|
|
if f, err := v.Float64(); err == nil && math.Trunc(f) == f {
|
|
return int64(f), true
|
|
}
|
|
case string:
|
|
raw := strings.TrimSpace(v)
|
|
if n, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
|
return n, true
|
|
}
|
|
if f, err := strconv.ParseFloat(raw, 64); err == nil && math.Trunc(f) == f {
|
|
return int64(f), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func appsFloat64Value(value interface{}) (float64, bool) {
|
|
switch v := value.(type) {
|
|
case int:
|
|
return float64(v), true
|
|
case int8:
|
|
return float64(v), true
|
|
case int16:
|
|
return float64(v), true
|
|
case int32:
|
|
return float64(v), true
|
|
case int64:
|
|
return float64(v), true
|
|
case uint:
|
|
return float64(v), true
|
|
case uint8:
|
|
return float64(v), true
|
|
case uint16:
|
|
return float64(v), true
|
|
case uint32:
|
|
return float64(v), true
|
|
case uint64:
|
|
return float64(v), true
|
|
case float32:
|
|
return float64(v), true
|
|
case float64:
|
|
return v, true
|
|
case json.Number:
|
|
f, err := v.Float64()
|
|
return f, err == nil
|
|
case string:
|
|
f, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
|
|
return f, err == nil
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func appsUint64ToInt64(value uint64) (int64, bool) {
|
|
if value > uint64(math.MaxInt64) {
|
|
return 0, false
|
|
}
|
|
return int64(value), true
|
|
}
|
|
|
|
func appsAttrValue(key string) func(map[string]interface{}) interface{} {
|
|
return func(row map[string]interface{}) interface{} {
|
|
return appsAttributeValue(row["attributes"], key)
|
|
}
|
|
}
|
|
|
|
func appsAttributeValue(raw interface{}, key string) interface{} {
|
|
switch attrs := raw.(type) {
|
|
case map[string]interface{}:
|
|
return attrs[key]
|
|
case []interface{}:
|
|
for _, rawItem := range attrs {
|
|
item, ok := rawItem.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
itemKey := strings.TrimSpace(fmt.Sprint(firstObservabilityValue(item, "key", "name")))
|
|
if itemKey == key {
|
|
return firstObservabilityValue(item, "value")
|
|
}
|
|
}
|
|
case []map[string]interface{}:
|
|
for _, item := range attrs {
|
|
itemKey := strings.TrimSpace(fmt.Sprint(firstObservabilityValue(item, "key", "name")))
|
|
if itemKey == key {
|
|
return firstObservabilityValue(item, "value")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|