Skip to content
This repository has been archived by the owner on Sep 30, 2019. It is now read-only.

Support open deployments and related realtime events #175

Merged
merged 28 commits into from
Apr 6, 2017

Conversation

dennari
Copy link
Contributor

@dennari dennari commented Apr 4, 2017

If a raw deployment is considered 'open', it's served without authentication or authorization.

Adds a new endpoint at /events/deployment/{projectId}-{deploymentId} which returns COMMENT_ADDED and COMMENT_DELETED events related to the specified open deployment.

NOTE: change the base to master after #174 has been merged.

@vsaarinen
Copy link
Member

vsaarinen commented Apr 4, 2017

If the user is not logged in, they don't have a teamId. Unless there's something that I'm missing, the path can't have teamId in it.

Copy link
Member

@vsaarinen vsaarinen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments.

private requestHandlerFactory(streamFactory: StreamFactory) {
return async (request: Hapi.Request, reply: Hapi.IReply) => {
try {
// const observable = await this.getObservable(teamId, since ? parseInt(since, 10) : undefined);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment

@@ -304,7 +324,7 @@ export class RealtimeHapiPlugin {
try {
const deployment = event.payload.deployment;
const apiResponse = await this.jsonApiPlugin.getEntity(
'deployment', api => api.toApiDeployment(deployment.projectId, deployment));
'deployment', api => api.toApiDeployment(deployment.projectId, deployment));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last ) should be on the following line.

super({
name: 'realtime-plugin',
version: '1.0.0',
});
this.persistedEvents = this.eventBus.getStream()
.filter(isPersistedEvent)
.map(event => <PersistedEvent<any>> event)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since isPersistedEvent is a type guard, isn't this map unnecessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's purpose is only to make the compiler agree that this.persistedEvents has the desired narrowed type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it without the map and that seemed to be the case.

}
observable = Observable.concat(Observable.from(existing), observable);
private async getStream(request: Hapi.Request) {
const {teamId, projectId, deploymentId} = request.params;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have spaces inside curly brackets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we missing some linter rule?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's currently not a supported rule in tslint AFAIK.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.persistedEvents.filter(predicate),
);
if (since && !isNaN(since)) {
const existing = await this.eventBus.getEvents(_teamId, since);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this pass on missed events that are not related to this deployment if projectId and deploymentId exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice catch indeed 👍

@dennari dennari force-pushed the deployment-subdomain branch from 63155a4 to 3543baa Compare April 4, 2017 21:38
@dennari dennari force-pushed the deployment-realtime branch from 5fad3be to e247cf8 Compare April 5, 2017 01:12
@dennari dennari changed the title Add an endpoint for deployment specific comment events Support open deployments and related realtime events Apr 5, 2017
@dennari
Copy link
Contributor Author

dennari commented Apr 5, 2017

Changed to not require the teamId. This PR now also includes the related changes for serving open raw deployments.

Copy link
Member

@vsaarinen vsaarinen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea splitting up the realtime stuff into a separate module.

I haven't finished going over all the code, but one bigger thing comes to mind: in the future, it's likely that we want to configure deployment "openness" on a per-project basis. Because of this, making the API ready for this type of check is likely the best route since it's logically clear and we can easily implement our current situation in the "is this project open?" check.

In the realtime API, you now do some checks related to an "open team ID", but I feel it would be better to have some sort of "is this project open?" check instead. That check can then be implemented as "is this project an open team's project?" in our current situation. That also allows us to have multiple "open" teams with minimal changes in the future, if needed.

} else {
server.route(this.traceRoute());
server.route(this.rawDeploymentRoutes(false));
server.route(this.rawDeploymentRoutes(true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are both of these here on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the one with false is for the open deployments

};
}

private rawDeploymentRoutes(needsAuthorization: boolean): Hapi.IRouteConfiguration[] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much clearer now. 👍

@@ -39,6 +39,7 @@ interface SSE {

const flowToken = process.env.FLOWDOCK_FLOW_TOKEN;
const projectFolder = process.env.SYSTEM_TEST_PROJECT ? process.env.SYSTEM_TEST_PROJECT : 'blank';
const openProjectFolder = process.env.SYSTEM_TEST_PROJECT_OPEN ? process.env.SYSTEM_TEST_PROJECT_OPEN : 'blank-open';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and other adjacent rows) could be simplified to

const openProjectFolder = process.env.SYSTEM_TEST_PROJECT_OPEN || 'blank-open';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// listens to SSEEvents and shares them
this.sseStream = this.eventBus.getStream()
.filter(isPersistedEvent)
.map(event => event as PersistedEvent<any>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, it seems that removing the map still makes this.sseStream of the type Observable<PersistedEvent<any>> because of the type guard, making it unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I'd like to say the typings for filter have improved, making the map unnecessary.

cors: true,
validate: {
params: {
teamId: Joi.number().required(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be here?

this.logger.error(msg, { error, event });
throw Error(msg);
const { projectId, deploymentId } = request.params;
const teamId = await request.getOpenTeamId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this now assume that whenever we fetch events from this endpoint that they belong to the open team? This is not the case if I'm a logged in user from another team and I use a Deployment View as a landing page, at which point this endpoint will be used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A higher-level comment about this handler in the main review comment.

@@ -20,6 +20,8 @@ declare module 'hapi' {
interface RequestDecorators {
userHasAccessToProject: (projectId: number) => Promise<boolean>;
userHasAccessToTeam: (teamId: number) => Promise<boolean>;
isOpenDeployment: (projectId: number, deploymentId: number) => Promise<boolean>;
getOpenTeamId: () => Promise<number | undefined>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we have multiple teams that want open deployments?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd maybe get rid of this function altogether. More info in the main review comment.

@dennari dennari mentioned this pull request Apr 5, 2017
@dennari
Copy link
Contributor Author

dennari commented Apr 5, 2017

Good points. The initial implementation left much to be desired. I've now rehauled the way the 'open' check works and feel this is much closer to what it should've been.

@dennari dennari force-pushed the deployment-realtime branch from 36649b6 to 4e7d6db Compare April 5, 2017 12:41
@vsaarinen vsaarinen changed the base branch from deployment-subdomain to master April 5, 2017 13:00
return async (
payload: AccessToken,
request: Hapi.Request,
callback: (err: any, valid: boolean, credentials?: any) => void,
) => {
let isAuthorized = false;
let isAuthorized: boolean | undefined = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this can now have three values, I'd prefer e.g. an enum to make it explicit what each state means.

@dennari dennari force-pushed the deployment-realtime branch from 4e7d6db to 3aa3188 Compare April 6, 2017 09:42
dennari added 2 commits April 6, 2017 15:10
Add typings and include authorizationStatus as an enum
Copy link
Member

@vsaarinen vsaarinen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice changes! Two very minor things.

// Store the original hostname
request.app.hostname = hostname;
// Overwrite the hostname to use virtual host routing
// Each vhost (including the default one) has a separate routing table
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we still use two separate ones

return callback(
undefined,
authorizationStatus === AuthorizationStatus.AUTHORIZED ||
authorizationStatus === AuthorizationStatus.NOT_CHECKED,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be indented a bit more.

@vsaarinen vsaarinen merged commit b098dc7 into master Apr 6, 2017
@vsaarinen
Copy link
Member

🎉

@vsaarinen vsaarinen deleted the deployment-realtime branch April 6, 2017 13:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants