Commit 5f55ad219f72d734cb27efc4d543d507f1fd04ae
latest
Piet Geursen committed on 5/26/2018, 11:19:58 AMParent: 9792bede1cbfa9f873cc29f78a3b82929b371767
Files changed
package-lock.json | changed |
package.json | changed |
poll/async/get.js | changed |
poll/obs/get.js | changed |
results/sync/buildResults.js | changed |
test/poll/obs/get.test.js | changed |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 195008 bytes New file size: 195263 bytes |
package.json | ||
---|---|---|
@@ -28,9 +28,13 @@ | ||
28 | 28 | "libnested": "^1.2.1", |
29 | 29 | "lodash.clonedeep": "^4.5.0", |
30 | 30 | "mutant": "^3.22.1", |
31 | 31 | "pull-async": "^1.0.0", |
32 | + "pull-cat": "^1.1.11", | |
33 | + "pull-concat": "^1.1.1", | |
34 | + "pull-defer": "^0.2.2", | |
32 | 35 | "pull-notify": "^0.1.1", |
36 | + "pull-scan": "^1.0.0", | |
33 | 37 | "pull-stream": "^3.6.2", |
34 | 38 | "ssb-msg-content": "^1.0.1", |
35 | 39 | "ssb-msg-schemas": "^6.3.0", |
36 | 40 | "ssb-poll-schema": "^1.6.1", |
poll/async/get.js | ||
---|---|---|
@@ -117,9 +117,9 @@ | ||
117 | 117 | .filter(msg => isPosition(msg) && !isPosition[type](msg)) |
118 | 118 | .map(position => { |
119 | 119 | return { |
120 | 120 | type: ERROR_POSITION_TYPE, |
121 | - message: `Position responses need to be off the ${type} type for this poll`, | |
121 | + message: `Position responses need to be of the ${type} type for this poll`, | |
122 | 122 | position |
123 | 123 | } |
124 | 124 | }) |
125 | 125 |
poll/obs/get.js | ||
---|---|---|
@@ -1,59 +1,29 @@ | ||
1 | 1 | const pull = require('pull-stream') |
2 | -const PullNotify = require('pull-notify') | |
3 | 2 | const sort = require('ssb-sort') |
4 | 3 | const { Struct, Value, Array: MutantArray, computed, resolve } = require('mutant') |
5 | 4 | const getContent = require('ssb-msg-content') |
6 | 5 | const { isPoll, isPosition, isChooseOnePoll, isPollUpdate, isChooseOnePosition } = require('ssb-poll-schema') |
7 | 6 | isPoll.chooseOne = isChooseOnePoll |
8 | 7 | isPosition.chooseOne = isChooseOnePosition |
9 | 8 | const buildResults = require('../../results/sync/buildResults') |
10 | 9 | const { CHOOSE_ONE } = require('../../types') |
11 | - | |
12 | -// PollDoc is a mutant that initially only has the value sync false | |
13 | -// Which things need to be observabel? | |
14 | -// - Positions | |
15 | -// - Errors (these are the position errors that are not valid positions that should still be displayed somehow) | |
16 | -// - ClosesAt | |
17 | -// - Results (but that's computed from positions & closing time. | |
18 | -// | |
19 | -// Things that are not observable and can all live in one Value which will get set just once.: | |
20 | -// - title, author, body, channel, mentions, recps, the original poll message. | |
21 | -// { | |
22 | -// sync: boolean, (initially false until the value gets set) | |
23 | -// poll: MutantValue({ | |
24 | -// title: '', | |
25 | -// author: '', | |
26 | -// body: '', | |
27 | -// channel: '', | |
28 | -// mentions: '', | |
29 | -// recps: '', | |
30 | -// value: {...poll msg} | |
31 | -// }), | |
32 | -// closesAt: MutantValue('date string'), | |
33 | -// positions: MutantArray(), (sorted in causal order) | |
34 | -// results: MutantStruct(),A computed obs of positions | |
35 | -// errors | |
36 | -// } | |
37 | -// The shape of this object is different to the one returned by async.get. Ugh. | |
38 | - | |
39 | 10 | module.exports = function (server) { |
40 | 11 | return function get (key) { |
41 | 12 | const myKey = server.id |
42 | 13 | |
43 | 14 | const positions = MutantArray([]) |
44 | - const myPositions = MutantArray([]) | |
45 | 15 | const closingTimes = MutantArray([]) |
46 | 16 | const sortedClosingTimes = computed(closingTimes, sort) |
47 | 17 | const sortedPositions = computed(positions, sort) |
18 | + const myPosition = computed(sortedPositions, (positions) => { | |
19 | + const myPositions = positions.filter(position => position.value.author === myKey) | |
20 | + return myPositions.pop() | |
21 | + }) | |
48 | 22 | const closesAt = computed(sortedClosingTimes, (times) => { |
49 | 23 | const time = times.pop() |
50 | 24 | return time ? time.value.content.closesAt : '' |
51 | 25 | }) |
52 | - const myPosition = computed(myPositions, (positions) => { | |
53 | - const sortedPositions = sort(positions) | |
54 | - return sortedPositions.pop() | |
55 | - }) | |
56 | 26 | |
57 | 27 | const poll = Value({}) |
58 | 28 | const results = computed(sortedPositions, (positions) => { |
59 | 29 | const resultsErrors = buildResults({ poll: resolve(poll), positions }) |
@@ -61,9 +31,10 @@ | ||
61 | 31 | }) |
62 | 32 | |
63 | 33 | const errors = computed(sortedPositions, (positions) => { |
64 | 34 | const resultsErrors = buildResults({ poll: resolve(poll), positions }) |
65 | - return resultsErrors ? resultsErrors.errors : {} | |
35 | + if (resultsErrors && resultsErrors.errors) { } | |
36 | + return resultsErrors ? resultsErrors.errors : [] | |
66 | 37 | }) |
67 | 38 | |
68 | 39 | const pollDoc = Struct({ |
69 | 40 | sync: false, |
@@ -84,67 +55,60 @@ | ||
84 | 55 | // give subscribers a chance to start listening so they don't miss updates. |
85 | 56 | setImmediate(function () { |
86 | 57 | pollDoc.poll.set(decoratePoll(poll)) |
87 | 58 | |
88 | - const refs = PullNotify() | |
89 | - | |
90 | 59 | pull( |
91 | - createBacklinkStream(key), | |
92 | - pull.drain(refs) | |
93 | - ) | |
60 | + createBacklinkStream(key, {live: false, old: true}), | |
61 | + pull.collect((err, refs) => { | |
62 | + if (!err) { | |
63 | + const sorted = sort(refs) | |
64 | + const decoratedPosition = sorted | |
65 | + .filter(isPosition) | |
66 | + .map(DecoratePosition(poll)) | |
94 | 67 | |
95 | - // don't sync obs until we got sync from the stream to save some renders. | |
96 | - pull( | |
97 | - refs.listen(), | |
98 | - pull.filter(ref => ref.sync), | |
99 | - pull.drain(() => { | |
100 | - // allow the other streams to update their observables before sync goes true | |
101 | - setImmediate(() => pollDoc.sync.set(true)) | |
68 | + positions.set(decoratedPosition) | |
69 | + // push in the closing time from the poll object and then update if there are updates published. | |
70 | + closingTimes.push(poll) | |
71 | + | |
72 | + // don't sync obs until we got sync from the stream to save some renders. | |
73 | + setImmediate(() => pollDoc.sync.set(true)) | |
74 | + } | |
102 | 75 | }) |
103 | 76 | ) |
104 | 77 | |
105 | 78 | pull( |
106 | - refs.listen(), | |
107 | - pull.filter(isPosition[CHOOSE_ONE]), // TODO: this shouldn't be hard coded | |
108 | - pull.map(position => { | |
109 | - return decoratePosition({position, poll}) | |
110 | - }), | |
111 | - pull.drain((position) => { | |
112 | - positions.push(position) | |
113 | - }) | |
79 | + createBacklinkStream(key, {old: false, live: true}), | |
80 | + pull.filter(isPosition), | |
81 | + pull.map(DecoratePosition(poll)), | |
82 | + pull.drain(positions.push) | |
114 | 83 | ) |
115 | 84 | |
116 | - // push in the closing time from the poll object and then update if there are updates published. | |
117 | - closingTimes.push(poll) | |
118 | 85 | pull( |
119 | - refs.listen(), | |
86 | + createBacklinkStream(key, {old: false, live: true}), | |
120 | 87 | pull.filter(isPollUpdate), |
121 | - pull.drain(at => closingTimes.push(at)) | |
88 | + pull.drain(closingTimes.push) | |
122 | 89 | ) |
123 | - | |
124 | - pull( | |
125 | - refs.listen(), | |
126 | - pull.filter(isPosition[CHOOSE_ONE]), | |
127 | - pull.filter(position => position.value.author === myKey), | |
128 | - pull.drain(mine => myPositions.push(mine)) | |
129 | - ) | |
130 | 90 | }) |
131 | 91 | }) |
132 | 92 | return pollDoc |
133 | 93 | } |
134 | 94 | |
135 | - function createBacklinkStream (key) { | |
95 | + function createBacklinkStream (key, opts) { | |
96 | + opts = opts || { | |
97 | + live: true, | |
98 | + old: true | |
99 | + } | |
100 | + | |
136 | 101 | var filterQuery = { |
137 | 102 | $filter: { |
138 | 103 | dest: key |
139 | 104 | } |
140 | 105 | } |
141 | 106 | |
142 | - return server.backlinks.read({ | |
107 | + return server.backlinks.read(Object.assign({ | |
143 | 108 | query: [filterQuery], |
144 | - live: true, | |
145 | 109 | index: 'DTA' // use asserted timestamps |
146 | - }) | |
110 | + }, opts)) | |
147 | 111 | } |
148 | 112 | } |
149 | 113 | |
150 | 114 | function decoratePoll (rawPoll) { |
@@ -172,8 +136,12 @@ | ||
172 | 136 | |
173 | 137 | return poll |
174 | 138 | } |
175 | 139 | |
140 | +function DecoratePosition (poll) { | |
141 | + return (position) => decoratePosition({position, poll}) | |
142 | +} | |
143 | + | |
176 | 144 | function decoratePosition ({position: rawPosition, poll: rawPoll}) { |
177 | 145 | var position = getContent(rawPosition) |
178 | 146 | var poll = getContent(rawPoll) |
179 | 147 |
results/sync/buildResults.js | ||
---|---|---|
@@ -1,8 +1,9 @@ | ||
1 | 1 | const getContent = require('ssb-msg-content') |
2 | -const { isChooseOnePoll } = require('ssb-poll-schema') | |
2 | +const { isChooseOnePoll, isPosition } = require('ssb-poll-schema') | |
3 | 3 | const PositionChoiceError = require('../../errors/sync/positionChoiceError') |
4 | 4 | const PositionLateError = require('../../errors/sync/positionLateError') |
5 | +const PositionTypeError = require('../../errors/sync/positionTypeError') | |
5 | 6 | |
6 | 7 | // Expects `poll` and `position` objects passed in to be of shape: |
7 | 8 | // { |
8 | 9 | // key, |
@@ -36,8 +37,14 @@ | ||
36 | 37 | return positions.reduce(function (acc, position) { |
37 | 38 | const { author } = position.value |
38 | 39 | const { choice } = getContent(position).details |
39 | 40 | |
41 | + if (isInvalidType({position, poll})) { | |
42 | + console.log('got an aninvalid position type') | |
43 | + acc.errors.push(PositionTypeError({position})) | |
44 | + return acc | |
45 | + } | |
46 | + | |
40 | 47 | if (isInvalidChoice({position, poll})) { |
41 | 48 | acc.errors.push(PositionChoiceError({position})) |
42 | 49 | return acc |
43 | 50 | } |
@@ -63,12 +70,20 @@ | ||
63 | 70 | } |
64 | 71 | }) |
65 | 72 | } |
66 | 73 | |
74 | +function isInvalidType ({position, poll}) { | |
75 | + // TODO:this is super fragile. We should be using a parsed or decorated poll | |
76 | + const pollType = poll.value.content.details.type | |
77 | + return !isPosition[pollType](position) | |
78 | +} | |
79 | + | |
67 | 80 | function isInvalidChoice ({position, poll}) { |
68 | 81 | const { choice } = position.value.content.details |
82 | + // TODO:this is super fragile. We should be using a parsed or decorated poll | |
69 | 83 | return choice >= poll.value.content.details.choices.length |
70 | 84 | } |
71 | 85 | |
72 | 86 | function isPositionLate ({position, poll}) { |
87 | + // TODO:this is super fragile. We should be using a parsed or decorated poll | |
73 | 88 | return position.value.timestamp > poll.value.content.closesAt |
74 | 89 | } |
test/poll/obs/get.test.js | ||
---|---|---|
@@ -10,8 +10,9 @@ | ||
10 | 10 | const server = Server() |
11 | 11 | |
12 | 12 | const katie = server.createFeed() |
13 | 13 | const piet = server.createFeed() |
14 | +const me = server.whoami() | |
14 | 15 | |
15 | 16 | const pollContent = ChooseOnePoll({ |
16 | 17 | title: "what's our mascott team?", |
17 | 18 | choices: ['prairie dog', 'kea', 'hermit crab'], |
@@ -21,9 +22,9 @@ | ||
21 | 22 | const agesAway = nDaysTime(100) |
22 | 23 | const soSoon = nDaysTime(1) |
23 | 24 | |
24 | 25 | test('poll.obs.get', t => { |
25 | - t.plan(16) | |
26 | + t.plan(17) | |
26 | 27 | piet.publish(pollContent, (err, poll) => { |
27 | 28 | t.error(err) |
28 | 29 | const pollDoc = getPoll(server)(poll.key) |
29 | 30 | |
@@ -60,21 +61,30 @@ | ||
60 | 61 | }) |
61 | 62 | |
62 | 63 | pollDoc.results(function (results) { |
63 | 64 | if (results[1].voters[katie.id] && results[2].voters[piet.id]) { |
65 | + // we hit this test twice. Not super nice but not worth fixing now. | |
64 | 66 | t.ok(true, 'results eventually are correct') |
65 | 67 | } |
66 | 68 | }) |
67 | 69 | |
70 | + pollDoc.errors(function (errors) { | |
71 | + console.log('got an error', errors) | |
72 | + }) | |
73 | + | |
68 | 74 | pull( |
69 | 75 | pull.values([ |
70 | 76 | { author: katie, opts: { poll, choice: 1, reason: 'they are sick!' } }, |
71 | - { author: piet, opts: { poll, choice: 2, reason: 'scuttles 4life' } } | |
77 | + { author: piet, opts: { poll, choice: 2, reason: 'scuttles 4life' } }, | |
78 | + { author: piet, opts: { poll, choice: 2, reason: 'INVALID' } } | |
72 | 79 | ]), |
73 | 80 | pull.asyncMap((t, cb) => { |
74 | 81 | // NOTE: piet.get does not exist, so have to build using the master server |
75 | 82 | ChooseOnePosition(server)(t.opts, (err, position) => { |
76 | 83 | if (err) return cb(err) |
84 | + if (position.reason === 'INVALID') { | |
85 | + position.details.choice = 1000 | |
86 | + } | |
77 | 87 | t.position = position |
78 | 88 | cb(null, t) |
79 | 89 | }) |
80 | 90 | }), |
Built with git-ssb-web