feat(documents): rebuild project document workbench
This commit is contained in:
@@ -59,8 +59,8 @@ func (a StaticAnalyzer) Analyze(item models.SaInboxItem, userID uint) ([]Suggest
|
||||
// 默认分析器只把用户主动收集的内容整理成可审阅草稿;它不调用 provider,也不创建正式对象。
|
||||
return []Suggestion{
|
||||
{Kind: "task", Title: "跟进:" + subject, Body: body},
|
||||
{Kind: "note", Title: subject + "整理笔记", Body: body},
|
||||
{Kind: "source", Title: subject + "背景资料", Body: body},
|
||||
{Kind: "document", Title: subject + "整理笔记.md", Body: body},
|
||||
{Kind: "document", Title: subject + "背景资料.md", Body: body},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ func normalizeSuggestions(suggestions []Suggestion) ([]Suggestion, error) {
|
||||
for _, suggestion := range suggestions {
|
||||
kind := strings.ToLower(strings.TrimSpace(suggestion.Kind))
|
||||
title := strings.TrimSpace(suggestion.Title)
|
||||
if title == "" || (kind != "task" && kind != "note" && kind != "source") {
|
||||
if title == "" || (kind != "task" && kind != "document") {
|
||||
return nil, ErrInvalidSuggestion
|
||||
}
|
||||
result = append(result, Suggestion{Kind: kind, Title: title, Body: strings.TrimSpace(suggestion.Body)})
|
||||
@@ -274,17 +274,21 @@ func createConfirmedObject(tx *gorm.DB, item models.SaInboxItem, suggestion mode
|
||||
ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
|
||||
SourceInboxItemID: &sourceInboxItemID, Title: suggestion.Title, Description: suggestion.Body, Status: "open",
|
||||
}).Error
|
||||
case "note":
|
||||
return tx.Create(&models.SaNote{
|
||||
case "document":
|
||||
name := strings.TrimSpace(suggestion.Title)
|
||||
if !strings.HasSuffix(strings.ToLower(name), ".md") {
|
||||
name += ".md"
|
||||
}
|
||||
document := models.SaDocument{
|
||||
ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
|
||||
SourceInboxItemID: &sourceInboxItemID, Title: suggestion.Title, Markdown: suggestion.Body,
|
||||
}).Error
|
||||
case "source":
|
||||
// 文本型 Inbox 资料不是上传文件,不构造或伪造任何本地路径。
|
||||
return tx.Create(&models.SaSource{
|
||||
ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
|
||||
SourceInboxItemID: &sourceInboxItemID, Kind: "text", Title: suggestion.Title, ContentText: suggestion.Body,
|
||||
}).Error
|
||||
SourceInboxItemID: &sourceInboxItemID, Kind: models.DocumentKindFile, Name: name,
|
||||
NormalizedName: strings.ToLower(name), Extension: ".md", MimeType: "text/markdown; charset=utf-8",
|
||||
Size: int64(len([]byte(suggestion.Body))), Revision: 1, ScanStatus: "not_scanned",
|
||||
}
|
||||
if err := tx.Create(&document).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&models.SaDocumentContent{DocumentID: document.ID, Markdown: suggestion.Body}).Error
|
||||
default:
|
||||
return ErrInvalidSuggestion
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestStaticAnalyzerBuildsReviewableDraftsFromInboxContent(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, suggestions, 3)
|
||||
require.Equal(t, []string{"task", "note", "source"}, []string{suggestions[0].Kind, suggestions[1].Kind, suggestions[2].Kind})
|
||||
require.Equal(t, []string{"task", "document", "document"}, []string{suggestions[0].Kind, suggestions[1].Kind, suggestions[2].Kind})
|
||||
for _, suggestion := range suggestions {
|
||||
require.Contains(t, suggestion.Title, "客户访谈")
|
||||
require.Equal(t, item.Body, suggestion.Body)
|
||||
@@ -126,8 +126,8 @@ func TestConfirmCreatesOnlySelectedSavedSuggestionsWithInboxIdentity(t *testing.
|
||||
item := fixture.createInbox(t, fixture.owner.ID, fixture.project.ID)
|
||||
router := fixture.router(fixture.owner.ID, StaticAnalyzer{Suggestions: []Suggestion{
|
||||
{Kind: "task", Title: "跟进报价", Body: "联系客户确认报价"},
|
||||
{Kind: "note", Title: "会议纪要", Body: "保留讨论结论"},
|
||||
{Kind: "source", Title: "背景资料", Body: "这是一段收集内容,不是上传文件"},
|
||||
{Kind: "document", Title: "会议纪要.md", Body: "保留讨论结论"},
|
||||
{Kind: "document", Title: "背景资料.md", Body: "这是一段收集内容,不是上传文件"},
|
||||
{Kind: "task", Title: "不创建的任务", Body: "未被勾选"},
|
||||
}})
|
||||
analysisResponse := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/analyze", nil)
|
||||
@@ -155,22 +155,19 @@ func TestConfirmCreatesOnlySelectedSavedSuggestionsWithInboxIdentity(t *testing.
|
||||
require.Equal(t, fixture.owner.ID, task.CreatedBy)
|
||||
requireSourceInboxIdentity(t, item, task.SourceInboxItemID, task.SourceInboxItemIdentity)
|
||||
|
||||
var note models.SaNote
|
||||
require.NoError(t, fixture.database.First(¬e).Error)
|
||||
require.Equal(t, "会议纪要", note.Title)
|
||||
require.Equal(t, fixture.project.ID, note.ProjectID)
|
||||
require.Equal(t, fixture.owner.ID, note.CreatedBy)
|
||||
requireSourceInboxIdentity(t, item, note.SourceInboxItemID, note.SourceInboxItemIdentity)
|
||||
|
||||
var source models.SaSource
|
||||
require.NoError(t, fixture.database.First(&source).Error)
|
||||
require.Equal(t, "背景资料", source.Title)
|
||||
require.Equal(t, "text", source.Kind)
|
||||
require.Empty(t, source.FilePath)
|
||||
require.Equal(t, "这是一段收集内容,不是上传文件", source.ContentText)
|
||||
require.Equal(t, fixture.project.ID, source.ProjectID)
|
||||
require.Equal(t, fixture.owner.ID, source.CreatedBy)
|
||||
requireSourceInboxIdentity(t, item, source.SourceInboxItemID, source.SourceInboxItemIdentity)
|
||||
var documents []models.SaDocument
|
||||
require.NoError(t, fixture.database.Order("id asc").Find(&documents).Error)
|
||||
require.Len(t, documents, 2)
|
||||
require.Equal(t, "会议纪要.md", documents[0].Name)
|
||||
require.Equal(t, "背景资料.md", documents[1].Name)
|
||||
for _, document := range documents {
|
||||
require.Equal(t, fixture.project.ID, document.ProjectID)
|
||||
require.Equal(t, fixture.owner.ID, document.CreatedBy)
|
||||
requireSourceInboxIdentity(t, item, document.SourceInboxItemID, document.SourceInboxItemIdentity)
|
||||
}
|
||||
var secondContent models.SaDocumentContent
|
||||
require.NoError(t, fixture.database.Where("document_id = ?", documents[1].ID).First(&secondContent).Error)
|
||||
require.Equal(t, "这是一段收集内容,不是上传文件", secondContent.Markdown)
|
||||
|
||||
var skipped int64
|
||||
require.NoError(t, fixture.database.Model(&models.SaTask{}).Where("title = ?", "不创建的任务").Count(&skipped).Error)
|
||||
@@ -227,14 +224,15 @@ func TestConfirmIsTransactionalWhenASelectedWriteFails(t *testing.T) {
|
||||
item := fixture.createInbox(t, fixture.owner.ID, fixture.project.ID)
|
||||
router := fixture.router(fixture.owner.ID, StaticAnalyzer{Suggestions: []Suggestion{
|
||||
{Kind: "task", Title: "事务任务", Body: "必须随失败回滚"},
|
||||
{Kind: "source", Title: "失败资料", Body: "模拟持久化失败"},
|
||||
{Kind: "document", Title: "失败资料.md", Body: "模拟持久化失败"},
|
||||
}})
|
||||
analysis := decodeInboxAnalysis(t, performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/analyze", nil))
|
||||
require.Len(t, analysis.Suggestions, 2)
|
||||
|
||||
const callbackName = "test:fail_inbox_source_create"
|
||||
const callbackName = "test:fail_inbox_document_create"
|
||||
require.NoError(t, fixture.database.Callback().Create().Before("gorm:create").Register(callbackName, func(tx *gorm.DB) {
|
||||
if tx.Statement.Schema != nil && tx.Statement.Schema.Name == "SaSource" {
|
||||
document, ok := tx.Statement.Dest.(*models.SaDocument)
|
||||
if ok && document.Name == "失败资料.md" {
|
||||
tx.AddError(errors.New("simulated source write failure"))
|
||||
}
|
||||
}))
|
||||
@@ -256,8 +254,8 @@ func TestRepeatedConfirmReturnsPersistedResultAfterRelatedObjectsChange(t *testi
|
||||
item := fixture.createInbox(t, fixture.owner.ID, fixture.project.ID)
|
||||
router := fixture.router(fixture.owner.ID, StaticAnalyzer{Suggestions: []Suggestion{
|
||||
{Kind: "task", Title: "只创建一次", Body: "重复确认不能复制"},
|
||||
{Kind: "note", Title: "确认结果", Body: "原始确认创建两个对象"},
|
||||
{Kind: "source", Title: "不能追加创建", Body: "已确认后忽略不同建议"},
|
||||
{Kind: "document", Title: "确认结果.md", Body: "原始确认创建两个对象"},
|
||||
{Kind: "document", Title: "不能追加创建.md", Body: "已确认后忽略不同建议"},
|
||||
}})
|
||||
analysis := decodeInboxAnalysis(t, performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/analyze", nil))
|
||||
body := gin.H{"suggestionIds": []string{analysis.Suggestions[0].ID, analysis.Suggestions[1].ID}}
|
||||
@@ -268,11 +266,12 @@ func TestRepeatedConfirmReturnsPersistedResultAfterRelatedObjectsChange(t *testi
|
||||
requireFormalObjectCounts(t, fixture.database, 1, 1, 0)
|
||||
|
||||
sourceInboxItemID := item.ID
|
||||
extraSource := models.SaSource{
|
||||
extraDocument := models.SaDocument{
|
||||
ProjectID: item.ProjectID, CreatedBy: fixture.owner.ID, SourceInboxItemID: &sourceInboxItemID,
|
||||
Kind: "text", Title: "后续关联资料", ContentText: "不得改变历史确认结果",
|
||||
Kind: models.DocumentKindFile, Name: "后续关联资料.md", NormalizedName: "后续关联资料.md",
|
||||
Extension: ".md", Revision: 1,
|
||||
}
|
||||
require.NoError(t, fixture.database.Create(&extraSource).Error)
|
||||
require.NoError(t, fixture.database.Create(&extraDocument).Error)
|
||||
second := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", body)
|
||||
require.Equal(t, http.StatusOK, second.Code, second.Body.String())
|
||||
requireConfirmCreatedCount(t, second, 2)
|
||||
@@ -280,7 +279,7 @@ func TestRepeatedConfirmReturnsPersistedResultAfterRelatedObjectsChange(t *testi
|
||||
var createdTask models.SaTask
|
||||
require.NoError(t, fixture.database.Where("source_inbox_item_id = ?", item.ID).First(&createdTask).Error)
|
||||
require.NoError(t, fixture.database.Delete(&createdTask).Error)
|
||||
require.NoError(t, fixture.database.Delete(&extraSource).Error)
|
||||
require.NoError(t, fixture.database.Delete(&extraDocument).Error)
|
||||
differentSelection := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", gin.H{
|
||||
"suggestionIds": []string{analysis.Suggestions[2].ID},
|
||||
})
|
||||
@@ -349,8 +348,7 @@ func requireFormalObjectCounts(t *testing.T, database *gorm.DB, tasks, notes, so
|
||||
want int64
|
||||
}{
|
||||
{model: &models.SaTask{}, want: tasks},
|
||||
{model: &models.SaNote{}, want: notes},
|
||||
{model: &models.SaSource{}, want: sources},
|
||||
{model: &models.SaDocument{}, want: notes + sources},
|
||||
} {
|
||||
var count int64
|
||||
require.NoError(t, database.Model(check.model).Count(&count).Error)
|
||||
|
||||
Reference in New Issue
Block a user