diff --git a/kernel/av/filter.go b/kernel/av/filter.go index 50cc15713..8dd8c137e 100644 --- a/kernel/av/filter.go +++ b/kernel/av/filter.go @@ -22,7 +22,7 @@ import ( ) type Filterable interface { - FilterRows() + FilterRows(attrView *AttributeView) } type ViewFilter struct { diff --git a/kernel/av/table.go b/kernel/av/table.go index 57ea9a2a4..3928ac4da 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -230,7 +230,44 @@ func (value *Value) Compare(other *Value) int { return 0 } -func (value *Value) CompareOperator(other *Value, operator FilterOperator) bool { +func (value *Value) CompareOperator(other *Value, operator FilterOperator, attrView *AttributeView, rowID string) bool { + if nil != value.Rollup && nil != other.Rollup { + rollupKey, _ := attrView.GetKey(value.KeyID) + if nil == rollupKey { + return false + } + relKey, _ := attrView.GetKey(rollupKey.Rollup.RelationKeyID) + if nil == relKey { + return false + } + + relVal := attrView.GetValue(relKey.ID, rowID) + if nil == relVal || nil == relVal.Relation { + return false + } + + destAv, _ := ParseAttributeView(relKey.Relation.AvID) + if nil == destAv { + return false + } + + for _, blockID := range relVal.Relation.BlockIDs { + destVal := destAv.GetValue(rollupKey.Rollup.KeyID, blockID) + if nil == destVal { + continue + } + + if destVal.compareOperator(other, operator, attrView) { + return true + } + } + return false + } + + return value.compareOperator(other, operator, attrView) +} + +func (value *Value) compareOperator(other *Value, operator FilterOperator, attrView *AttributeView) bool { if nil == other { return true } @@ -639,9 +676,6 @@ func (value *Value) CompareOperator(other *Value, operator FilterOperator) bool } } - if nil != value.Rollup && nil != other.Rollup { - // TODO: rollup filter - } return false } @@ -745,7 +779,7 @@ func (table *Table) SortRows() { }) } -func (table *Table) FilterRows() { +func (table *Table) FilterRows(attrView *AttributeView) { if 1 > len(table.Filters) { return } @@ -780,7 +814,7 @@ func (table *Table) FilterRows() { break } - if !row.Cells[index].Value.CompareOperator(table.Filters[j].Value, operator) { + if !row.Cells[index].Value.CompareOperator(table.Filters[j].Value, operator, attrView, row.ID) { pass = false break } diff --git a/kernel/av/value.go b/kernel/av/value.go index 4c0c6bddd..46ab870f9 100644 --- a/kernel/av/value.go +++ b/kernel/av/value.go @@ -19,6 +19,7 @@ package av import ( "fmt" "math" + "sort" "strconv" "strings" "time" @@ -469,3 +470,132 @@ type ValueRelation struct { type ValueRollup struct { Contents []string `json:"contents"` } + +func (r *ValueRollup) RenderContents(calc *RollupCalc) { + if nil == calc { + return + } + + switch calc.Operator { + case CalcOperatorNone: + case CalcOperatorCountAll: + r.Contents = []string{strconv.Itoa(len(r.Contents))} + case CalcOperatorCountValues: + r.Contents = []string{strconv.Itoa(len(r.Contents))} + case CalcOperatorCountUniqueValues: + countUniqueValues := 0 + uniqueValues := map[string]bool{} + for _, v := range r.Contents { + if !uniqueValues[v] { + uniqueValues[v] = true + countUniqueValues++ + } + } + r.Contents = []string{strconv.Itoa(countUniqueValues)} + case CalcOperatorCountEmpty: + countEmpty := 0 + for _, v := range r.Contents { + if "" == v { + countEmpty++ + } + } + r.Contents = []string{strconv.Itoa(countEmpty)} + case CalcOperatorCountNotEmpty: + countNonEmpty := 0 + for _, v := range r.Contents { + if "" != v { + countNonEmpty++ + } + } + r.Contents = []string{strconv.Itoa(countNonEmpty)} + case CalcOperatorPercentEmpty: + countEmpty := 0 + for _, v := range r.Contents { + if "" == v { + countEmpty++ + } + } + r.Contents = []string{strconv.Itoa(countEmpty*100/len(r.Contents)) + "%"} + case CalcOperatorPercentNotEmpty: + countNonEmpty := 0 + for _, v := range r.Contents { + if "" != v { + countNonEmpty++ + } + } + r.Contents = []string{strconv.Itoa(countNonEmpty*100/len(r.Contents)) + "%"} + case CalcOperatorSum: + sum := 0.0 + for _, v := range r.Contents { + if "" != v { + n, _ := strconv.ParseFloat(v, 64) + sum += n + } + } + r.Contents = []string{strconv.FormatFloat(sum, 'f', -1, 64)} + case CalcOperatorAverage: + sum := 0.0 + count := 0 + for _, v := range r.Contents { + if "" != v { + n, _ := strconv.ParseFloat(v, 64) + sum += n + count++ + } + } + r.Contents = []string{strconv.FormatFloat(sum/float64(count), 'f', -1, 64)} + case CalcOperatorMedian: + numbers := []float64{} + for _, v := range r.Contents { + if "" != v { + n, _ := strconv.ParseFloat(v, 64) + numbers = append(numbers, n) + } + } + sort.Float64s(numbers) + if 0 < len(numbers) { + if 0 == len(numbers)%2 { + r.Contents = []string{strconv.FormatFloat((numbers[len(numbers)/2-1]+numbers[len(numbers)/2])/2, 'f', -1, 64)} + } else { + r.Contents = []string{strconv.FormatFloat(numbers[len(numbers)/2], 'f', -1, 64)} + } + } + case CalcOperatorMin: + min := math.MaxFloat64 + for _, v := range r.Contents { + if "" != v { + n, _ := strconv.ParseFloat(v, 64) + if n < min { + min = n + } + } + } + r.Contents = []string{strconv.FormatFloat(min, 'f', -1, 64)} + case CalcOperatorMax: + max := -math.MaxFloat64 + for _, v := range r.Contents { + if "" != v { + n, _ := strconv.ParseFloat(v, 64) + if n > max { + max = n + } + } + } + r.Contents = []string{strconv.FormatFloat(max, 'f', -1, 64)} + case CalcOperatorRange: + min := math.MaxFloat64 + max := -math.MaxFloat64 + for _, v := range r.Contents { + if "" != v { + n, _ := strconv.ParseFloat(v, 64) + if n < min { + min = n + } + if n > max { + max = n + } + } + } + r.Contents = []string{strconv.FormatFloat(max-min, 'f', -1, 64)} + } +} diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 31f9cbb3d..29f3aca01 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -211,6 +211,8 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) { } switch kValues.Key.Type { + case av.KeyTypeRollup: + kValues.Values = append(kValues.Values, &av.Value{ID: ast.NewNodeID(), KeyID: kValues.Key.ID, BlockID: blockID, Type: av.KeyTypeRollup, Rollup: &av.ValueRollup{Contents: []string{}}}) case av.KeyTypeTemplate: kValues.Values = append(kValues.Values, &av.Value{ID: ast.NewNodeID(), KeyID: kValues.Key.ID, BlockID: blockID, Type: av.KeyTypeTemplate, Template: &av.ValueTemplate{Content: ""}}) case av.KeyTypeCreated: @@ -238,16 +240,23 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) { break } - var blockIDs []string relVal := attrView.GetValue(kv.Key.Rollup.RelationKeyID, kv.Values[0].BlockID) if nil != relVal && nil != relVal.Relation { - blockIDs = relVal.Relation.BlockIDs - } + destAv, _ := av.ParseAttributeView(relKey.Relation.AvID) + if nil != destAv { + for _, bID := range relVal.Relation.BlockIDs { + destVal := destAv.GetValue(kv.Key.Rollup.KeyID, bID) + if nil != destVal { + if av.KeyTypeNumber == destVal.Type { + destVal.Number.Format = kv.Key.NumberFormat + destVal.Number.FormatNumber() + } - destAv, _ := av.ParseAttributeView(relKey.Relation.AvID) - if nil != destAv { - for _, bID := range blockIDs { - kv.Values[0].Rollup.Contents = append(kv.Values[0].Rollup.Contents, destAv.GetValue(kv.Key.Rollup.KeyID, bID).String()) + kv.Values[0].Rollup.Contents = append(kv.Values[0].Rollup.Contents, destAv.GetValue(kv.Key.Rollup.KeyID, bID).String()) + } + + kv.Values[0].Rollup.RenderContents(kv.Key.Rollup.Calc) + } } } case av.KeyTypeRelation: @@ -547,7 +556,7 @@ func renderAttributeView(attrView *av.AttributeView, viewID string, page, pageSi viewable, err = renderAttributeViewTable(attrView, view) } - viewable.FilterRows() + viewable.FilterRows(attrView) viewable.SortRows() viewable.CalcCols() @@ -806,6 +815,8 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *a cell.Value.Rollup.Contents = append(cell.Value.Rollup.Contents, destVal.String()) } + + cell.Value.Rollup.RenderContents(rollupKey.Rollup.Calc) case av.KeyTypeRelation: // 渲染关联列 relKey, _ := attrView.GetKey(cell.Value.KeyID) if nil != relKey && nil != relKey.Relation { @@ -1515,7 +1526,7 @@ func addAttributeViewBlock(blockID string, operation *Operation, tree *parse.Tre view, _ := attrView.GetCurrentView() if nil != view && 0 < len(view.Table.Filters) { viewable, _ := renderAttributeViewTable(attrView, view) - viewable.FilterRows() + viewable.FilterRows(attrView) viewable.SortRows() addedVal := false diff --git a/kernel/treenode/node.go b/kernel/treenode/node.go index bbf792455..ee906e6f2 100644 --- a/kernel/treenode/node.go +++ b/kernel/treenode/node.go @@ -757,8 +757,11 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *a destVal.Number.Format = rollupKey.NumberFormat destVal.Number.FormatNumber() } + cell.Value.Rollup.Contents = append(cell.Value.Rollup.Contents, destVal.String()) } + + cell.Value.Rollup.RenderContents(rollupKey.Rollup.Calc) case av.KeyTypeRelation: // 渲染关联列 relKey, _ := attrView.GetKey(cell.Value.KeyID) if nil != relKey && nil != relKey.Relation {