mirror of
https://github.com/documize/community.git
synced 2025-08-02 20:15:26 +02:00
improve level code
This commit is contained in:
parent
049b83e0b9
commit
5f59e95495
25 changed files with 1104 additions and 461 deletions
|
@ -38,6 +38,9 @@ const (
|
|||
|
||||
// SourceTypeDocument indicates activity against a document.
|
||||
SourceTypeDocument SourceType = 2
|
||||
|
||||
// SourceTypePage indicates activity against a document page.
|
||||
SourceTypePage SourceType = 3
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -70,6 +73,9 @@ const (
|
|||
|
||||
// TypeFeedback records user providing document feedback
|
||||
TypeFeedback Type = 10
|
||||
|
||||
// TypeRejected records user rejecting document
|
||||
TypeRejected Type = 11
|
||||
)
|
||||
|
||||
// DocumentActivity represents an activity taken against a document.
|
||||
|
|
|
@ -45,6 +45,13 @@ func (d *Document) SetDefaults() {
|
|||
}
|
||||
}
|
||||
|
||||
// ByTitle sorts a collection of documents by document title.
|
||||
type ByTitle []Document
|
||||
|
||||
func (a ByTitle) Len() int { return len(a) }
|
||||
func (a ByTitle) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByTitle) Less(i, j int) bool { return strings.ToLower(a[i].Title) < strings.ToLower(a[j].Title) }
|
||||
|
||||
// DocumentMeta details who viewed the document.
|
||||
type DocumentMeta struct {
|
||||
Viewers []DocumentMetaViewer `json:"viewers"`
|
||||
|
|
|
@ -72,3 +72,71 @@ func Numberize(pages []Page) {
|
|||
prevPageLevel = p.Level
|
||||
}
|
||||
}
|
||||
|
||||
// Levelize ensure page level increments are consistent
|
||||
// after a page is inserted or removed.
|
||||
//
|
||||
// Valid: 1, 2, 3, 4, 4, 4, 2, 1
|
||||
// Invalid: 1, 2, 4, 4, 2, 1 (note the jump from 2 --> 4)
|
||||
//
|
||||
// Rules:
|
||||
// 1. levels can increase by 1 only (e.g. from 1 to 2 to 3 to 4)
|
||||
// 2. levels can decrease by any amount (e.g. drop from 4 to 1)
|
||||
func Levelize(pages []Page) {
|
||||
var prevLevel uint64
|
||||
prevLevel = 1
|
||||
|
||||
for i := 0; i < len(pages); i++ {
|
||||
currLevel := pages[i].Level
|
||||
|
||||
// handle deprecated level value of 0
|
||||
if pages[i].Level == 0 {
|
||||
pages[i].Level = 1
|
||||
}
|
||||
|
||||
// first time thru
|
||||
if i == 0 {
|
||||
// first time thru
|
||||
pages[i].Level = 1
|
||||
prevLevel = 1
|
||||
continue
|
||||
}
|
||||
|
||||
if currLevel == prevLevel {
|
||||
// nothing doing
|
||||
continue
|
||||
}
|
||||
|
||||
if currLevel > prevLevel+1 {
|
||||
// bad data detected e.g. prevLevel=1 and pages[i].Level=3
|
||||
// so we re-level to pages[i].Level=2 and all child pages
|
||||
// and then increment i to correct point
|
||||
prevLevel++
|
||||
pages[i].Level = prevLevel
|
||||
|
||||
// safety check before entering loop and renumbering child pages
|
||||
if i+1 <= len(pages) {
|
||||
|
||||
for j := i + 1; j < len(pages); j++ {
|
||||
if pages[j].Level < prevLevel {
|
||||
i = j
|
||||
break
|
||||
}
|
||||
|
||||
if pages[j].Level == currLevel {
|
||||
pages[j].Level = prevLevel
|
||||
} else if (pages[j].Level - prevLevel) > 1 {
|
||||
currLevel = pages[j].Level
|
||||
prevLevel++
|
||||
pages[j].Level = prevLevel
|
||||
}
|
||||
|
||||
i = j
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
prevLevel = currLevel
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,4 +111,124 @@ func TestNumberize3(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// go test github.com/documize/community/core/model -run TestNumberize
|
||||
// Tests that numbering does not crash because of bad data
|
||||
func TestNumberize4(t *testing.T) {
|
||||
pages := []Page{}
|
||||
|
||||
pages = append(pages, Page{Level: 0, Sequence: 1000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 2000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 3000})
|
||||
|
||||
// corruption starts here with Level=3 instead of Level=2
|
||||
pages = append(pages, Page{Level: 3, Sequence: 4000})
|
||||
pages = append(pages, Page{Level: 4, Sequence: 4000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 5000})
|
||||
pages = append(pages, Page{Level: 2, Sequence: 6000})
|
||||
|
||||
Numberize(pages)
|
||||
|
||||
expecting := []string{
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"3.1",
|
||||
"3.1.1",
|
||||
// data below cannot be processed due to corruption
|
||||
"", // should be 4
|
||||
"1", // should be 5
|
||||
}
|
||||
|
||||
for i, p := range pages {
|
||||
if p.Numbering != expecting[i] {
|
||||
t.Errorf("(Test 4) Position %d: expecting %s got %s\n", i, expecting[i], p.Numbering)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that good level data is not messed with
|
||||
func TestLevelize1(t *testing.T) {
|
||||
pages := []Page{}
|
||||
|
||||
pages = append(pages, Page{Level: 1, Sequence: 1000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 2000})
|
||||
pages = append(pages, Page{Level: 2, Sequence: 3000})
|
||||
pages = append(pages, Page{Level: 3, Sequence: 4000})
|
||||
pages = append(pages, Page{Level: 4, Sequence: 5000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 6000})
|
||||
pages = append(pages, Page{Level: 2, Sequence: 7000})
|
||||
|
||||
Levelize(pages)
|
||||
|
||||
expecting := []uint64{1, 1, 2, 3, 4, 1, 2}
|
||||
|
||||
for i, p := range pages {
|
||||
if p.Level != expecting[i] {
|
||||
t.Errorf("(TestLevelize1) Position %d: expecting %d got %d (sequence: %f)\n", i+1, expecting[i], p.Level, p.Sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that bad level data
|
||||
func TestLevelize2(t *testing.T) {
|
||||
pages := []Page{}
|
||||
|
||||
pages = append(pages, Page{Level: 1, Sequence: 1000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 2000})
|
||||
pages = append(pages, Page{Level: 3, Sequence: 3000})
|
||||
pages = append(pages, Page{Level: 3, Sequence: 4000})
|
||||
pages = append(pages, Page{Level: 4, Sequence: 5000})
|
||||
pages = append(pages, Page{Level: 1, Sequence: 6000})
|
||||
pages = append(pages, Page{Level: 2, Sequence: 7000})
|
||||
|
||||
Levelize(pages)
|
||||
|
||||
expecting := []uint64{1, 1, 2, 2, 3, 1, 2}
|
||||
|
||||
for i, p := range pages {
|
||||
if p.Level != expecting[i] {
|
||||
t.Errorf("(TestLevelize2) Position %d: expecting %d got %d (sequence: %f)\n", i+1, expecting[i], p.Level, p.Sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelize3(t *testing.T) {
|
||||
pages := []Page{}
|
||||
|
||||
pages = append(pages, Page{Level: 1, Sequence: 1000})
|
||||
pages = append(pages, Page{Level: 4, Sequence: 2000})
|
||||
pages = append(pages, Page{Level: 5, Sequence: 3000})
|
||||
|
||||
Levelize(pages)
|
||||
|
||||
expecting := []uint64{1, 2, 3}
|
||||
|
||||
for i, p := range pages {
|
||||
if p.Level != expecting[i] {
|
||||
t.Errorf("(TestLevelize3) Position %d: expecting %d got %d (sequence: %f)\n", i+1, expecting[i], p.Level, p.Sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelize4(t *testing.T) {
|
||||
pages := []Page{}
|
||||
|
||||
pages = append(pages, Page{Level: 1, Sequence: 1000})
|
||||
pages = append(pages, Page{Level: 4, Sequence: 2000})
|
||||
pages = append(pages, Page{Level: 5, Sequence: 3000})
|
||||
pages = append(pages, Page{Level: 5, Sequence: 4000})
|
||||
pages = append(pages, Page{Level: 6, Sequence: 5000})
|
||||
pages = append(pages, Page{Level: 6, Sequence: 6000})
|
||||
pages = append(pages, Page{Level: 2, Sequence: 7000})
|
||||
|
||||
Levelize(pages)
|
||||
|
||||
expecting := []uint64{1, 2, 3, 3, 4, 4, 2}
|
||||
|
||||
for i, p := range pages {
|
||||
if p.Level != expecting[i] {
|
||||
t.Errorf("(TestLevelize4) Position %d: expecting %d got %d (sequence: %f)\n", i+1, expecting[i], p.Level, p.Sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go test github.com/documize/community/core/model -run TestNumberiz, 3, 4, 4, 2e
|
||||
|
|
|
@ -16,23 +16,26 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/documize/community/model"
|
||||
"github.com/documize/community/model/workflow"
|
||||
)
|
||||
|
||||
// Page represents a section within a document.
|
||||
type Page struct {
|
||||
model.BaseEntity
|
||||
OrgID string `json:"orgId"`
|
||||
DocumentID string `json:"documentId"`
|
||||
UserID string `json:"userId"`
|
||||
ContentType string `json:"contentType"`
|
||||
PageType string `json:"pageType"`
|
||||
BlockID string `json:"blockId"`
|
||||
Level uint64 `json:"level"`
|
||||
Sequence float64 `json:"sequence"`
|
||||
Numbering string `json:"numbering"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Revisions uint64 `json:"revisions"`
|
||||
OrgID string `json:"orgId"`
|
||||
DocumentID string `json:"documentId"`
|
||||
UserID string `json:"userId"`
|
||||
ContentType string `json:"contentType"`
|
||||
PageType string `json:"pageType"`
|
||||
BlockID string `json:"blockId"`
|
||||
Level uint64 `json:"level"`
|
||||
Sequence float64 `json:"sequence"`
|
||||
Numbering string `json:"numbering"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Revisions uint64 `json:"revisions"`
|
||||
Status workflow.ChangeStatus `json:"status"`
|
||||
RelativeID string `json:"relativeId"` // links page to pending page edits
|
||||
}
|
||||
|
||||
// SetDefaults ensures no blank values.
|
||||
|
@ -41,6 +44,10 @@ func (p *Page) SetDefaults() {
|
|||
p.ContentType = "wysiwyg"
|
||||
}
|
||||
|
||||
if p.Level == 0 {
|
||||
p.Level = 1
|
||||
}
|
||||
|
||||
p.Title = strings.TrimSpace(p.Title)
|
||||
}
|
||||
|
||||
|
@ -114,3 +121,18 @@ type LevelRequest struct {
|
|||
PageID string `json:"pageId"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// BulkRequest details page, it's meta, pending page changes.
|
||||
// Used to bulk load data by GUI so as to reduce network requests.
|
||||
type BulkRequest struct {
|
||||
Page Page `json:"page"`
|
||||
Meta Meta `json:"meta"`
|
||||
Pending []PendingPage `json:"pending"`
|
||||
}
|
||||
|
||||
// PendingPage details page that is yet to be published
|
||||
type PendingPage struct {
|
||||
Page Page `json:"page"`
|
||||
Meta Meta `json:"meta"`
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
|
|
@ -15,29 +15,51 @@ package workflow
|
|||
type Protection int
|
||||
|
||||
const (
|
||||
// NoProtection means no protection so data item changes are permitted
|
||||
NoProtection Protection = 0
|
||||
// ProtectionNone means no protection so data item changes are permitted
|
||||
ProtectionNone Protection = 0
|
||||
|
||||
// Lock means no data itme changes
|
||||
Lock Protection = 1
|
||||
// ProtectionLock means no data itme changes
|
||||
ProtectionLock Protection = 1
|
||||
|
||||
// Review means changes must be reviewed and approved
|
||||
Review Protection = 2
|
||||
// ProtectionReview means changes must be reviewed and approved
|
||||
ProtectionReview Protection = 2
|
||||
)
|
||||
|
||||
// Approval tells us how some data item change is to be approved
|
||||
type Approval int
|
||||
|
||||
const (
|
||||
// NoApproval means no approval necessary
|
||||
NoApproval Approval = 0
|
||||
// ApprovalNone means no approval necessary
|
||||
ApprovalNone Approval = 0
|
||||
|
||||
// Anybody can approve data item change
|
||||
Anybody Approval = 1
|
||||
// ApprovalAnybody can approve data item change
|
||||
ApprovalAnybody Approval = 1
|
||||
|
||||
// Majority must approve data item change
|
||||
Majority Approval = 2
|
||||
// ApprovalMajority must approve data item change
|
||||
ApprovalMajority Approval = 2
|
||||
|
||||
// Unanimous approval must be given for data item change
|
||||
Unanimous Approval = 3
|
||||
// ApprovalUnanimous approval must be given for data item change
|
||||
ApprovalUnanimous Approval = 3
|
||||
)
|
||||
|
||||
// ChangeStatus tells us the state of a data item
|
||||
type ChangeStatus int
|
||||
|
||||
const (
|
||||
// ChangePublished means data item is visible all
|
||||
ChangePublished ChangeStatus = 0
|
||||
|
||||
// ChangePending means data item is still being edited and not yet requesting review
|
||||
ChangePending ChangeStatus = 1
|
||||
|
||||
// ChangeUnderReview means data item is being reviewed
|
||||
// Next step would be to mark data item as either
|
||||
// Published or Rejected
|
||||
ChangeUnderReview ChangeStatus = 2
|
||||
|
||||
// ChangeRejected means data item was not approved for publication
|
||||
ChangeRejected ChangeStatus = 3
|
||||
|
||||
// ChangePendingNew means a new section to a document is pending review
|
||||
ChangePendingNew ChangeStatus = 4
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue