-
Notifications
You must be signed in to change notification settings - Fork 1
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
Receive Server-Side-Events within the UX and display them raw on the Publishing Process view #224
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.
Overall this looks fantastic! Really liked that we split off the work for managing these in the store to review just the simplest version of this - which is already pretty complex.
The API for the EventStream is great! I just had a few nitpicks, and bits that I think we can clean up around typing and organization. Also had a few logic questions to be sure I understood everything.
console.log(eventStream.status()); | ||
|
||
// Have to be sure to close connection or it will be leaked on agent (if it continues to run) | ||
onBeforeUnmount(() => { |
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.
Out of curiosity how did you choose between onBeforeUnmount
and unmounted
?
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 I looked up lifecycle events for guidance, I followed this reference: Destruction Hooks – Cleaning Things Up which said:
Because this is before the component starts to get torn down, this is the time to do most, if not all, of the clean up. At this stage, your component is still fully functional and nothing has been destroyed yet.
It did seem more useful to have some of the component still around just incase I needed it, versus at unmount:
at this point, most of your component and its properties are gone so there’s not much you can do.
<p ref="agentLogEnd"> | ||
| ||
</p> |
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 is a bit strange to me that this is has size (from the  
) and margin (default from Quasar). I think for this we will want special CSS to keep this from affecting the document flow in the DOM.
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 really is just a no-op html target ref that I needed to be able to scroll down to. This will absolutely change in the next PR and be deleted, although we'll probably need something similar. I was unable to get the visibility scrolling to work unless it actually had a presence within the DOM more than just the node. Totally open to a new way of doing this in the next PR.
import { scroll as qScroll } from 'quasar'; | ||
const { getScrollTarget, setVerticalScrollPosition } = qScroll; |
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.
Really like your import aliasing here and deconstructing to make this a bit easier to read later in the doc.
Nitpicky, but I'd separate the imports from the deconstructing with a whitespace line, but really like your methodology here.
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.
Thanks, I don't deserve the credit, you'll see that I stole this from one of the Quasar examples.
I also wanted to have a white space between import and const, but the restructuring that I was doing left me wanting to really have an explicit import restructure for those two, so keeping them adjacent was the compromise I came to (with myself). I'll go ahead and add the line, I think I agree with you.
const publishingComplete = () => { | ||
backButtonDisabled.value = false; | ||
}; | ||
props.eventStream.addEventMonitorCallback(['publish/success'], publishingComplete); |
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 extremely clean. I really like how simple and understandable the callback here is.
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.
Thank you!
if (wildCardIndex > 0 && subscriptionType.length === wildCardIndex + 2) { | ||
const basePath = subscriptionType.substring(0, wildCardIndex); | ||
if (incomingEventType.indexOf(basePath) === 0) { | ||
this.logMsg('matched on start of string'); | ||
return true; | ||
} |
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.
Just want to be sure I'm reading this block correctly.
This is checking that /*
is no the first bit of the string, and the subscription type ends with /*
? Then from there it checks that the subscription and the event have the same basePath
(the bit before /*
)?
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.
Correct. I've added some comments to that section to help us all understand the logic in the future.
incomingEventType.indexOf(parts[0]) === 0 && | ||
incomingEventType.indexOf(parts[1]) === incomingEventType.length - parts[1].length |
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 does something very similar to the above from the looks of it, but doesn't appear to check that the base is the same. Instead it appears to check if the subscription and incoming event have the same length at the end? That feels incorrect to me, instead shouldn't we check to see if the ending and beginning path match?
This felt a bit tough to read. Perhaps a comment, or splitting these into separate, more bite-sized matching functions wouldn't be helpful. 🤔
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've added these comments:
// Are we using a glob, which is meant to be in the middle of two strings
// which need to be matched
const globIndex = subscriptionType.indexOf('/**/');
if (globIndex > 0) {
// split our subscription type string into two parts (before and after the glob characters)
const parts = subscriptionType.split('/**/');
// to match, we must make sure we find that the incoming event type starts
// exactly with our first part and ends with exactly our second part, regardless of how
// many characters in the incoming event type are "consumed" by our glob query.
if (
incomingEventType.indexOf(parts[0]) === 0 &&
incomingEventType.indexOf(parts[1]) === incomingEventType.length - parts[1].length
) {
this.logMsg('matched on glob');
return true;
}
}
Does that make more sense?
this.dispatchMessage({ | ||
type: 'open/sse', | ||
time: new Date().toString(), | ||
data: {}, |
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 data
is optional so we could remove this rather than sending an empty object.
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 have refactored it due to comments above, so now it is required.
web/src/api/resources/EventStream.ts
Outdated
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const rawObj: any = JSON.parse(data); | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const obj: any = camelcaseKeys(rawObj); | ||
// Type safety guard! | ||
if (isEventStreamMessage(obj)) { | ||
return obj as EventStreamMessage; | ||
} | ||
return null; |
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.
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
const rawObj: any = JSON.parse(data); | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
const obj: any = camelcaseKeys(rawObj); | |
// Type safety guard! | |
if (isEventStreamMessage(obj)) { | |
return obj as EventStreamMessage; | |
} | |
return null; | |
const rawObj = JSON.parse(data); | |
const obj = camelcaseKeys(rawObj); | |
if (isEventStreamMessage(obj)) { | |
return obj; | |
} | |
return null; |
We can actually get rid of all of the any
types here and the as
. rawObj
and obj
infer their any
types from JSON.parse
. isEventStreamMessage
also lets us know that obj
is a EventSreamMessage
so we don't need the as
inside the if conditional since obj
is now typed as EventStreamMessage
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.
Great observation, thank you.
callback: OnMessageEventSourceCallback, | ||
} | ||
|
||
export class EventStream { |
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 is a bit strange to me that this handles both the real case and the mocking case. Perhaps it would be better to separate these and have a parent interface that both implement. That way we can separate the real implementation and the mocking implementation more easily.
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.
Since we're not yet unit testing this, I'll remove this and we can implement it following your findings with the unit test mocks. I had some ideas for playing around with the concept of production runtime mocking of events, rather than bringing in a different provider, but I'm not so excited about it anymore.
Co-authored-by: Jordan Jensen <[email protected]>
Co-authored-by: Jordan Jensen <[email protected]>
Intent
This PR provides the plumbing within the UX to receive the Server-Side-Events being made available from the Agent and updates the Agent code to provide a API endpoint to expose them. The events are displayed on the publish process view after the user hits the publish button on the configuration view.
Resolves #161.
Type of Change
Approach
/api/events
Automated Tests
Directions for Reviewers
Functionality can be verify by:
just build-dev