diff --git a/README.md b/README.md index fe7e3140..8eed0c7e 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ Space view. ## Latest version -[Community edition: v1.60.0](https://github.com/documize/community/releases) +[Community edition: v1.61.0](https://github.com/documize/community/releases) -[Enterprise edition: v1.62.0](https://documize.com/downloads) +[Enterprise edition: v1.63.0](https://documize.com/downloads) ## OS support @@ -69,7 +69,7 @@ Documize runs on the following: Documize is built with the following technologies: - EmberJS (v2.18.0) -- Go (v1.10) +- Go (v1.10.1) ...and supports the following databases: @@ -77,7 +77,7 @@ Documize is built with the following technologies: - Percona (v5.7.16-10+) - MariaDB (10.3.0+) -Coming soon, PostgreSQL and Microsoft SQL Server support. +Coming soon, PostgreSQL and Microsoft SQL Server database support. ## Authentication options diff --git a/core/database/scripts/autobuild/db_00020.sql b/core/database/scripts/autobuild/db_00020.sql new file mode 100644 index 00000000..54489713 --- /dev/null +++ b/core/database/scripts/autobuild/db_00020.sql @@ -0,0 +1,28 @@ +/* enterprise edition */ + +-- content analytics +ALTER TABLE useractivity ADD COLUMN `metadata` VARCHAR(1000) NOT NULL DEFAULT '' AFTER `activitytype`; + +-- content likes/feedback +-- DROP TABLE IF EXISTS `vote`; + +-- CREATE TABLE IF NOT EXISTS `vote` ( +-- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, +-- `refid` CHAR(16) NOT NULL COLLATE utf8_bin, +-- `orgid` CHAR(16) NOT NULL COLLATE utf8_bin, +-- `documentid` CHAR(16) NOT NULL COLLATE utf8_bin, +-- `userid` CHAR(16) NOT NULL DEFAULT '' COLLATE utf8_bin, +-- `vote` INT NOT NULL DEFAULT 0, +-- `comment` VARCHAR(300) NOT NULL DEFAULT '', +-- `created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +-- `revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +-- UNIQUE INDEX `idx_vote_id` (`id` ASC), +-- INDEX `idx_vote_refid` (`refid` ASC), +-- INDEX `idx_vote_documentid` (`documentid` ASC), +-- INDEX `idx_vote_orgid` (`orgid` ASC)) +-- DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci +-- ENGINE = MyISAM; + +-- CREATE INDEX idx_vote_1 ON vaote(orgid,documentid); + +-- deprecations diff --git a/domain/activity/mysql/store.go b/domain/activity/mysql/store.go index 1ab17ec0..0c737521 100644 --- a/domain/activity/mysql/store.go +++ b/domain/activity/mysql/store.go @@ -34,8 +34,8 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U activity.UserID = ctx.UserID activity.Created = time.Now().UTC() - _, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Created) + _, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, metadata, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created) if err != nil { err = errors.Wrap(err, "execute record user activity") @@ -46,7 +46,7 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U // GetDocumentActivity returns the metadata for a specified document. func (s Scope) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) { - qry := `SELECT a.id, DATE(a.created) as created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.documentid, a.pageid, a.activitytype, + qry := `SELECT a.id, DATE(a.created) as created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.documentid, a.pageid, a.activitytype, a.metadata, IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname, IFNULL(p.title, '') as pagetitle FROM useractivity a diff --git a/domain/document/endpoint.go b/domain/document/endpoint.go index c8390e3b..d35164c3 100644 --- a/domain/document/endpoint.go +++ b/domain/document/endpoint.go @@ -401,20 +401,44 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) { // Put in slugs for easy UI display of search URL for key, result := range results { - result.DocumentSlug = stringutil.MakeSlug(result.Document) - result.SpaceSlug = stringutil.MakeSlug(result.Space) - results[key] = result + results[key].DocumentSlug = stringutil.MakeSlug(result.Document) + results[key].SpaceSlug = stringutil.MakeSlug(result.Space) } - if len(results) == 0 { - results = []search.QueryResult{} - } + // Record user search history + go h.recordSearchActivity(ctx, results) h.Store.Audit.Record(ctx, audit.EventTypeSearch) response.WriteJSON(w, results) } +func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.QueryResult) { + method := "recordSearchActivity" + var err error + + ctx.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + h.Runtime.Log.Error(method, err) + return + } + + for i := range q { + err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ + LabelID: q[i].SpaceID, + DocumentID: q[i].DocumentID, + SourceType: activity.SourceTypeSearch, + ActivityType: activity.TypeSearched}) + + if err != nil { + ctx.Transaction.Rollback() + h.Runtime.Log.Error(method, err) + } + } + + ctx.Transaction.Commit() +} + // FetchDocumentData returns all document data in single API call. func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) { method := "document.FetchDocumentData" diff --git a/domain/search/mysql/store.go b/domain/search/mysql/store.go index 00303156..79d41567 100644 --- a/domain/search/mysql/store.go +++ b/domain/search/mysql/store.go @@ -204,6 +204,10 @@ func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (resu results = append(results, r4...) } + if len(results) == 0 { + results = []search.QueryResult{} + } + return } @@ -227,9 +231,10 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin ( SELECT refid FROM label WHERE orgid=? AND refid IN ( - SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' + SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' UNION ALL - SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND r.userid=? + SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' + AND p.location='space' AND (r.userid=? OR r.userid='0') ) ) AND MATCH(s.content) AGAINST(? IN BOOLEAN MODE)` @@ -280,13 +285,13 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) ( -- AND d.template = 0 AND d.labelid IN ( - SELECT refid FROM label WHERE orgid=? - AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN ( - SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' + SELECT refid FROM label WHERE orgid=? AND refid IN + ( + SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' UNION ALL SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' - AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0') - )) + AND p.location='space' AND (r.userid=? OR r.userid='0') + ) ) AND s.content LIKE ?` diff --git a/edition/community.go b/edition/community.go index cb6fdae4..19363f9c 100644 --- a/edition/community.go +++ b/edition/community.go @@ -41,7 +41,7 @@ func main() { // product details rt.Product = env.ProdInfo{} rt.Product.Major = "1" - rt.Product.Minor = "60" + rt.Product.Minor = "61" rt.Product.Patch = "0" rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch) rt.Product.Edition = "Community" diff --git a/gui/package.json b/gui/package.json index f99d265e..d613376e 100644 --- a/gui/package.json +++ b/gui/package.json @@ -1,6 +1,6 @@ { "name": "documize", - "version": "1.60.0", + "version": "1.61.0", "description": "The Document IDE", "private": true, "repository": "", diff --git a/model/activity/activity.go b/model/activity/activity.go index e5307454..3077d961 100644 --- a/model/activity/activity.go +++ b/model/activity/activity.go @@ -23,8 +23,11 @@ type UserActivity struct { PageID string `json:"pageId"` ActivityType Type `json:"activityType"` SourceType SourceType `json:"sourceType"` - SourceName string `json:"sourceName"` + Metadata string `json:"metadata"` Created time.Time `json:"created"` + + // Read-only outbound fields (e.g. for UI display) + SourceName string `json:"sourceName"` } // DocumentActivity represents an activity taken against a document. @@ -57,50 +60,57 @@ const ( // SourceTypePage indicates activity against a document page. SourceTypePage SourceType = 3 + + // SourceTypeSearch indicates activity on search page. + SourceTypeSearch SourceType = 4 ) const ( - // TypeCreated records user document creation + // TypeCreated records user object creation (document or space). TypeCreated Type = 1 - // TypeRead states user has read document + // TypeRead states user has consumed object (document or space). TypeRead Type = 2 - // TypeEdited states user has editing document + // TypeEdited states user has editing document. TypeEdited Type = 3 - // TypeDeleted records user deleting space/document + // TypeDeleted records user deleting space/document. TypeDeleted Type = 4 - // TypeArchived records user archiving space/document + // TypeArchived records user archiving space/document. TypeArchived Type = 5 - // TypeApproved records user approval of document + // TypeApproved records user approval of document. TypeApproved Type = 6 - // TypeReverted records user content roll-back to previous version + // TypeReverted records user content roll-back to previous document version. TypeReverted Type = 7 - // TypePublishedTemplate records user creating new document template + // TypePublishedTemplate records user creating new document template. TypePublishedTemplate Type = 8 - // TypePublishedBlock records user creating reusable content block + // TypePublishedBlock records user creating reusable content block. TypePublishedBlock Type = 9 - // TypeCommented records user providing document feedback + // TypeCommented records user providing document feedback. TypeCommented Type = 10 - // TypeRejected records user rejecting document + // TypeRejected records user rejecting document. TypeRejected Type = 11 - // TypeSentSecureLink records user sending secure document link to email address(es) + // TypeSentSecureLink records user sending secure document link via email. TypeSentSecureLink Type = 12 - // TypeDraft records user marking space/document as draft + // TypeDraft records user marking space/document as draft. TypeDraft Type = 13 - // TypeVersioned records user creating new document version + // TypeVersioned records user creating new document version. TypeVersioned Type = 14 + + // TypeSearched records user performing document keyword search. + // Metadata field should contain search terms. + TypeSearched Type = 15 ) // TypeName returns one-work descriptor for activity type @@ -130,6 +140,12 @@ func TypeName(t Type) string { return "Reject" case TypeSentSecureLink: return "Share" + case TypeDraft: + return "Draft" + case TypeVersioned: + return "Version" + case TypeSearched: + return "Search" } return ""