@financial-times/qanda-ui 0.0.1-beta.28 → 0.0.1-beta.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/services/comments-api.js +2 -2
- package/dist/cjs/services/listeners-middleware.js +54 -36
- package/dist/cjs/store/stream.js +18 -5
- package/dist/esm/services/comments-api.js +3 -3
- package/dist/esm/services/listeners-middleware.js +55 -37
- package/dist/esm/store/stream.js +17 -4
- package/dist/types/store/stream.d.ts +12 -2
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ exports.nextCommentsApi = (0, react_1.createApi)({
|
|
|
47
47
|
exports.eventSource.onopen = async () => {
|
|
48
48
|
console.log(`Connected to SSE: ${sseEndpoint}`);
|
|
49
49
|
const state = getState();
|
|
50
|
-
const lastEventId = state.stream.
|
|
50
|
+
const lastEventId = state.stream.mostRecentUpdateTime;
|
|
51
51
|
if (lastEventId !== null && lastEventId !== undefined) {
|
|
52
52
|
try {
|
|
53
53
|
await dispatch(exports.nextCommentsApi.endpoints.getCatchupUpdates.initiate({
|
|
@@ -74,7 +74,7 @@ exports.nextCommentsApi = (0, react_1.createApi)({
|
|
|
74
74
|
});
|
|
75
75
|
// Store the lastEventId for use in reconnection
|
|
76
76
|
if (event.lastEventId) {
|
|
77
|
-
dispatch((0, stream_1.
|
|
77
|
+
dispatch((0, stream_1.setMostRecentUpdateTime)(event.lastEventId));
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -15,40 +15,41 @@ const comments_api_1 = require("./comments-api");
|
|
|
15
15
|
const stream_1 = require("../store/stream");
|
|
16
16
|
exports.listenerMiddleware = (0, toolkit_1.createListenerMiddleware)();
|
|
17
17
|
/**
|
|
18
|
-
* Calculates the most recent update timestamp from a
|
|
18
|
+
* Calculates the most recent update timestamp and answer id from a Q&A question
|
|
19
19
|
*
|
|
20
|
-
* @param qandas -
|
|
21
|
-
* @returns
|
|
20
|
+
* @param qandas - A Comment object representing a question with nested answers
|
|
21
|
+
* @returns Object containing mostRecentUpdateTime (timestamp) and mostRecentUpdateId (answer id)
|
|
22
22
|
*
|
|
23
23
|
* @example
|
|
24
|
-
* const
|
|
25
|
-
* // Returns: 167253120
|
|
24
|
+
* const { mostRecentUpdateTime, mostRecentUpdateId } = getMostRecentUpdate(qandas);
|
|
25
|
+
* // Returns: { mostRecentUpdateTime: 167253120, mostRecentUpdateId: 'answer-123' }
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
function getMostRecentUpdate(qandaComment) {
|
|
28
|
+
let mostRecentUpdateTime = 0;
|
|
29
|
+
let mostRecentUpdateId = '';
|
|
30
|
+
if (qandaComment.children && Array.isArray(qandaComment.children)) {
|
|
31
|
+
for (const answer of qandaComment?.children) {
|
|
32
|
+
const publishedDateTimestamp = new Date(answer.publishedDate).getTime();
|
|
33
|
+
if (publishedDateTimestamp > mostRecentUpdateTime) {
|
|
34
|
+
mostRecentUpdateTime = publishedDateTimestamp;
|
|
35
|
+
mostRecentUpdateId = answer.id;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
|
-
|
|
35
|
-
const mostRecentUpdate = qandas[0].children.reduce((mostRecent, answer) => {
|
|
36
|
-
const publishedDateTimestamp = new Date(answer.publishedDate).getTime();
|
|
37
|
-
return publishedDateTimestamp > mostRecent
|
|
38
|
-
? publishedDateTimestamp
|
|
39
|
-
: mostRecent;
|
|
40
|
-
}, 0);
|
|
41
|
-
return mostRecentUpdate;
|
|
39
|
+
return { mostRecentUpdateTime, mostRecentUpdateId };
|
|
42
40
|
}
|
|
43
41
|
/**
|
|
44
|
-
* Listener: Updates most recent timestamp when live Q&A stream data is fetched
|
|
42
|
+
* Listener: Updates most recent timestamp and initiates catchup when live Q&A stream data is fetched
|
|
45
43
|
*
|
|
46
44
|
* Responds to successful getQandAStream queries with status='live' and extracts
|
|
47
45
|
* the most recent answer timestamp to store in Redux state. This timestamp is
|
|
48
|
-
* used for reconnection logic and catching up on missed updates.
|
|
46
|
+
* used for reconnection logic and catching up on missed updates. Also automatically
|
|
47
|
+
* initiates a catchup request to get any updates that occurred since the last timestamp.
|
|
49
48
|
*
|
|
50
49
|
* @listens nextCommentsApi.endpoints.getQandAStream.matchFulfilled
|
|
51
|
-
* @dispatches
|
|
50
|
+
* @dispatches setMostRecentUpdateTime - Updates stream.mostRecentUpdateTime in Redux store
|
|
51
|
+
* @dispatches setStreamStartMostRecentAnswerId - Updates stream.streamStartMostRecentAnswerId in Redux store
|
|
52
|
+
* @dispatches nextCommentsApi.endpoints.getCatchupUpdates.initiate - Triggers catchup request for missed updates
|
|
52
53
|
*/
|
|
53
54
|
exports.listenerMiddleware.startListening({
|
|
54
55
|
matcher: (0, toolkit_1.isAnyOf)(comments_api_1.nextCommentsApi.endpoints.getQandAStream.matchFulfilled),
|
|
@@ -57,18 +58,21 @@ exports.listenerMiddleware.startListening({
|
|
|
57
58
|
const { storyId, useStaging, commentsAPIUrl, status } = meta.arg.originalArgs;
|
|
58
59
|
// Check if this is a live stream QUERY, not response. If it is a live query we get questions and answers.
|
|
59
60
|
const isLiveStream = status === 'live';
|
|
60
|
-
if (isLiveStream &&
|
|
61
|
-
|
|
61
|
+
if (isLiveStream &&
|
|
62
|
+
Array.isArray(payload.qandas) &&
|
|
63
|
+
payload.qandas.length > 0) {
|
|
64
|
+
const { mostRecentUpdateTime, mostRecentUpdateId } = getMostRecentUpdate(payload.qandas[0]);
|
|
62
65
|
// Update the store with the most recent update as a string
|
|
63
|
-
if (
|
|
64
|
-
console.log(`Setting most recent update for story ${storyId}: ${
|
|
65
|
-
listenerApi.dispatch((0, stream_1.
|
|
66
|
+
if (mostRecentUpdateTime !== undefined) {
|
|
67
|
+
console.log(`Setting most recent update for story ${storyId}: ${mostRecentUpdateTime}`);
|
|
68
|
+
listenerApi.dispatch((0, stream_1.setMostRecentUpdateTime)(`${mostRecentUpdateTime}`));
|
|
69
|
+
listenerApi.dispatch((0, stream_1.setStreamLoadMostRecentAnswerId)(mostRecentUpdateId));
|
|
66
70
|
}
|
|
67
71
|
listenerApi.dispatch(comments_api_1.nextCommentsApi.endpoints.getCatchupUpdates.initiate({
|
|
68
72
|
storyId: storyId,
|
|
69
73
|
useStaging: useStaging,
|
|
70
74
|
commentsAPIUrl: commentsAPIUrl,
|
|
71
|
-
lastEventId: `${
|
|
75
|
+
lastEventId: `${mostRecentUpdateTime}`,
|
|
72
76
|
}));
|
|
73
77
|
}
|
|
74
78
|
},
|
|
@@ -76,11 +80,13 @@ exports.listenerMiddleware.startListening({
|
|
|
76
80
|
/**
|
|
77
81
|
* Listener: Updates updates cache and most recent update when catchup comments are received
|
|
78
82
|
*
|
|
79
|
-
* Responds to successful getCatchupUpdates queries and adds them to the updates list
|
|
83
|
+
* Responds to successful getCatchupUpdates queries and adds them to the updates list.
|
|
84
|
+
* Filters out comments that match the most recent update from the stream to avoid
|
|
85
|
+
* duplicates caused by latency between coral and valkey stream updates.
|
|
80
86
|
*
|
|
81
87
|
* @listens nextCommentsApi.endpoints.getCatchupUpdates.matchFulfilled
|
|
82
|
-
* @dispatches
|
|
83
|
-
* @dispatches
|
|
88
|
+
* @dispatches nextCommentsApi.util.updateQueryData - Updates the cache of updated comments in the store
|
|
89
|
+
* @dispatches setMostRecentUpdateTime - Updates stream.mostRecentUpdateTime with new lastEventId
|
|
84
90
|
*/
|
|
85
91
|
exports.listenerMiddleware.startListening({
|
|
86
92
|
matcher: comments_api_1.nextCommentsApi.endpoints.getCatchupUpdates.matchFulfilled,
|
|
@@ -88,16 +94,28 @@ exports.listenerMiddleware.startListening({
|
|
|
88
94
|
const { payload, meta } = action;
|
|
89
95
|
const { storyId, useStaging, commentsAPIUrl } = meta.arg.originalArgs;
|
|
90
96
|
const { updatedComments, lastEventId } = payload;
|
|
97
|
+
if (!updatedComments)
|
|
98
|
+
return;
|
|
91
99
|
console.log('Received catchup updates, new event id:', lastEventId);
|
|
92
100
|
const updateType = comments_api_1.updateTypes['QA_NEW_REPLY'];
|
|
93
|
-
|
|
101
|
+
const state = listenerApi.getState();
|
|
102
|
+
const { streamLoadMostRecentAnswerId, mostRecentUpdateTime: mostRecentInStreamTime, } = state.stream;
|
|
103
|
+
// filter out catchup comments that are the same as the most recent update in the stream list, which can happen on first load
|
|
104
|
+
// there is latency between the comment being saved in coral and then added to our valkey stream. Updates after this will all come from valkey.
|
|
105
|
+
const filteredComments = updatedComments.filter((comment) => {
|
|
106
|
+
const { mostRecentUpdateId, mostRecentUpdateTime } = getMostRecentUpdate(comment);
|
|
107
|
+
if (streamLoadMostRecentAnswerId === mostRecentUpdateId &&
|
|
108
|
+
mostRecentInStreamTime === `${mostRecentUpdateTime}`) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
if (Array.isArray(filteredComments) && filteredComments.length) {
|
|
94
114
|
listenerApi.dispatch(comments_api_1.nextCommentsApi.util.updateQueryData('getQandAUpdates', { storyId, useStaging, commentsAPIUrl }, (draft) => {
|
|
95
115
|
draft[updateType] = draft[updateType] || [];
|
|
96
|
-
draft[updateType].push(...
|
|
116
|
+
draft[updateType].push(...filteredComments);
|
|
97
117
|
}));
|
|
98
118
|
}
|
|
99
|
-
|
|
100
|
-
listenerApi.dispatch((0, stream_1.setMostRecentUpdate)(lastEventId));
|
|
101
|
-
}
|
|
119
|
+
listenerApi.dispatch((0, stream_1.setMostRecentUpdateTime)(lastEventId));
|
|
102
120
|
},
|
|
103
121
|
});
|
package/dist/cjs/store/stream.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Redux slice containing stream state and reducers.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* This slice provides actions to update:
|
|
7
|
+
* - latestAnsweredQuestionId: he ID for the question with the latest answer, used for scrolling to place
|
|
8
|
+
* - mostRecentUpdateTime: The timestamp or valkey ID of the most recent update
|
|
9
|
+
* - streamLoadMostRecentAnswerId: The ID of the most recent answer when you load the stream data
|
|
10
|
+
*/
|
|
2
11
|
var _a;
|
|
3
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.
|
|
13
|
+
exports.setStreamLoadMostRecentAnswerId = exports.setMostRecentUpdateTime = exports.setLatestAnsweredQuestionId = exports.stream = exports.initialState = void 0;
|
|
5
14
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
6
15
|
exports.initialState = {
|
|
7
16
|
latestAnsweredQuestionId: '',
|
|
8
|
-
|
|
17
|
+
mostRecentUpdateTime: null,
|
|
18
|
+
streamLoadMostRecentAnswerId: null,
|
|
9
19
|
};
|
|
10
20
|
exports.stream = (0, toolkit_1.createSlice)({
|
|
11
21
|
name: 'stream',
|
|
@@ -14,9 +24,12 @@ exports.stream = (0, toolkit_1.createSlice)({
|
|
|
14
24
|
setLatestAnsweredQuestionId: (state, action) => {
|
|
15
25
|
state.latestAnsweredQuestionId = action.payload;
|
|
16
26
|
},
|
|
17
|
-
|
|
18
|
-
state.
|
|
27
|
+
setMostRecentUpdateTime: (state, action) => {
|
|
28
|
+
state.mostRecentUpdateTime = action.payload;
|
|
29
|
+
},
|
|
30
|
+
setStreamLoadMostRecentAnswerId: (state, action) => {
|
|
31
|
+
state.streamLoadMostRecentAnswerId = action.payload;
|
|
19
32
|
},
|
|
20
33
|
},
|
|
21
34
|
});
|
|
22
|
-
_a = exports.stream.actions, exports.setLatestAnsweredQuestionId = _a.setLatestAnsweredQuestionId, exports.
|
|
35
|
+
_a = exports.stream.actions, exports.setLatestAnsweredQuestionId = _a.setLatestAnsweredQuestionId, exports.setMostRecentUpdateTime = _a.setMostRecentUpdateTime, exports.setStreamLoadMostRecentAnswerId = _a.setStreamLoadMostRecentAnswerId;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
2
|
-
import {
|
|
2
|
+
import { setMostRecentUpdateTime } from '../store/stream';
|
|
3
3
|
import { qandaReliability } from './reliability';
|
|
4
4
|
// NB: this could do with a refactor to split things out into smaller files: see https://redux-toolkit.js.org/rtk-query/usage/code-splitting
|
|
5
5
|
// NB: adding retry limit would be a good idea https://financialtimes.atlassian.net/browse/CI-2715
|
|
@@ -44,7 +44,7 @@ export const nextCommentsApi = createApi({
|
|
|
44
44
|
eventSource.onopen = async () => {
|
|
45
45
|
console.log(`Connected to SSE: ${sseEndpoint}`);
|
|
46
46
|
const state = getState();
|
|
47
|
-
const lastEventId = state.stream.
|
|
47
|
+
const lastEventId = state.stream.mostRecentUpdateTime;
|
|
48
48
|
if (lastEventId !== null && lastEventId !== undefined) {
|
|
49
49
|
try {
|
|
50
50
|
await dispatch(nextCommentsApi.endpoints.getCatchupUpdates.initiate({
|
|
@@ -71,7 +71,7 @@ export const nextCommentsApi = createApi({
|
|
|
71
71
|
});
|
|
72
72
|
// Store the lastEventId for use in reconnection
|
|
73
73
|
if (event.lastEventId) {
|
|
74
|
-
dispatch(
|
|
74
|
+
dispatch(setMostRecentUpdateTime(event.lastEventId));
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -9,43 +9,44 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit';
|
|
11
11
|
import { nextCommentsApi, updateTypes } from './comments-api';
|
|
12
|
-
import {
|
|
12
|
+
import { setMostRecentUpdateTime, setStreamLoadMostRecentAnswerId, } from '../store/stream';
|
|
13
13
|
export const listenerMiddleware = createListenerMiddleware();
|
|
14
14
|
/**
|
|
15
|
-
* Calculates the most recent update timestamp from a
|
|
15
|
+
* Calculates the most recent update timestamp and answer id from a Q&A question
|
|
16
16
|
*
|
|
17
|
-
* @param qandas -
|
|
18
|
-
* @returns
|
|
17
|
+
* @param qandas - A Comment object representing a question with nested answers
|
|
18
|
+
* @returns Object containing mostRecentUpdateTime (timestamp) and mostRecentUpdateId (answer id)
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
|
-
* const
|
|
22
|
-
* // Returns: 167253120
|
|
21
|
+
* const { mostRecentUpdateTime, mostRecentUpdateId } = getMostRecentUpdate(qandas);
|
|
22
|
+
* // Returns: { mostRecentUpdateTime: 167253120, mostRecentUpdateId: 'answer-123' }
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
function getMostRecentUpdate(qandaComment) {
|
|
25
|
+
let mostRecentUpdateTime = 0;
|
|
26
|
+
let mostRecentUpdateId = '';
|
|
27
|
+
if (qandaComment.children && Array.isArray(qandaComment.children)) {
|
|
28
|
+
for (const answer of qandaComment?.children) {
|
|
29
|
+
const publishedDateTimestamp = new Date(answer.publishedDate).getTime();
|
|
30
|
+
if (publishedDateTimestamp > mostRecentUpdateTime) {
|
|
31
|
+
mostRecentUpdateTime = publishedDateTimestamp;
|
|
32
|
+
mostRecentUpdateId = answer.id;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
|
-
|
|
32
|
-
const mostRecentUpdate = qandas[0].children.reduce((mostRecent, answer) => {
|
|
33
|
-
const publishedDateTimestamp = new Date(answer.publishedDate).getTime();
|
|
34
|
-
return publishedDateTimestamp > mostRecent
|
|
35
|
-
? publishedDateTimestamp
|
|
36
|
-
: mostRecent;
|
|
37
|
-
}, 0);
|
|
38
|
-
return mostRecentUpdate;
|
|
36
|
+
return { mostRecentUpdateTime, mostRecentUpdateId };
|
|
39
37
|
}
|
|
40
38
|
/**
|
|
41
|
-
* Listener: Updates most recent timestamp when live Q&A stream data is fetched
|
|
39
|
+
* Listener: Updates most recent timestamp and initiates catchup when live Q&A stream data is fetched
|
|
42
40
|
*
|
|
43
41
|
* Responds to successful getQandAStream queries with status='live' and extracts
|
|
44
42
|
* the most recent answer timestamp to store in Redux state. This timestamp is
|
|
45
|
-
* used for reconnection logic and catching up on missed updates.
|
|
43
|
+
* used for reconnection logic and catching up on missed updates. Also automatically
|
|
44
|
+
* initiates a catchup request to get any updates that occurred since the last timestamp.
|
|
46
45
|
*
|
|
47
46
|
* @listens nextCommentsApi.endpoints.getQandAStream.matchFulfilled
|
|
48
|
-
* @dispatches
|
|
47
|
+
* @dispatches setMostRecentUpdateTime - Updates stream.mostRecentUpdateTime in Redux store
|
|
48
|
+
* @dispatches setStreamStartMostRecentAnswerId - Updates stream.streamStartMostRecentAnswerId in Redux store
|
|
49
|
+
* @dispatches nextCommentsApi.endpoints.getCatchupUpdates.initiate - Triggers catchup request for missed updates
|
|
49
50
|
*/
|
|
50
51
|
listenerMiddleware.startListening({
|
|
51
52
|
matcher: isAnyOf(nextCommentsApi.endpoints.getQandAStream.matchFulfilled),
|
|
@@ -54,18 +55,21 @@ listenerMiddleware.startListening({
|
|
|
54
55
|
const { storyId, useStaging, commentsAPIUrl, status } = meta.arg.originalArgs;
|
|
55
56
|
// Check if this is a live stream QUERY, not response. If it is a live query we get questions and answers.
|
|
56
57
|
const isLiveStream = status === 'live';
|
|
57
|
-
if (isLiveStream &&
|
|
58
|
-
|
|
58
|
+
if (isLiveStream &&
|
|
59
|
+
Array.isArray(payload.qandas) &&
|
|
60
|
+
payload.qandas.length > 0) {
|
|
61
|
+
const { mostRecentUpdateTime, mostRecentUpdateId } = getMostRecentUpdate(payload.qandas[0]);
|
|
59
62
|
// Update the store with the most recent update as a string
|
|
60
|
-
if (
|
|
61
|
-
console.log(`Setting most recent update for story ${storyId}: ${
|
|
62
|
-
listenerApi.dispatch(
|
|
63
|
+
if (mostRecentUpdateTime !== undefined) {
|
|
64
|
+
console.log(`Setting most recent update for story ${storyId}: ${mostRecentUpdateTime}`);
|
|
65
|
+
listenerApi.dispatch(setMostRecentUpdateTime(`${mostRecentUpdateTime}`));
|
|
66
|
+
listenerApi.dispatch(setStreamLoadMostRecentAnswerId(mostRecentUpdateId));
|
|
63
67
|
}
|
|
64
68
|
listenerApi.dispatch(nextCommentsApi.endpoints.getCatchupUpdates.initiate({
|
|
65
69
|
storyId: storyId,
|
|
66
70
|
useStaging: useStaging,
|
|
67
71
|
commentsAPIUrl: commentsAPIUrl,
|
|
68
|
-
lastEventId: `${
|
|
72
|
+
lastEventId: `${mostRecentUpdateTime}`,
|
|
69
73
|
}));
|
|
70
74
|
}
|
|
71
75
|
},
|
|
@@ -73,11 +77,13 @@ listenerMiddleware.startListening({
|
|
|
73
77
|
/**
|
|
74
78
|
* Listener: Updates updates cache and most recent update when catchup comments are received
|
|
75
79
|
*
|
|
76
|
-
* Responds to successful getCatchupUpdates queries and adds them to the updates list
|
|
80
|
+
* Responds to successful getCatchupUpdates queries and adds them to the updates list.
|
|
81
|
+
* Filters out comments that match the most recent update from the stream to avoid
|
|
82
|
+
* duplicates caused by latency between coral and valkey stream updates.
|
|
77
83
|
*
|
|
78
84
|
* @listens nextCommentsApi.endpoints.getCatchupUpdates.matchFulfilled
|
|
79
|
-
* @dispatches
|
|
80
|
-
* @dispatches
|
|
85
|
+
* @dispatches nextCommentsApi.util.updateQueryData - Updates the cache of updated comments in the store
|
|
86
|
+
* @dispatches setMostRecentUpdateTime - Updates stream.mostRecentUpdateTime with new lastEventId
|
|
81
87
|
*/
|
|
82
88
|
listenerMiddleware.startListening({
|
|
83
89
|
matcher: nextCommentsApi.endpoints.getCatchupUpdates.matchFulfilled,
|
|
@@ -85,16 +91,28 @@ listenerMiddleware.startListening({
|
|
|
85
91
|
const { payload, meta } = action;
|
|
86
92
|
const { storyId, useStaging, commentsAPIUrl } = meta.arg.originalArgs;
|
|
87
93
|
const { updatedComments, lastEventId } = payload;
|
|
94
|
+
if (!updatedComments)
|
|
95
|
+
return;
|
|
88
96
|
console.log('Received catchup updates, new event id:', lastEventId);
|
|
89
97
|
const updateType = updateTypes['QA_NEW_REPLY'];
|
|
90
|
-
|
|
98
|
+
const state = listenerApi.getState();
|
|
99
|
+
const { streamLoadMostRecentAnswerId, mostRecentUpdateTime: mostRecentInStreamTime, } = state.stream;
|
|
100
|
+
// filter out catchup comments that are the same as the most recent update in the stream list, which can happen on first load
|
|
101
|
+
// there is latency between the comment being saved in coral and then added to our valkey stream. Updates after this will all come from valkey.
|
|
102
|
+
const filteredComments = updatedComments.filter((comment) => {
|
|
103
|
+
const { mostRecentUpdateId, mostRecentUpdateTime } = getMostRecentUpdate(comment);
|
|
104
|
+
if (streamLoadMostRecentAnswerId === mostRecentUpdateId &&
|
|
105
|
+
mostRecentInStreamTime === `${mostRecentUpdateTime}`) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
});
|
|
110
|
+
if (Array.isArray(filteredComments) && filteredComments.length) {
|
|
91
111
|
listenerApi.dispatch(nextCommentsApi.util.updateQueryData('getQandAUpdates', { storyId, useStaging, commentsAPIUrl }, (draft) => {
|
|
92
112
|
draft[updateType] = draft[updateType] || [];
|
|
93
|
-
draft[updateType].push(...
|
|
113
|
+
draft[updateType].push(...filteredComments);
|
|
94
114
|
}));
|
|
95
115
|
}
|
|
96
|
-
|
|
97
|
-
listenerApi.dispatch(setMostRecentUpdate(lastEventId));
|
|
98
|
-
}
|
|
116
|
+
listenerApi.dispatch(setMostRecentUpdateTime(lastEventId));
|
|
99
117
|
},
|
|
100
118
|
});
|
package/dist/esm/store/stream.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redux slice containing stream state and reducers.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This slice provides actions to update:
|
|
6
|
+
* - latestAnsweredQuestionId: he ID for the question with the latest answer, used for scrolling to place
|
|
7
|
+
* - mostRecentUpdateTime: The timestamp or valkey ID of the most recent update
|
|
8
|
+
* - streamLoadMostRecentAnswerId: The ID of the most recent answer when you load the stream data
|
|
9
|
+
*/
|
|
1
10
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
11
|
export const initialState = {
|
|
3
12
|
latestAnsweredQuestionId: '',
|
|
4
|
-
|
|
13
|
+
mostRecentUpdateTime: null,
|
|
14
|
+
streamLoadMostRecentAnswerId: null,
|
|
5
15
|
};
|
|
6
16
|
export const stream = createSlice({
|
|
7
17
|
name: 'stream',
|
|
@@ -10,9 +20,12 @@ export const stream = createSlice({
|
|
|
10
20
|
setLatestAnsweredQuestionId: (state, action) => {
|
|
11
21
|
state.latestAnsweredQuestionId = action.payload;
|
|
12
22
|
},
|
|
13
|
-
|
|
14
|
-
state.
|
|
23
|
+
setMostRecentUpdateTime: (state, action) => {
|
|
24
|
+
state.mostRecentUpdateTime = action.payload;
|
|
25
|
+
},
|
|
26
|
+
setStreamLoadMostRecentAnswerId: (state, action) => {
|
|
27
|
+
state.streamLoadMostRecentAnswerId = action.payload;
|
|
15
28
|
},
|
|
16
29
|
},
|
|
17
30
|
});
|
|
18
|
-
export const { setLatestAnsweredQuestionId,
|
|
31
|
+
export const { setLatestAnsweredQuestionId, setMostRecentUpdateTime, setStreamLoadMostRecentAnswerId, } = stream.actions;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redux slice containing stream state and reducers.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This slice provides actions to update:
|
|
6
|
+
* - latestAnsweredQuestionId: he ID for the question with the latest answer, used for scrolling to place
|
|
7
|
+
* - mostRecentUpdateTime: The timestamp or valkey ID of the most recent update
|
|
8
|
+
* - streamLoadMostRecentAnswerId: The ID of the most recent answer when you load the stream data
|
|
9
|
+
*/
|
|
1
10
|
import { Slice } from '@reduxjs/toolkit';
|
|
2
11
|
export type StreamState = {
|
|
3
12
|
latestAnsweredQuestionId: string;
|
|
4
|
-
|
|
13
|
+
mostRecentUpdateTime: string | null;
|
|
14
|
+
streamLoadMostRecentAnswerId: string | null;
|
|
5
15
|
};
|
|
6
16
|
export declare const initialState: StreamState;
|
|
7
17
|
export declare const stream: Slice;
|
|
8
|
-
export declare const setLatestAnsweredQuestionId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`>,
|
|
18
|
+
export declare const setLatestAnsweredQuestionId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`>, setMostRecentUpdateTime: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`>, setStreamLoadMostRecentAnswerId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`>;
|