git ssb

2+

mixmix / ticktack



Commit 0dff233ce29767d6fc042cf4bf59dcecdb8b347d

minimal stats page

mix irving committed on 4/17/2018, 11:44:14 AM
Parent: 1c2359030ac279f1fdfef805d7938f3003372c24

Files changed

app/page/statsShow.jschanged
app/page/statsShow.mcssadded
ssb-server-blog-stats.jschanged
app/page/statsShow.jsView
@@ -1,90 +1,179 @@
11 const nest = require('depnest')
2-const { h, Array: MutantArray, Dict, onceTrue, map } = require('mutant')
2+const { h, Value, Struct, Array: MutantArray, Dict, onceTrue, map, computed, dictToCollection } = require('mutant')
33 const pull = require('pull-stream')
44
55 exports.gives = nest('app.page.statsShow')
66
77 exports.needs = nest({
8- 'sbot.obs.connection': 'first'
8+ 'sbot.obs.connection': 'first',
9+ 'history.sync.push': 'first'
910 })
1011
1112 exports.create = (api) => {
1213 return nest('app.page.statsShow', statsShow)
1314
1415 function statsShow (location) {
15- var blogs = MutantArray([])
16- var comments = Dict()
17- var likes = Dict()
18- // comments(console.log)
16+ var store = Struct({
17+ blogs: MutantArray([]),
18+ comments: Dict(),
19+ likes: Dict()
20+ })
1921
20- onceTrue(api.sbot.obs.connection, server => {
21- // console.log(Object.keys(server.blogStats))
22+ var howFarBack = Value(0)
23+ // stats show a moving window of 30 days
24+ const now = Date.now()
25+ const thirtyDays = 30 * 24 * 60 * 60 * 1000
2226
23- fetchBlogs()
24-
25- function fetchBlogs () {
26- pull(
27- server.blogStats.readBlogs({ reverse: false }),
28- pull.drain(blog => {
29- blogs.push(blog)
30- fetchComments(blog)
31- fetchLikes(blog)
32- })
33- )
27+ // TODO
28+ var range = computed([howFarBack], howFarBack => {
29+ return {
30+ upper: now - howFarBack * thirtyDays,
31+ lower: now - (howFarBack + 1) * thirtyDays
3432 }
33+ })
3534
36- function fetchComments (blog) {
37- if (!comments.has(blog.key)) comments.put(blog.key, MutantArray())
35+ var rangeComments = computed([dictToCollection(store.comments), range], (comments, range) => {
36+ return comments
37+ .map(c => c.value)
38+ .reduce((n, sofar) => [...n, ...sofar], [])
39+ .filter(msg => {
40+ const ts = msg.value.timestamp
41+ return ts >= range.lower && ts <= range.upper
42+ })
43+ })
3844
39- pull(
40- server.blogStats.readComments({ blog }),
41- pull.drain(comment => {
42- comments.get(blog.key).push(comment)
43- })
44- )
45- }
45+ var rangeLikes = computed([dictToCollection(store.likes), range], (likes, range) => {
46+ return likes
47+ .map(c => c.value)
48+ .reduce((n, sofar) => [...n, ...sofar], [])
49+ .filter(msg => {
50+ const ts = msg.value.timestamp
51+ return ts >= range.lower && ts <= range.upper
52+ })
53+ })
4654
47- function fetchLikes (blog) {
48- if (!likes.has(blog.key)) likes.put(blog.key, MutantArray())
55+ onceTrue(api.sbot.obs.connection, server => {
56+ fetchBlogs({ server, store })
57+ // fetches blogs and all associated data
4958
50- pull(
51- server.blogStats.readLikes({ blog }),
52- pull.drain(comment => {
53- likes.get(blog.key).push(comment)
54- })
55- )
56- }
57-
5859 // ///// test code /////
59- var blogKey = '%3JeEg7voZF4aplk9xCEAfhFOx+zocbKhgstzvfD3G8w=.sha256'
60+ // var blogKey = '%3JeEg7voZF4aplk9xCEAfhFOx+zocbKhgstzvfD3G8w=.sha256'
6061 // console.log('fetching comments', blogKey) // has 2 comments, 1 like
6162
62- pull(
63- server.blogStats.read({
64- gt: ['L', blogKey, null],
65- lt: ['L', blogKey+'~', undefined],
66- // gt: ['L', blogKey, null],
67- // lte: ['L', blogKey+'~', undefined],
68- // limit: 100,
69- keys: true,
70- values: true,
71- seqs: false,
72- reverse: true
73- }),
74- // pull.filter(o => o.key[1] === blogKey),
75- pull.log(() => console.log('DONE'))
76- )
63+ // pull(
64+ // server.blogStats.read({
65+ // gt: ['L', blogKey, null],
66+ // lt: ['L', blogKey+'~', undefined],
67+ // // gt: ['L', blogKey, null],
68+ // // lte: ['L', blogKey+'~', undefined],
69+ // // limit: 100,
70+ // keys: true,
71+ // values: true,
72+ // seqs: false,
73+ // reverse: true
74+ // }),
75+ // // pull.filter(o => o.key[1] === blogKey),
76+ // pull.log(() => console.log('DONE'))
77+ // )
7778 /// ///// test code /////
7879 })
7980
8081 return h('Page -statsShow', [
81- h('pre', map(blogs, blog => {
82- return h('div', [
83- h('b', blog.value.content.title),
84- h('div', map(comments.get(blog.key), msg => 'C')),
85- h('div', map(likes.get(blog.key), msg => 'L'))
82+ h('div.content', [
83+ h('h1', 'Stats'),
84+ h('section.totals', [
85+ h('div.comments', [
86+ h('div.count', computed(rangeComments, msgs => msgs.length)),
87+ h('strong', 'Comments'),
88+ '(30 days)'
89+ ]),
90+ h('div.likes', [
91+ h('div.count', computed(rangeLikes, msgs => msgs.length)),
92+ h('strong', 'Likes'),
93+ '(30 days)'
94+ ]),
95+ h('div.shares', [
96+ ])
97+ ]),
98+ h('section.graph', [
99+ // TODO insert actual graph
100+ h('div', [
101+ h('div', [ 'Comments ', map(rangeComments, msg => [new Date(msg.value.timestamp).toDateString(), ' ']) ]),
102+ h('div', [ 'Likes ', map(rangeLikes, msg => [new Date(msg.value.timestamp).toDateString(), ' ']) ])
103+ ]),
104+ h('div', [
105+ h('a', { href: '#', 'ev-click': () => howFarBack.set(howFarBack() + 1) }, '< Prev 30 days'),
106+ ' | ',
107+ h('a', { href: '#', 'ev-click': () => howFarBack.set(howFarBack() - 1) }, 'Next 30 days >'),
108+ h('div', ['howFarBack:', howFarBack]) // TODO change - this is temporary
109+ ])
110+ ]),
111+ h('table.blogs', [
112+ h('thead', [
113+ h('tr', [
114+ h('th.details'),
115+ h('th.comment', 'Comments'),
116+ h('th.likes', 'Likes')
117+ ])
118+ ]),
119+ h('tbody', map(store.blogs, blog => h('tr.blog', [
120+ h('td.details', [
121+ h('div.title', {}, blog.value.content.title),
122+ h('a',
123+ {
124+ href: '#',
125+ 'ev-click': viewBlog(blog)
126+ },
127+ 'View blog'
128+ )
129+ ]),
130+ h('td.comments', computed(store.comments.get(blog.key), msgs => msgs ? msgs.length : 0)),
131+ h('td.likes', computed(store.likes.get(blog.key), msgs => msgs ? msgs.length : 0))
132+ ])))
86133 ])
87- }))
134+ ])
88135 ])
89136 }
137+
138+ function viewBlog (blog) {
139+ return () => api.history.sync.push(blog)
140+ }
90141 }
142+
143+function fetchBlogs ({ server, store }) {
144+ pull(
145+ server.blogStats.readBlogs({ reverse: false }),
146+ pull.drain(blog => {
147+ store.blogs.push(blog)
148+
149+ fetchComments({ server, store, blog })
150+ fetchLikes({ server, store, blog })
151+ })
152+ )
153+}
154+
155+function fetchComments ({ server, store, blog }) {
156+ if (!store.comments.has(blog.key)) store.comments.put(blog.key, MutantArray())
157+
158+ pull(
159+ server.blogStats.readComments(blog),
160+ pull.drain(comment => {
161+ store.comments.get(blog.key).push(comment)
162+ // TODO remove my comments from count?
163+ })
164+ )
165+}
166+
167+function fetchLikes ({ server, store, blog }) {
168+ if (!store.likes.has(blog.key)) store.likes.put(blog.key, MutantArray())
169+
170+ pull(
171+ server.blogStats.readLikes(blog),
172+ pull.drain(comment => {
173+ store.likes.get(blog.key).push(comment)
174+ // TODO this needs reducing... like + unlike are muddled in here
175+ // find any thing by same author
176+ // if exists - over-write or delete
177+ })
178+ )
179+}
app/page/statsShow.mcssView
@@ -1,0 +1,80 @@
1+Page -statsShow {
2+ div.content {
3+ flex-grow: 0
4+ $backgroundPrimaryText
5+ margin-top: 1rem
6+ width: 800px
7+
8+ h1 {
9+ font-size: .8rem
10+ letter-spacing: 4px
11+ }
12+
13+ section.totals {
14+ display: flex
15+ justify-content: space-between
16+
17+ div {
18+ div.count {
19+ font-size: 3rem
20+ font-weight: 600
21+ margin-right: .5rem
22+ }
23+ strong {
24+ margin-right: .5rem
25+ }
26+ }
27+ }
28+
29+ section.graph {
30+ margin: 2rem 0
31+ }
32+
33+ table.blogs {
34+ margin: 1rem 0
35+
36+ thead {
37+ tr {
38+ margin-bottom: 1rem
39+ color: hsl(0, 0%, 25%)
40+ td {
41+
42+ }
43+ }
44+ }
45+ tbody {
46+ tr.blog {
47+ margin-bottom: 1rem
48+ td.details {
49+ width: 100%
50+ padding: .8rem 2rem .8rem 0
51+ border-bottom: 1px solid rgba(0, 0, 0, .05)
52+
53+ div.title {
54+ font-size: 1.3rem
55+ font-weight: 600
56+ margin-bottom: .5rem
57+ }
58+
59+ a {
60+ color: hsl(0, 0%, 15%)
61+ font-size: .8rem
62+ text-decoration: none
63+
64+ :hover {
65+ text-decoration: underline
66+ }
67+ }
68+ }
69+ td.comments, td.likes {
70+ padding: 0 2.5rem
71+ font-size: 1.3rem
72+ font-weight: 600
73+ text-align: center
74+ }
75+ }
76+ }
77+ }
78+ }
79+
80+}
ssb-server-blog-stats.jsView
@@ -97,13 +97,12 @@
9797 pull.collect(cb)
9898 )
9999 }
100100
101- function readComments (options = {}) {
101+ function readComments (blog, options = {}) {
102102 var key
103- if (!options.blog) key = null
104- else if (isMsgRef(options.blog)) key = options.blog
105- else if (isMsgRef(options.blog.key) && isBlog(options.blog)) key = options.blog.key
103+ if (isMsgRef(blog)) key = blog
104+ else if (isMsgRef(blog.key) && isBlog(blog)) key = blog.key
106105
107106 const query = Object.assign({}, {
108107 gt: ['C', key, null],
109108 lt: ['C', key, undefined],
@@ -113,18 +112,15 @@
113112 keys: false,
114113 seqs: false
115114 }, options)
116115
117- delete query.blog
118-
119116 return view.read(query)
120117 }
121118
122- function readLikes (options = {}) {
119+ function readLikes (blog, options = {}) {
123120 var key
124- if (!options.blog) key = null
125- else if (isMsgRef(options.blog)) key = options.blog
126- else if (isMsgRef(options.blog.key) && isBlog(options.blog)) key = options.blog.key
121+ if (isMsgRef(blog)) key = blog
122+ else if (isMsgRef(blog.key) && isBlog(blog)) key = blog.key
127123
128124 const query = Object.assign({}, {
129125 // gt: ['L', key, null],
130126 // lt: ['L', key, undefined], // why doesn't this work?
@@ -135,10 +131,8 @@
135131 keys: false,
136132 seqs: false
137133 }, options)
138134
139- delete query.blog
140-
141135 return view.read(query)
142136 }
143137
144138 function myBlog (msg) {

Built with git-ssb-web