Skip to content

Commit

Permalink
add websocket health check implementation & test
Browse files Browse the repository at this point in the history
  • Loading branch information
future-pirate-king committed Oct 26, 2024
1 parent 87fc4bb commit 012cd29
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 71 deletions.
2 changes: 1 addition & 1 deletion app/components/system-status/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<t.head @columns={{this.columns}} />

<t.body @rows={{this.rows}} as |b|>
<b.row as |r|>
<b.row data-test-system-status-rows='{{b.rowValue.id}}' as |r|>
<r.cell>
{{#if r.columnValue.component}}
{{#let (component r.columnValue.component) as |Component|}}
Expand Down
112 changes: 106 additions & 6 deletions app/components/system-status/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,61 @@
import Component from '@glimmer/component';
import { task } from 'ember-concurrency';
import { isNotFoundError, AjaxError } from 'ember-ajax/errors';
import ENV from 'irene/config/environment';
import { task, timeout } from 'ember-concurrency';
import { isNotFoundError, type AjaxError } from 'ember-ajax/errors';
import { inject as service } from '@ember/service';
import DevicefarmService from 'irene/services/devicefarm';
import { tracked } from '@glimmer/tracking';
import IntlService from 'ember-intl/services/intl';
import { next } from '@ember/runloop';
import { waitForPromise } from '@ember/test-waiters';
import type Store from '@ember-data/store';
import type IntlService from 'ember-intl/services/intl';

import ENV from 'irene/config/environment';
import type DevicefarmService from 'irene/services/devicefarm';
import type WebsocketService from 'irene/services/websocket';

import type {
SocketHealthMessage,
SocketInstance,
} from 'irene/services/websocket';

export default class SystemStatusComponent extends Component {
@service declare devicefarm: DevicefarmService;
@service declare websocket: WebsocketService;
@service declare ajax: any;
@service declare session: any;
@service declare store: Store;
@service declare intl: IntlService;

@tracked isStorageWorking = false;
@tracked isDeviceFarmWorking = false;
@tracked isAPIServerWorking = false;
@tracked isWebsocketWorking = false;

socket?: SocketInstance;

constructor(owner: unknown, args: object) {
super(owner, args);

this.getStorageStatus.perform();
this.getDeviceFarmStatus.perform();
this.getAPIServerStatus.perform();

if (this.showRealtimeServerStatus) {
next(this, () => this.getWebsocketHealthStatus.perform());
}
}

willDestroy(): void {
super.willDestroy();

this.handleSocketHealthCheckCleanUp();
}

get isAuthenticated() {
return this.session.isAuthenticated;
}

get showRealtimeServerStatus() {
return this.isAuthenticated;
}

get columns() {
Expand All @@ -32,7 +66,7 @@ export default class SystemStatusComponent extends Component {
}

get rows() {
return [
const statusRows = [
{
id: 'storage',
system: this.intl.t('storage'),
Expand All @@ -53,6 +87,17 @@ export default class SystemStatusComponent extends Component {
isWorking: this.isAPIServerWorking,
},
];

if (this.showRealtimeServerStatus) {
statusRows.push({
id: 'websocket',
system: this.intl.t('realtimeServer'),
isRunning: this.getWebsocketHealthStatus.isRunning,
isWorking: this.isWebsocketWorking,
});
}

return statusRows;
}

getStorageStatus = task({ drop: true }, async () => {
Expand Down Expand Up @@ -84,6 +129,61 @@ export default class SystemStatusComponent extends Component {
this.isAPIServerWorking = false;
}
});

getWebsocketHealthStatus = task({ drop: true }, async () => {
const userId = this.session.data.authenticated.user_id;
this.socket = this.websocket.getSocketInstance();

try {
this.socket.on(
'websocket_health_check',
this.onWebsocketHealthCheck.bind(this)
);

const user = await this.store.findRecord('user', userId);

if (user.socketId) {
await waitForPromise(
this.triggerWebsocketHealthCheck.perform(user.socketId)
);
}
} catch (_) {
this.isWebsocketWorking = false;

this.handleSocketHealthCheckCleanUp();
}
});

triggerWebsocketHealthCheck = task(async (socketId: string) => {
this.socket?.emit('subscribe', { room: socketId });

// wait for user to join room
await timeout(1000);

await this.ajax.post('/api/websocket_health_check', { data: {} });

// wait for socket to notify
await timeout(1000);
});

onWebsocketHealthCheck(data: SocketHealthMessage) {
this.isWebsocketWorking =
ENV.environment === 'test'
? JSON.parse(`${data}`).is_healthy
: data.is_healthy;

this.handleSocketHealthCheckCleanUp();
}

handleSocketHealthCheckCleanUp() {
this.socket?.off(
'websocket_health_check',
this.onWebsocketHealthCheck,
this
);

this.socket?.close();
}
}

declare module '@glint/environment-ember-loose/registry' {
Expand Down
27 changes: 14 additions & 13 deletions app/routes/authenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import Transition from '@ember/routing/transition';
import Store from '@ember-data/store';
import { all } from 'rsvp';
import IntlService from 'ember-intl/services/intl';
import RouterService from '@ember/routing/router-service';

import MeService from 'irene/services/me';
import DatetimeService from 'irene/services/datetime';
import TrialService from 'irene/services/trial';
import IntegrationService from 'irene/services/integration';
import OrganizationService from 'irene/services/organization';
import ConfigurationService from 'irene/services/configuration';
import UserModel from 'irene/models/user';
import type Transition from '@ember/routing/transition';
import type Store from '@ember-data/store';
import type IntlService from 'ember-intl/services/intl';
import type RouterService from '@ember/routing/router-service';

import type MeService from 'irene/services/me';
import type DatetimeService from 'irene/services/datetime';
import type TrialService from 'irene/services/trial';
import type IntegrationService from 'irene/services/integration';
import type OrganizationService from 'irene/services/organization';
import type ConfigurationService from 'irene/services/configuration';
import type WebsocketService from 'irene/services/websocket';
import type UserModel from 'irene/models/user';
import { CSBMap } from 'irene/router';
import ENV from 'irene/config/environment';
import triggerAnalytics from 'irene/utils/trigger-analytics';
Expand All @@ -26,7 +27,7 @@ export default class AuthenticatedRoute extends Route {
@service declare datetime: DatetimeService;
@service declare trial: TrialService;
@service declare rollbar: any;
@service declare websocket: any;
@service declare websocket: WebsocketService;
@service declare integration: IntegrationService;
@service declare store: Store;
@service('notifications') declare notify: NotificationService;
Expand Down
Loading

0 comments on commit 012cd29

Please sign in to comment.