mirror of
https://github.com/documize/community.git
synced 2025-07-21 06:09:42 +02:00
Record document search history
This commit is contained in:
parent
4816cf73c0
commit
a6828e6b7f
8 changed files with 110 additions and 37 deletions
|
@ -52,9 +52,9 @@ Space view.
|
||||||
|
|
||||||
## Latest version
|
## 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
|
## OS support
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ Documize runs on the following:
|
||||||
Documize is built with the following technologies:
|
Documize is built with the following technologies:
|
||||||
|
|
||||||
- EmberJS (v2.18.0)
|
- EmberJS (v2.18.0)
|
||||||
- Go (v1.10)
|
- Go (v1.10.1)
|
||||||
|
|
||||||
...and supports the following databases:
|
...and supports the following databases:
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ Documize is built with the following technologies:
|
||||||
- Percona (v5.7.16-10+)
|
- Percona (v5.7.16-10+)
|
||||||
- MariaDB (10.3.0+)
|
- MariaDB (10.3.0+)
|
||||||
|
|
||||||
Coming soon, PostgreSQL and Microsoft SQL Server support.
|
Coming soon, PostgreSQL and Microsoft SQL Server database support.
|
||||||
|
|
||||||
## Authentication options
|
## Authentication options
|
||||||
|
|
||||||
|
|
28
core/database/scripts/autobuild/db_00020.sql
Normal file
28
core/database/scripts/autobuild/db_00020.sql
Normal file
|
@ -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
|
|
@ -34,8 +34,8 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
||||||
activity.UserID = ctx.UserID
|
activity.UserID = ctx.UserID
|
||||||
activity.Created = time.Now().UTC()
|
activity.Created = time.Now().UTC()
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
_, 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.Created)
|
activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "execute record user activity")
|
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.
|
// GetDocumentActivity returns the metadata for a specified document.
|
||||||
func (s Scope) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
|
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(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname,
|
||||||
IFNULL(p.title, '') as pagetitle
|
IFNULL(p.title, '') as pagetitle
|
||||||
FROM useractivity a
|
FROM useractivity a
|
||||||
|
|
|
@ -401,20 +401,44 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Put in slugs for easy UI display of search URL
|
// Put in slugs for easy UI display of search URL
|
||||||
for key, result := range results {
|
for key, result := range results {
|
||||||
result.DocumentSlug = stringutil.MakeSlug(result.Document)
|
results[key].DocumentSlug = stringutil.MakeSlug(result.Document)
|
||||||
result.SpaceSlug = stringutil.MakeSlug(result.Space)
|
results[key].SpaceSlug = stringutil.MakeSlug(result.Space)
|
||||||
results[key] = result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(results) == 0 {
|
// Record user search history
|
||||||
results = []search.QueryResult{}
|
go h.recordSearchActivity(ctx, results)
|
||||||
}
|
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSearch)
|
h.Store.Audit.Record(ctx, audit.EventTypeSearch)
|
||||||
|
|
||||||
response.WriteJSON(w, results)
|
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.
|
// FetchDocumentData returns all document data in single API call.
|
||||||
func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "document.FetchDocumentData"
|
method := "document.FetchDocumentData"
|
||||||
|
|
|
@ -204,6 +204,10 @@ func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (resu
|
||||||
results = append(results, r4...)
|
results = append(results, r4...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
results = []search.QueryResult{}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
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 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
|
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)`
|
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.template = 0
|
||||||
AND d.labelid IN
|
AND d.labelid IN
|
||||||
(
|
(
|
||||||
SELECT refid FROM label WHERE orgid=?
|
SELECT refid FROM label WHERE orgid=? AND refid IN
|
||||||
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 permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space'
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
|
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 ?`
|
AND s.content LIKE ?`
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ func main() {
|
||||||
// product details
|
// product details
|
||||||
rt.Product = env.ProdInfo{}
|
rt.Product = env.ProdInfo{}
|
||||||
rt.Product.Major = "1"
|
rt.Product.Major = "1"
|
||||||
rt.Product.Minor = "60"
|
rt.Product.Minor = "61"
|
||||||
rt.Product.Patch = "0"
|
rt.Product.Patch = "0"
|
||||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||||
rt.Product.Edition = "Community"
|
rt.Product.Edition = "Community"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "documize",
|
"name": "documize",
|
||||||
"version": "1.60.0",
|
"version": "1.61.0",
|
||||||
"description": "The Document IDE",
|
"description": "The Document IDE",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": "",
|
"repository": "",
|
||||||
|
|
|
@ -23,8 +23,11 @@ type UserActivity struct {
|
||||||
PageID string `json:"pageId"`
|
PageID string `json:"pageId"`
|
||||||
ActivityType Type `json:"activityType"`
|
ActivityType Type `json:"activityType"`
|
||||||
SourceType SourceType `json:"sourceType"`
|
SourceType SourceType `json:"sourceType"`
|
||||||
SourceName string `json:"sourceName"`
|
Metadata string `json:"metadata"`
|
||||||
Created time.Time `json:"created"`
|
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.
|
// DocumentActivity represents an activity taken against a document.
|
||||||
|
@ -57,50 +60,57 @@ const (
|
||||||
|
|
||||||
// SourceTypePage indicates activity against a document page.
|
// SourceTypePage indicates activity against a document page.
|
||||||
SourceTypePage SourceType = 3
|
SourceTypePage SourceType = 3
|
||||||
|
|
||||||
|
// SourceTypeSearch indicates activity on search page.
|
||||||
|
SourceTypeSearch SourceType = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TypeCreated records user document creation
|
// TypeCreated records user object creation (document or space).
|
||||||
TypeCreated Type = 1
|
TypeCreated Type = 1
|
||||||
|
|
||||||
// TypeRead states user has read document
|
// TypeRead states user has consumed object (document or space).
|
||||||
TypeRead Type = 2
|
TypeRead Type = 2
|
||||||
|
|
||||||
// TypeEdited states user has editing document
|
// TypeEdited states user has editing document.
|
||||||
TypeEdited Type = 3
|
TypeEdited Type = 3
|
||||||
|
|
||||||
// TypeDeleted records user deleting space/document
|
// TypeDeleted records user deleting space/document.
|
||||||
TypeDeleted Type = 4
|
TypeDeleted Type = 4
|
||||||
|
|
||||||
// TypeArchived records user archiving space/document
|
// TypeArchived records user archiving space/document.
|
||||||
TypeArchived Type = 5
|
TypeArchived Type = 5
|
||||||
|
|
||||||
// TypeApproved records user approval of document
|
// TypeApproved records user approval of document.
|
||||||
TypeApproved Type = 6
|
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
|
TypeReverted Type = 7
|
||||||
|
|
||||||
// TypePublishedTemplate records user creating new document template
|
// TypePublishedTemplate records user creating new document template.
|
||||||
TypePublishedTemplate Type = 8
|
TypePublishedTemplate Type = 8
|
||||||
|
|
||||||
// TypePublishedBlock records user creating reusable content block
|
// TypePublishedBlock records user creating reusable content block.
|
||||||
TypePublishedBlock Type = 9
|
TypePublishedBlock Type = 9
|
||||||
|
|
||||||
// TypeCommented records user providing document feedback
|
// TypeCommented records user providing document feedback.
|
||||||
TypeCommented Type = 10
|
TypeCommented Type = 10
|
||||||
|
|
||||||
// TypeRejected records user rejecting document
|
// TypeRejected records user rejecting document.
|
||||||
TypeRejected Type = 11
|
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
|
TypeSentSecureLink Type = 12
|
||||||
|
|
||||||
// TypeDraft records user marking space/document as draft
|
// TypeDraft records user marking space/document as draft.
|
||||||
TypeDraft Type = 13
|
TypeDraft Type = 13
|
||||||
|
|
||||||
// TypeVersioned records user creating new document version
|
// TypeVersioned records user creating new document version.
|
||||||
TypeVersioned Type = 14
|
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
|
// TypeName returns one-work descriptor for activity type
|
||||||
|
@ -130,6 +140,12 @@ func TypeName(t Type) string {
|
||||||
return "Reject"
|
return "Reject"
|
||||||
case TypeSentSecureLink:
|
case TypeSentSecureLink:
|
||||||
return "Share"
|
return "Share"
|
||||||
|
case TypeDraft:
|
||||||
|
return "Draft"
|
||||||
|
case TypeVersioned:
|
||||||
|
return "Version"
|
||||||
|
case TypeSearched:
|
||||||
|
return "Search"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue