1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-18 20:59:43 +02:00

bulk data load methods for space and document views

This commit is contained in:
Harvey Kandola 2017-10-08 20:53:25 -04:00
parent bc72db711f
commit 318abef710
9 changed files with 315 additions and 29 deletions

View file

@ -1,15 +1,19 @@
# Documize Community Edition
## The mission
To bring software development inspired features to the world of documenting -- refactoring, importing, testing, linting, metrics, PRs, versioning....
## What is it?
Documize is an intelligent document environment (IDE) for creating, securing and sharing documents -- everything yoyu need in one place.
Documize is an intelligent document environment (IDE) for creating, securing and sharing documents -- everything you need in one place.
## Why should I care?
Because maybe like us you are tired of:
Because maybe like us, you might be tired of:
* juggling WYSIWYG editors, wiki software and various document related solutions
* playing email tennis with document versions, contributions and feedback
* playing document related email tennis with contributions, versions and feedback
* sharing not-so-secure folders with external participants
Sound familiar? Read on.
@ -18,10 +22,10 @@ Sound familiar? Read on.
Anyone who wants a single place for any kind of document.
Anyone who wants to loop in external participants without leaking information.
Anyone who wants to loop in external participants complete security.
Anyone who wishes documentation and knowledge capture worked like agile software development.
## What's different about Documize?
Sane organization through personal, team and public spaces.
@ -83,10 +87,6 @@ Documize is compatible with Auth0 identity as a service.
Open Source Identity and Access Management
## The mission
To bring software development inspired features to the world of documenting -- refactoring, testing, linting, metrics, PRs, versioning....
## The legal bit at the end
<https://documize.com>

View file

@ -112,6 +112,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get space categories visible to user failed", err)
response.WriteServerError(w, method, err)
return
}
@ -460,3 +461,66 @@ func (h *Handler) GetSpaceCategoryMembers(w http.ResponseWriter, r *http.Request
response.WriteJSON(w, cat)
}
// FetchSpaceData returns:
// 1. categories that user can see for given space
// 2. summary data for each category
// 3. category viewing membership records
func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
method := "category.FetchSpaceData"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
if !ok {
response.WriteForbiddenError(w)
return
}
fetch := category.FetchSpaceModel{}
// get space categories visible to user
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get space categories visible to user failed", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Category{}
}
// summary of space category usage
summary, err := h.Store.Category.GetSpaceCategorySummary(ctx, spaceID)
if err != nil {
h.Runtime.Log.Error("get space category summary failed", err)
response.WriteServerError(w, method, err)
return
}
if len(summary) == 0 {
summary = []category.SummaryModel{}
}
// get category membership records
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get document category membership for space", err)
response.WriteServerError(w, method, err)
return
}
if len(member) == 0 {
member = []category.Member{}
}
fetch.Category = cat
fetch.Summary = summary
fetch.Membership = member
response.WriteJSON(w, fetch)
}

View file

@ -29,7 +29,9 @@ import (
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/link"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/search"
"github.com/documize/community/model/space"
)
// Handler contains the runtime information such as logging and database.
@ -371,3 +373,104 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, results)
}
// FetchDocumentData returns all document data in single API call.
func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
method := "document.FetchDocumentData"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
// document
document, err := h.Store.Document.Get(ctx, id)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, id)
return
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
response.WriteForbiddenError(w)
return
}
// permissions
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []pm.Permission{}
}
record := pm.DecodeUserPermissions(perms)
// links
l, err := h.Store.Link.GetDocumentOutboundLinks(ctx, id)
if len(l) == 0 {
l = []link.Link{}
}
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// spaces
sp, err := h.Store.Space.GetViewable(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(sp) == 0 {
sp = []space.Space{}
}
data := documentData{}
data.Document = document
data.Permissions = record
data.Links = l
data.Spaces = sp
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: document.LabelID,
SourceID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
if err != nil {
h.Runtime.Log.Error(method, err)
}
h.Store.Audit.Record(ctx, audit.EventTypeDocumentView)
ctx.Transaction.Commit()
response.WriteJSON(w, data)
}
// documentData represents all data associated for a single document.
// Used by FetchDocumentData() bulk data load call.
type documentData struct {
Document doc.Document `json:"document"`
Permissions pm.Record `json:"permissions"`
Spaces []space.Space `json:"folders"`
Links []link.Link `json:"link"`
}

View file

@ -24,22 +24,31 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
this.set('documentId', this.paramsFor('document').document_id);
return new Ember.RSVP.Promise((resolve) => {
this.get('documentService').getDocument(this.get('documentId')).then((document) => {
this.set('document', document);
this.get('folderService').getAll().then((folders) => {
this.set('folders', folders);
this.get('folderService').getFolder(this.get('folderId')).then((folder) => {
this.set('folder', folder);
this.get('folderService').setCurrentFolder(folder).then(() => {
this.set('permissions', this.get('folderService').get('permissions'));
resolve();
});
});
});
this.get('documentService').fetchDocumentData(this.get('documentId')).then((data) => {
this.set('document', data.document);
this.set('folders', data.folders);
this.set('folder', data.folder);
this.set('permissions', data.permissions);
this.set('links', data.links);
resolve();
});
// this.get('documentService').getDocument(this.get('documentId')).then((document) => {
// this.set('document', document);
// this.get('folderService').getAll().then((folders) => {
// this.set('folders', folders);
// this.get('folderService').getFolder(this.get('folderId')).then((folder) => {
// this.set('folder', folder);
// this.get('folderService').setCurrentFolder(folder).then(() => {
// this.set('permissions', this.get('folderService').get('permissions'));
// resolve();
// });
// });
// });
// });
});
},
@ -50,7 +59,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
document: this.get('document'),
page: this.get('pageId'),
permissions: this.get('permissions'),
links: this.get('linkService').getDocumentLinks(this.get('documentId')),
links: this.get('links'),
sections: this.get('sectionService').getAll()
});
},

View file

@ -15,6 +15,18 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout
export default Ember.Route.extend(AuthenticatedRouteMixin, {
categoryService: Ember.inject.service('category'),
beforeModel() {
return new Ember.RSVP.Promise((resolve) => {
this.get('categoryService').fetchSpaceData(this.modelFor('folder').folder.get('id')).then((data) => {
this.set('categories', data.category);
this.set('categorySummary', data.summary);
this.set('categoryMembers', data.membership);
resolve(data);
});
});
},
model() {
this.get('browser').setTitle(this.modelFor('folder').folder.get('name'));
@ -25,14 +37,25 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
documents: this.modelFor('folder').documents,
templates: this.modelFor('folder').templates,
showStartDocument: false,
categories: this.get('categoryService').getUserVisible(this.modelFor('folder').folder.get('id')),
categorySummary: this.get('categoryService').getSummary(this.modelFor('folder').folder.get('id')),
categoryMembers: this.get('categoryService').getSpaceCategoryMembership(this.modelFor('folder').folder.get('id')),
rootDocCount: 0
rootDocCount: 0,
categories: this.get('categories'),
categorySummary: this.get('categorySummary'),
categoryMembers: this.get('categoryMembers'),
// categories: this.get('categoryService').getUserVisible(this.modelFor('folder').folder.get('id')),
// categorySummary: this.get('categoryService').getSummary(this.modelFor('folder').folder.get('id')),
// categoryMembers: this.get('categoryService').getSpaceCategoryMembership(this.modelFor('folder').folder.get('id')),
});
},
afterModel(model, transition) { // eslint-disable-line no-unused-vars
// model.folder = this.modelFor('folder').folder;
// model.permissions = this.modelFor('folder').permissions;
// model.folders = this.modelFor('folder').folders;
// model.documents = this.modelFor('folder').documents;
// model.templates = this.modelFor('folder').templates;
// model.showStartDocument = false;
// model.rootDocCount = 0;
let docs = model.documents;
let categoryMembers = model.categoryMembers;
let rootDocCount = 0;

View file

@ -151,5 +151,34 @@ export default BaseService.extend({
}).then((response) => {
return response;
});
},
// fetchXXX represents UI specific bulk data loading designed to
// reduce network traffic and boost app performance.
// This method that returns:
// 1. getUserVisible()
// 2. getSummary()
// 3. getSpaceCategoryMembership()
fetchSpaceData(spaceId) {
return this.get('ajax').request(`fetch/category/space/${spaceId}`, {
method: 'GET'
}).then((response) => {
let data = {
category: [],
membership: [],
summary: []
};
let cats = response.category.map((obj) => {
let data = this.get('store').normalize('category', obj);
return this.get('store').push(data);
});
data.category = cats;
data.membership = response.membership;
data.summary = response.summary;
return data;
});
}
});

View file

@ -17,6 +17,7 @@ const {
export default Ember.Service.extend({
sessionService: service('session'),
folderService: service('folder'),
ajax: service(),
store: service(),
@ -308,6 +309,51 @@ export default Ember.Service.extend({
let data = this.get('store').normalize('page', response);
return this.get('store').push(data);
});
},
//**************************************************
// Fetch bulk data
//**************************************************
// fetchXXX represents UI specific bulk data loading designed to
// reduce network traffic and boost app performance.
// This method that returns:
// 1. getUserVisible()
// 2. getSummary()
// 3. getSpaceCategoryMembership()
fetchDocumentData(documentId) {
return this.get('ajax').request(`fetch/document/${documentId}`, {
method: 'GET'
}).then((response) => {
let data = {
document: {},
permissions: {},
folders: [],
folder: {},
links: [],
};
let doc = this.get('store').normalize('document', response.document);
doc = this.get('store').push(doc);
let perms = this.get('store').normalize('space-permission', response);
perms= this.get('store').push(perms);
this.get('folderService').set('permissions', perms);
let folders = response.folders.map((obj) => {
let data = this.get('store').normalize('folder', obj);
return this.get('store').push(data);
});
data.document = doc;
data.permissions = perms;
data.folders = folders;
data.folder = folders.findBy('id', doc.get('folderId'));
data.links = response.links;
return data;
});
}
});

View file

@ -36,3 +36,11 @@ type SummaryModel struct {
CategoryID string `json:"categoryId"`
Count int64 `json:"count"`
}
// FetchSpaceModel represents categories, summary and membership in a single payload.
// Designed to speed up front-end app.
type FetchSpaceModel struct {
Category []Category `json:"category"`
Summary []SummaryModel `json:"summary"`
Membership []Member `json:"membership"`
}

View file

@ -179,6 +179,10 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, pin.UpdatePinSequence)
Add(rt, RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, pin.DeleteUserPin)
// fetch methods exist to speed up UI rendering by returning data in bulk
Add(rt, RoutePrefixPrivate, "fetch/category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.FetchSpaceData)
Add(rt, RoutePrefixPrivate, "fetch/document/{documentID}", []string{"GET", "OPTIONS"}, nil, document.FetchDocumentData)
Add(rt, RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, meta.RobotsTxt)
Add(rt, RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, meta.Sitemap)