Skip to content

Commit

Permalink
Refactor refreshToggle type to string and update related components f…
Browse files Browse the repository at this point in the history
…or consistency
  • Loading branch information
simlarsen committed Sep 25, 2024
1 parent 61a275e commit ca7745c
Show file tree
Hide file tree
Showing 19 changed files with 170 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default class DatabaseBaseModel extends BaseEntity {
public pluralName!: string | null;

// realtime events.
public enableRealtimeEventsOn: EnableRealtimeEventsOn | null = null;
public enableRealtimeEventsOn!: EnableRealtimeEventsOn | null;

// total items by
public totalItemsByColumnName!: string | null;
Expand Down
2 changes: 1 addition & 1 deletion Common/Server/Services/AnalyticsDatabaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ export default class AnalyticsDatabaseService<

promises.push(
Realtime.emitModelEvent({
model: item,
modelId: item.id!,
tenantId: tenantId,
eventType: ModelEventType.Create,
modelType: this.modelType,
Expand Down
10 changes: 8 additions & 2 deletions Common/Server/Services/DatabaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,12 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
projectId: ObjectID,
modelEventType: ModelEventType,
): Promise<void> {
logger.debug("Realtime Events Enabled");
logger.debug(this.model.enableRealtimeEventsOn);

if (Realtime.isInitialized() && this.model.enableRealtimeEventsOn) {
let shouldEmitEvent = false;
logger.debug("Emitting realtime event");
let shouldEmitEvent: boolean = false;

if (
this.model.enableRealtimeEventsOn.create &&
Expand All @@ -548,15 +552,17 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
}

if (!shouldEmitEvent) {
logger.debug("Realtime event not enabled for this event type");
return;
}

logger.debug("Emitting realtime event");
Realtime.emitModelEvent({
tenantId: projectId,
eventType: modelEventType,
modelId: modelId,
modelType: this.modelType,
}).catch((err) => {
}).catch((err: Error) => {
logger.error("Cannot emit realtime event");
logger.error(err);
});
Expand Down
119 changes: 74 additions & 45 deletions Common/Server/Utils/Realtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,63 @@ export default abstract class Realtime {
private static socketServer: SocketServer | null = null;

public static isInitialized(): boolean {
return this.socketServer !== null;
logger.debug("Checking if socket server is initialized");
const isInitialized: boolean = this.socketServer !== null;
logger.debug(`Socket server is initialized: ${isInitialized}`);
return isInitialized;
}

public static async init(): Promise<SocketServer | null> {
if (!this.socketServer) {
logger.debug("Initializing socket server");
this.socketServer = IO.getSocketServer();
logger.debug("Realtime socket server initialized");
}

this.socketServer!.on("connection", (socket: Socket) => {
socket.on(EventName.ListenToModalEvent, async (data: JSONObject) => {
// TODO: validate if this soocket has access to this tenant

// TODO: validate if this socket has access to this model

// TODO: validate if this socket has access to this event type

// TODO: validate if this socket has access to this query

// TODO: validate if this socket has access to this select

// validate data

if (typeof data["eventType"] !== "string") {
throw new BadDataException("eventType is not a string");
}
if (typeof data["modelType"] !== "string") {
throw new BadDataException("modelType is not a string");
}
if (typeof data["modelName"] !== "string") {
throw new BadDataException("modelName is not a string");
}
if (typeof data["query"] !== "object") {
throw new BadDataException("query is not an object");
}
if (typeof data["tenantId"] !== "string") {
throw new BadDataException("tenantId is not a string");
}
if (typeof data["select"] !== "object") {
throw new BadDataException("select is not an object");
}

await Realtime.listenToModelEvent(socket, {
eventType: data["eventType"] as ModelEventType,
modelType: data["modelType"] as DatabaseType,
modelName: data["modelName"] as string,
tenantId: data["tenantId"] as string,
this.socketServer!.on("connection", (socket: Socket) => {
logger.debug("New socket connection established");

socket.on(EventName.ListenToModalEvent, async (data: JSONObject) => {
logger.debug("Received ListenToModalEvent with data:");
logger.debug(data);

// TODO: validate if this socket has access to this tenant

// TODO: validate if this socket has access to this model

// TODO: validate if this socket has access to this event type

// TODO: validate if this socket has access to this query

// TODO: validate if this socket has access to this select

// validate data

if (typeof data["eventType"] !== "string") {
logger.error("eventType is not a string");
throw new BadDataException("eventType is not a string");
}
if (typeof data["modelType"] !== "string") {
logger.error("modelType is not a string");
throw new BadDataException("modelType is not a string");
}
if (typeof data["modelName"] !== "string") {
logger.error("modelName is not a string");
throw new BadDataException("modelName is not a string");
}
if (typeof data["tenantId"] !== "string") {
logger.error("tenantId is not a string");
throw new BadDataException("tenantId is not a string");
}

await Realtime.listenToModelEvent(socket, {
eventType: data["eventType"] as ModelEventType,
modelType: data["modelType"] as DatabaseType,
modelName: data["modelName"] as string,
tenantId: data["tenantId"] as string,
});
});
});
});
}

return this.socketServer;
}
Expand All @@ -74,7 +81,11 @@ export default abstract class Realtime {
socket: Socket,
data: ListenToModelEventJSON,
): Promise<void> {
logger.debug("Listening to model event with data:");
logger.debug(data);

if (!this.socketServer) {
logger.debug("Socket server not initialized, initializing now");
await this.init();
}

Expand All @@ -84,6 +95,7 @@ export default abstract class Realtime {
data.eventType,
);

logger.debug(`Joining room with ID: ${roomId}`);
// join the room.
await socket.join(roomId);
}
Expand All @@ -92,14 +104,22 @@ export default abstract class Realtime {
socket: Socket,
data: ListenToModelEventJSON,
): Promise<void> {
logger.debug("Stopping listening to model event with data:");
logger.debug(data);

if (!this.socketServer) {
logger.debug("Socket server not initialized, initializing now");
await this.init();
}

// leave this room.
await socket.leave(
RealtimeUtil.getRoomId(data.tenantId, data.modelName, data.eventType),
const roomId: string = RealtimeUtil.getRoomId(
data.tenantId,
data.modelName,
data.eventType,
);
logger.debug(`Leaving room with ID: ${roomId}`);
// leave this room.
await socket.leave(roomId);
}

public static async emitModelEvent(data: {
Expand All @@ -108,17 +128,24 @@ export default abstract class Realtime {
modelId: ObjectID;
modelType: { new (): BaseModel | AnalyticsBaseModel };
}): Promise<void> {
logger.debug("Emitting model event with data:");
logger.debug(`Tenant ID: ${data.tenantId}`);
logger.debug(`Event Type: ${data.eventType}`);
logger.debug(`Model ID: ${data.modelId}`);

if (!this.socketServer) {
logger.debug("Socket server not initialized, initializing now");
await this.init();
}

const jsonObject: JSONObject = {
modelId: data.modelId.toString(),
};

const model = new data.modelType();
const model: BaseModel | AnalyticsBaseModel = new data.modelType();

if (!model.tableName) {
logger.warn("Model does not have a tableName, aborting emit");
return;
}

Expand All @@ -128,6 +155,8 @@ export default abstract class Realtime {
data.eventType,
);

logger.debug(`Emitting event to room with ID: ${roomId}`);
logger.debug(jsonObject);
this.socketServer!.to(roomId).emit(roomId, jsonObject);
}
}
2 changes: 1 addition & 1 deletion Common/Types/Database/TableMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default (props: {
pluralName: string;
icon: IconProp;
tableDescription: string;
enableRealtimeEventsOn?: EnableRealtimeEventsOn;
enableRealtimeEventsOn?: EnableRealtimeEventsOn | undefined;
}) => {
return (ctr: GenericFunction) => {
ctr.prototype.singularName = props.singularName;
Expand Down
7 changes: 1 addition & 6 deletions Common/UI/Components/HeaderAlert/HeaderModelAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBa
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import IconProp from "Common/Types/Icon/IconProp";
import React, { ReactElement, useEffect, useState } from "react";
import RealtimeUtil, { ModelEventType } from "../../../Utils/Realtime";
import Realtime from "../../Utils/Realtime";

export interface ComponentProps<TBaseModel extends BaseModel> {
icon: IconProp;
Expand All @@ -18,7 +16,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
requestOptions?: RequestOptions | undefined;
onCountFetchInit?: (() => void) | undefined;
onClick?: (() => void) | undefined;
refreshToggle?: boolean | undefined;
refreshToggle?: string | undefined;
className?: string | undefined;
}

Expand Down Expand Up @@ -68,9 +66,6 @@ const HeaderModelAlert: <TBaseModel extends BaseModel>(
setIsLoading(false);
}, []);




if (error) {
return <></>;
}
Expand Down
2 changes: 1 addition & 1 deletion Common/UI/Components/ModelList/ModelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
noItemsMessage: string;
headerField?: string | ((item: TBaseModel) => ReactElement) | undefined;
onSelectChange?: ((list: Array<TBaseModel>) => void) | undefined;
refreshToggle?: boolean | undefined;
refreshToggle?: string | undefined;
footer?: ReactElement | undefined;
isDeleteable?: boolean | undefined;
enableDragAndDrop?: boolean | undefined;
Expand Down
2 changes: 1 addition & 1 deletion Common/UI/Components/ModelTable/BaseModelTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export interface BaseTableProps<
onCreateEditModalClose?: (() => void) | undefined;
editButtonText?: string | undefined;
viewButtonText?: string | undefined;
refreshToggle?: boolean | undefined;
refreshToggle?: string | undefined;
fetchRequestOptions?: RequestOptions | undefined;
deleteRequestOptions?: RequestOptions | undefined;
onItemDeleted?: ((item: TBaseModel) => void) | undefined;
Expand Down
65 changes: 38 additions & 27 deletions Dashboard/src/Components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,42 +46,53 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [activeIncidentToggleRefresh, setActiveIncidentToggleRefresh] =
useState<boolean>(true);
useState<string>(OneUptimeDate.getCurrentDate().toString());

const refreshIncidentCount: VoidFunction = () => {
setActiveIncidentToggleRefresh(!activeIncidentToggleRefresh);
setActiveIncidentToggleRefresh(OneUptimeDate.getCurrentDate().toString());
};

useEffect(() => {

const stopListeningOnCreate = Realtime.listenToModelEvent<Incident>({
eventType: ModelEventType.Create,
modelType: Incident,
tenantId: DashboardNavigation.getProjectId()!,
}, () => {
refreshIncidentCount();
});

const stopListeningOnUpdate = Realtime.listenToModelEvent<Incident>({
eventType: ModelEventType.Update,
modelType: Incident,
tenantId: DashboardNavigation.getProjectId()!,
}, () => {
refreshIncidentCount();
});

const stopListeningOnDelete = Realtime.listenToModelEvent<Incident>({
eventType: ModelEventType.Delete,
modelType: Incident,
tenantId: DashboardNavigation.getProjectId()!,
}, () => {
refreshIncidentCount();
});
const stopListeningOnCreate: VoidFunction =
Realtime.listenToModelEvent<Incident>(
{
eventType: ModelEventType.Create,
modelType: Incident,
tenantId: DashboardNavigation.getProjectId()!,
},
() => {
refreshIncidentCount();
},
);

const stopListeningOnUpdate: VoidFunction =
Realtime.listenToModelEvent<Incident>(
{
eventType: ModelEventType.Update,
modelType: Incident,
tenantId: DashboardNavigation.getProjectId()!,
},
() => {
refreshIncidentCount();
},
);

const stopListeningOnDelete: VoidFunction =
Realtime.listenToModelEvent<Incident>(
{
eventType: ModelEventType.Delete,
modelType: Incident,
tenantId: DashboardNavigation.getProjectId()!,
},
() => {
refreshIncidentCount();
},
);

return () => {
// on unmount.
stopListeningOnCreate();
stopListeningOnUpdate();
stopListeningOnUpdate();
stopListeningOnDelete();
};
}, []);
Expand Down
2 changes: 0 additions & 2 deletions Dashboard/src/Components/Logs/LogsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,8 @@ const DashboardLogsViewer: FunctionComponent<ComponentProps> = (
const disconnectFunction: () => void = Realtime.listenToAnalyticsModelEvent(
{
modelType: Log,
query: {},
eventType: ModelEventType.Create,
tenantId: ProjectUtil.getCurrentProjectId()!,
select: select,
},
(model: Log) => {
setLogs((logs: Array<Log>) => {
Expand Down
2 changes: 1 addition & 1 deletion Dashboard/src/Components/Monitor/DisabledWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useAsyncEffect } from "use-async-effect";

export interface ComponentProps {
monitorId: ObjectID;
refreshToggle?: boolean | undefined;
refreshToggle?: string | undefined;
}

const DisabledWarning: FunctionComponent<ComponentProps> = (
Expand Down
Loading

0 comments on commit ca7745c

Please sign in to comment.