-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FIX] #345 - where smarti conversation cache got out of synch #346
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is comparatively huge - for actually two places in which functionality has changed. At least this is how far I understood it. This raised a lot of additional questions which I would like to get clarified prior to merging this. Anyway: Thanks for analyzing it 👍
* @param smartiConversationId | ||
* @param token | ||
*/ | ||
static analysisCompleted(roomId, smartiConversationId, token) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you think there should be no interface method anymore for Smarti to inform about a completed analysis?
As written above, I'd favor to keep the adapter as rich as possible. However you are right: The code for updating the LivechatExternalMessage
was redundant: _updateMapping
should be called
@@ -27,7 +27,8 @@ RocketChat.API.v1.addRoute('smarti.result/:_token', {authRequired: false}, { | |||
if (this.urlParams._token && this.urlParams._token === rcWebhookToken) { | |||
|
|||
SystemLogger.debug('Smarti - got conversation result:', JSON.stringify(this.bodyParams, null, 2)); | |||
SmartiAdapter.analysisCompleted(this.bodyParams.channelId, this.bodyParams.conversationId, this.bodyParams.token); | |||
SmartiAdapter.updateMapping(this.bodyParams.channelId, this.bodyParams.conversationId, new Date(), this.bodyParams.token); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This updating information retrieved from Smarti and notifying about it was intended to be handled in the semantically richer analysisCompleted
Method. I tried to have no application code in the routes but instead have the routes only handle the tech and then pass on the relevant data to the adapter layer. Reverting from this should be well justified.
@@ -35,34 +36,47 @@ export class SmartiAdapter { | |||
} | |||
|
|||
/** | |||
* Returns a Smarti conversation Id for the given roomId. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty much what the signature is saying, isn't it? I intentionally avoided redundant information in comments and obviously had nothing more to say about it ;)
@@ -18,7 +19,7 @@ export class SmartiAdapter { | |||
return RocketChat.settings.get('Assistify_AI_Smarti_Domain'); | |||
} | |||
|
|||
static _updateMapping(roomId, conversationId, timestamp) { | |||
static updateMapping(roomId, conversationId, timestamp) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
having the private methods prefixed _
was intentional. Changing the scope of a method is kind-of a huge change which shouldn't be done imho, since this is only a helper method which is not subject to anyone outside the adapter.
@@ -99,7 +113,7 @@ export class SmartiAdapter { | |||
SystemLogger.debug('RocketChatMessage:', message); | |||
SystemLogger.debug('Message:', requestBodyMessage); | |||
|
|||
let conversationId = SmartiAdapter._getConversationId(message.rid, message); | |||
let conversationId = SmartiAdapter.getConversationId(message.rid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems as if there has been a consumer who was passing on the message
. Thus, again: Why remove this from the mapping? Providing the message allos a more precise timestamping.
And the semantic is well included in the signature:
"get the current view Smarti has about a conversation for a given message which has occurred". Without the message, to me it looks like we could also get an outdated state.
if (smartiResponse && smartiResponse.conversationId) { | ||
conversationId = smartiResponse.conversationId; | ||
SystemLogger.debug(`Retrieving conversation ID for channel: ${ roomId }`); | ||
const m = RocketChat.models.LivechatExternalMessage.findOneById(roomId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you renamed smartiResponse
to m
? Do you think it was not explanatory earlier on? The collection holds the Smarti-Analysis and should better be named like that, but I was reluctant to perform this change yet.
// 404 is expected if no mapping exists | ||
if (error.response.statusCode === 404) { | ||
return null; | ||
const conversation = RocketChat.RateLimiter.limitFunction( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you limited this rate?
conversationId = conversation.id; | ||
const timestamp = message ? message.ts : Date.now(); | ||
SmartiAdapter._updateMapping(roomId, conversationId, timestamp); | ||
let timestamp = conversation.messages && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially, this is the actual change, right? Instead of utilizing the timestamp from the message, you take the timestamp from the conversation stored in Smarti. How can they differ? I'd love to read some comment about the "why we do it like that" in the code
|
||
Meteor.methods({ | ||
triggerFullResync() { | ||
SystemLogger.info('Full Smarti resync triggered'); | ||
|
||
const query = {$or: [{outOfSync: true}, {outOfSync: {$exists: false}}]}; | ||
const query = {$or: [{outOfSync: true}, {outOfSync: false}, {outOfSync: {$exists: false}}]}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now this includes pretty much anything, doesn't it? a boolean value can be absent or have the values true or false. Why did you introduce this? Should we not - given the method name - just remove the selection criterion?
SmartiProxy.propagateToSmarti(verbs.post, 'conversation', conversation); | ||
const response = SmartiProxy.propagateToSmarti(verbs.post, 'conversation', conversation); | ||
SystemLogger.debug('Update conversation with id: ', response.id); | ||
SmartiAdapter.updateMapping(rid, response.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should be done implicitly inside the Smarti adapter. Actually, I would expect we don't use the SmartiProxy
outside the Smarti Adapter. I obviously oversaw this earlier on.
Added an admin option to re-synch completely.
Ordered private dunctions and added some comments.
I've now finished my work so far. The current status should ensure that the cache for the Smarti analysis results is updated correctly. The most important concepts (visibility of methods and the distribution of responsibilities) were taken into account. In the admin backend there is now an additional option to perform a complete synchronization with Smarti. |
@mrsimpson Thank you for the valuable comments from the review. It would be great if you could take a look at the current state. The most important changes I've made you'll find within the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks way better now 👍 . The new determination of the sync-timestamp raises some questions as well as the limiting.
@@ -27,7 +27,8 @@ RocketChat.API.v1.addRoute('smarti.result/:_token', {authRequired: false}, { | |||
if (this.urlParams._token && this.urlParams._token === rcWebhookToken) { | |||
|
|||
SystemLogger.debug('Smarti - got conversation result:', JSON.stringify(this.bodyParams, null, 2)); | |||
SmartiAdapter.analysisCompleted(this.bodyParams.channelId, this.bodyParams.conversationId, this.bodyParams.token); | |||
SmartiAdapter.analysisCompleted(this.bodyParams.channelId, this.bodyParams.conversationId, Date.now(), this.bodyParams.token); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ruKurz after y'day's decision to use the timestamp of the smarti conversation for the mapping information, why do we pass Date.now()
into this method actually?
*/ | ||
static analysisCompleted(roomId, smartiConversationId, token) { | ||
static analysisCompleted(roomId, conversationId, timestamp, token) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it not like that we've always got the conversation at hand once this method is called? Then, we could (and should) get the timestamp via _getLastConversationAnalyzedTime
and remove the conversationId
and timestamp
from the signature.
return false; | ||
|
||
conversation = RocketChat.RateLimiter.limitFunction( | ||
SmartiProxy.propagateToSmarti, 5, 1000, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should limit the rate of this method being called - but not the interactions inside this method with Smarti - it would slow-down the sync.
SmartiProxy.propagateToSmarti(verbs.delete, | ||
`conversation/${ conversation.id }`, null); | ||
RocketChat.RateLimiter.limitFunction( | ||
SmartiProxy.propagateToSmarti, 5, 1000, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here: no limiting of the propagation
@@ -137,7 +154,16 @@ Meteor.methods({ | |||
conversation.messages.push(newMessage); | |||
} | |||
|
|||
SmartiProxy.propagateToSmarti(verbs.post, 'conversation', conversation); | |||
const analyzedConversation = RocketChat.RateLimiter.limitFunction( | |||
SmartiProxy.propagateToSmarti, 5, 1000, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and again: limiting
P.s.: If you merge |
Hi @mrsimpson, I had to notice that the asynchronous callbacks have been replaced by using the synchronous analysis when updating to Smarti If we want to get through the current changes, we have to do the analysis synchronously or we can use the conversationId cache (livechat-external-messages) in order to get the roomId by conversationId on the Assistify.Chat side. For now I've implemented to get the roomId by the Created an Issue for Smarti: redlink-gmbh/smarti#251 |
Restricted resynch access for admins Extracted synch to Smartiadapter Using getProperties instead legacy impl when sync
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some intermediate comments
SmartiProxy.propagateToSmarti(verbs.delete, `conversation/${ conversationId }/message/${ message._id }`); | ||
SmartiAdapter._getAnalysisResult(message.rid, conversationId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why's an analysis result retrieved after delete?
// post the new conversation to Smarti | ||
const newSmartiConversation = SmartiProxy.propagateToSmarti(verbs.post, 'conversation', conversation); | ||
// get the analysis result | ||
const analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ newSmartiConversation.id }/analysis`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When submitting analysis: true
in the body of the post, the analysis is synchronously returned. Thus, the get can be omitted.
Simplified clear method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks reasonable to me ;)
@@ -29,6 +31,9 @@ export class CreateRequestFromExpertise extends CreateRequestBase { | |||
this._members = CreateRequestFromExpertise.getExperts(this._expertise); | |||
} | |||
const roomCreateResult = RocketChat.createRoom('r', this.name, Meteor.user() && Meteor.user().username, this._members, false, {expertise: this._expertise}); | |||
const knowledgeAdapter = getKnowledgeAdapter(); | |||
knowledgeAdapter.afterCreateChannel(roomCreateResult.rid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked myself "Why not have this registered as a hook?". Then I found the answer: there's no good implementation of this in the core: There's only
if (type === 'c') {
Meteor.defer(() => {
RocketChat.callbacks.run('afterCreateChannel', owner, room);
});
} else if (type === 'p') {
Meteor.defer(() => {
RocketChat.callbacks.run('afterCreatePrivateGroup', owner, room);
});
}
in createRoom.js
From my side this PR has an nearly good state. Include
create a separate Issue
Noticed
|
(cherry picked from commit 406eca9)
@ThomasRoehl did you make the end-to-end tests run again? |
…Rocket.Chat into fix/smarti-results-not-loaded
…sistify/Rocket.Chat into fix/smarti-results-not-loaded
…Rocket.Chat into fix/smarti-results-not-loaded
Tests are finally running through on my machine, hopefully on circleCi as well. |
tests in circleCI went well. So is there anything keeping us from merging this PR into |
Closes #345
This PR includes: