focalboard/server/model/properties.go

173 lines
4.2 KiB
Go
Raw Normal View History

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"errors"
"fmt"
)
var ErrInvalidBoardBlock = errors.New("invalid board block")
var ErrInvalidPropSchema = errors.New("invalid property schema")
var ErrInvalidProperty = errors.New("invalid property")
// BlockProperties is a map of Prop's keyed by property id.
type BlockProperties map[string]BlockProp
// BlockProp represent a property attached to a block (typically a card).
type BlockProp struct {
ID string `json:"id"`
Index int `json:"index"`
Name string `json:"name"`
Value string `json:"value"`
}
// PropSchema is a map of PropDef's keyed by property id.
type PropSchema map[string]PropDef
// PropDefOption represents an option within a property definition.
type PropDefOption struct {
ID string `json:"id"`
Index int `json:"index"`
Color string `json:"color"`
Value string `json:"value"`
}
// PropDef represents a property definition as defined in a board's Fields member.
type PropDef struct {
ID string `json:"id"`
Index int `json:"index"`
Name string `json:"name"`
Type string `json:"type"`
Options map[string]PropDefOption `json:"options"`
}
// GetValue resolves the value of a property if the passed value is an ID for an option,
// otherwise returns the original value.
func (pd PropDef) GetValue(v string) string {
// v may be an id to an option.
opt, ok := pd.Options[v]
if ok {
return opt.Value
}
return v
}
// ParsePropertySchema parses a board block's `Fields` to extract the properties
// schema for all cards within the board.
// The result is provided as a map for quick lookup, and the original order is
// preserved via the `Index` field.
func ParsePropertySchema(board *Block) (PropSchema, error) {
if board == nil || board.Type != TypeBoard {
return nil, ErrInvalidBoardBlock
}
schema := make(map[string]PropDef)
// cardProperties contains a slice of maps (untyped at this point).
cardPropsIface, ok := board.Fields["cardProperties"]
if !ok {
return schema, nil
}
cardProps, ok := cardPropsIface.([]interface{})
if !ok || len(cardProps) == 0 {
return schema, nil
}
for i, cp := range cardProps {
prop, ok := cp.(map[string]interface{})
if !ok {
return nil, ErrInvalidPropSchema
}
pd := PropDef{
ID: getMapString("id", prop),
Index: i,
Name: getMapString("name", prop),
Type: getMapString("type", prop),
Options: make(map[string]PropDefOption),
}
optsIface, ok := prop["options"]
if ok {
opts, ok := optsIface.([]interface{})
if !ok {
return nil, ErrInvalidPropSchema
}
for j, propOptIface := range opts {
propOpt, ok := propOptIface.(map[string]interface{})
if !ok {
return nil, ErrInvalidPropSchema
}
po := PropDefOption{
ID: getMapString("id", propOpt),
Index: j,
Value: getMapString("value", propOpt),
Color: getMapString("color", propOpt),
}
pd.Options[po.ID] = po
}
}
schema[pd.ID] = pd
}
return schema, nil
}
func getMapString(key string, m map[string]interface{}) string {
iface, ok := m[key]
if !ok {
return ""
}
s, ok := iface.(string)
if !ok {
return ""
}
return s
}
// ParseProperties parses a block's `Fields` to extract the properties. Properties typically exist on
// card blocks.
func ParseProperties(block *Block, schema PropSchema) (BlockProperties, error) {
props := make(map[string]BlockProp)
if block == nil {
return props, nil
}
// `properties` contains a map (untyped at this point).
propsIface, ok := block.Fields["properties"]
if !ok {
return props, nil // this is expected for blocks that don't have any properties.
}
blockProps, ok := propsIface.(map[string]interface{})
if !ok {
return props, fmt.Errorf("`properties` field wrong type: %w", ErrInvalidProperty)
}
if len(blockProps) == 0 {
return props, nil
}
for k, v := range blockProps {
s := fmt.Sprintf("%v", v)
prop := BlockProp{
ID: k,
Name: k,
Value: s,
}
def, ok := schema[k]
if ok {
prop.Name = def.Name
prop.Value = def.GetValue(s)
prop.Index = def.Index
}
props[k] = prop
}
return props, nil
}