mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Add Push Notification Logs functionality
- Introduced PushNotificationLog model to track push notifications sent to users. - Added permissions for reading push logs in the Permission enum. - Updated various side menus to include links to Push Logs in Alerts, Incidents, and Settings. - Created routes for viewing Push Logs in Alerts, Incidents, and Status Pages. - Implemented UI components for displaying Push Logs in respective pages. - Added filtering and column configuration for Push Logs tables. - Integrated PushStatus enum to manage the status of push notifications. - Implemented PushNotificationLogService for database interactions related to push logs.
This commit is contained in:
@@ -282,6 +282,9 @@ import ShortLinkService, {
|
||||
import SmsLogService, {
|
||||
Service as SmsLogServiceType,
|
||||
} from "Common/Server/Services/SmsLogService";
|
||||
import PushNotificationLogService, {
|
||||
Service as PushNotificationLogServiceType,
|
||||
} from "Common/Server/Services/PushNotificationLogService";
|
||||
import SpanService, {
|
||||
SpanService as SpanServiceType,
|
||||
} from "Common/Server/Services/SpanService";
|
||||
@@ -379,6 +382,7 @@ import Span from "Common/Models/AnalyticsModels/Span";
|
||||
import ApiKey from "Common/Models/DatabaseModels/ApiKey";
|
||||
import ApiKeyPermission from "Common/Models/DatabaseModels/ApiKeyPermission";
|
||||
import CallLog from "Common/Models/DatabaseModels/CallLog";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import Domain from "Common/Models/DatabaseModels/Domain";
|
||||
import EmailLog from "Common/Models/DatabaseModels/EmailLog";
|
||||
import EmailVerificationToken from "Common/Models/DatabaseModels/EmailVerificationToken";
|
||||
@@ -1531,6 +1535,14 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new BaseAPI<SmsLog, SmsLogServiceType>(SmsLog, SmsLogService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<PushNotificationLog, PushNotificationLogServiceType>(
|
||||
PushNotificationLog,
|
||||
PushNotificationLogService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<EmailLog, EmailLogServiceType>(
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import PushService from "../Services/PushNotificationService";
|
||||
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
|
||||
await PushService.send(
|
||||
{
|
||||
deviceTokens: (body["deviceTokens"] as string[]) || [],
|
||||
deviceType: (body["deviceType"] as any) || "web",
|
||||
message: body["message"] as any,
|
||||
},
|
||||
{
|
||||
projectId: (body["projectId"] as ObjectID) || undefined,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import CallAPI from "./API/Call";
|
||||
// API
|
||||
import MailAPI from "./API/Mail";
|
||||
import SmsAPI from "./API/SMS";
|
||||
import PushNotificationAPI from "./API/PushNotification";
|
||||
import SMTPConfigAPI from "./API/SMTPConfig";
|
||||
import "./Utils/Handlebars";
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
@@ -15,6 +16,7 @@ const NotificationFeatureSet: FeatureSet = {
|
||||
|
||||
app.use([`/${APP_NAME}/email`, "/email"], MailAPI);
|
||||
app.use([`/${APP_NAME}/sms`, "/sms"], SmsAPI);
|
||||
app.use([`/${APP_NAME}/push`, "/push"], PushNotificationAPI);
|
||||
app.use([`/${APP_NAME}/call`, "/call"], CallAPI);
|
||||
app.use([`/${APP_NAME}/smtp-config`, "/smtp-config"], SMTPConfigAPI);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import PushNotificationRequest from "Common/Types/PushNotification/PushNotificationRequest";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PushNotificationServiceCommon from "Common/Server/Services/PushNotificationService";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import PushNotificationLogService from "Common/Server/Services/PushNotificationLogService";
|
||||
import UserOnCallLogTimelineService from "Common/Server/Services/UserOnCallLogTimelineService";
|
||||
import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus";
|
||||
import PushStatus from "Common/Types/PushNotification/PushStatus";
|
||||
|
||||
export default class PushNotificationService {
|
||||
public static async send(
|
||||
request: PushNotificationRequest,
|
||||
options: {
|
||||
projectId?: ObjectID | undefined;
|
||||
isSensitive?: boolean;
|
||||
userOnCallLogTimelineId?: ObjectID | undefined;
|
||||
incidentId?: ObjectID | undefined;
|
||||
alertId?: ObjectID | undefined;
|
||||
scheduledMaintenanceId?: ObjectID | undefined;
|
||||
statusPageId?: ObjectID | undefined;
|
||||
statusPageAnnouncementId?: ObjectID | undefined;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const log: PushNotificationLog = new PushNotificationLog();
|
||||
|
||||
if (options.projectId) {
|
||||
log.projectId = options.projectId;
|
||||
}
|
||||
|
||||
log.title = request.message.title || "";
|
||||
log.body = options.isSensitive ? "Sensitive message not logged" : (request.message.body || "");
|
||||
log.deviceType = request.deviceType;
|
||||
|
||||
if (options.incidentId) log.incidentId = options.incidentId;
|
||||
if (options.alertId) log.alertId = options.alertId;
|
||||
if (options.scheduledMaintenanceId)
|
||||
log.scheduledMaintenanceId = options.scheduledMaintenanceId;
|
||||
if (options.statusPageId) log.statusPageId = options.statusPageId;
|
||||
if (options.statusPageAnnouncementId)
|
||||
log.statusPageAnnouncementId = options.statusPageAnnouncementId;
|
||||
|
||||
try {
|
||||
await PushNotificationServiceCommon.sendPushNotification(request, {
|
||||
projectId: options.projectId,
|
||||
isSensitive: Boolean(options.isSensitive),
|
||||
userOnCallLogTimelineId: options.userOnCallLogTimelineId,
|
||||
});
|
||||
|
||||
log.status = PushStatus.Success;
|
||||
log.statusMessage = "Push notification sent";
|
||||
} catch (err: any) {
|
||||
log.status = PushStatus.Error;
|
||||
log.statusMessage = err?.message || err?.toString?.() || "Failed to send push notification";
|
||||
|
||||
if (options.userOnCallLogTimelineId) {
|
||||
await UserOnCallLogTimelineService.updateOneById({
|
||||
id: options.userOnCallLogTimelineId,
|
||||
data: {
|
||||
status: UserNotificationStatus.Error,
|
||||
statusMessage: log.statusMessage || "Push send failed",
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.projectId) {
|
||||
await PushNotificationLogService.create({
|
||||
data: log,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
}
|
||||
|
||||
if (log.status === PushStatus.Error) {
|
||||
throw new Error(log.statusMessage || "Push failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ import ServiceCopilotCodeRepository from "./ServiceCopilotCodeRepository";
|
||||
import ShortLink from "./ShortLink";
|
||||
// SMS
|
||||
import SmsLog from "./SmsLog";
|
||||
import PushNotificationLog from "./PushNotificationLog";
|
||||
// Status Page
|
||||
import StatusPage from "./StatusPage";
|
||||
import StatusPageAnnouncement from "./StatusPageAnnouncement";
|
||||
@@ -292,6 +293,7 @@ const AllModelTypes: Array<{
|
||||
StatusPageOwnerUser,
|
||||
|
||||
SmsLog,
|
||||
PushNotificationLog,
|
||||
CallLog,
|
||||
EmailLog,
|
||||
|
||||
|
||||
564
Common/Models/DatabaseModels/PushNotificationLog.ts
Normal file
564
Common/Models/DatabaseModels/PushNotificationLog.ts
Normal file
@@ -0,0 +1,564 @@
|
||||
import Project from "./Project";
|
||||
import Incident from "./Incident";
|
||||
import Alert from "./Alert";
|
||||
import ScheduledMaintenance from "./ScheduledMaintenance";
|
||||
import StatusPage from "./StatusPage";
|
||||
import StatusPageAnnouncement from "./StatusPageAnnouncement";
|
||||
import User from "./User";
|
||||
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import PushStatus from "../../Types/PushNotification/PushStatus";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
|
||||
@EnableDocumentation()
|
||||
@TenantColumn("projectId")
|
||||
@TableAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
delete: [],
|
||||
update: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/push-notification-log"))
|
||||
@Entity({
|
||||
name: "PushNotificationLog",
|
||||
})
|
||||
@EnableWorkflow({
|
||||
create: true,
|
||||
delete: false,
|
||||
update: false,
|
||||
})
|
||||
@TableMetadata({
|
||||
tableName: "PushNotificationLog",
|
||||
singularName: "Push Notification Log",
|
||||
pluralName: "Push Notification Logs",
|
||||
icon: IconProp.Bell,
|
||||
tableDescription:
|
||||
"Logs of all the Push Notifications sent out to all users and subscribers for this project.",
|
||||
})
|
||||
export default class PushNotificationLog extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "projectId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Project,
|
||||
title: "Project",
|
||||
description: "Relation to Project Resource in which this object belongs",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return Project;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "projectId" })
|
||||
public project?: Project = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Project ID",
|
||||
description: "ID of your OneUptime Project in which this object belongs",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public projectId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.LongText,
|
||||
title: "Title",
|
||||
description: "Title of the push notification",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public title?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.VeryLongText,
|
||||
title: "Body",
|
||||
description: "Body of the push notification",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.VeryLongText,
|
||||
})
|
||||
public body?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Device Type",
|
||||
description: "Type of device this was sent to (e.g., web)",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public deviceType?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.LongText,
|
||||
title: "Status Message",
|
||||
description: "Status Message (if any)",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public statusMessage?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Status",
|
||||
description: "Status of the push notification",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public status?: PushStatus = undefined;
|
||||
|
||||
// Relations to resources that triggered this push notification (nullable)
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "incidentId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Incident,
|
||||
title: "Incident",
|
||||
description: "Incident associated with this Push (if any)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return Incident;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "incidentId" })
|
||||
public incident?: Incident = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: false,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Incident ID",
|
||||
description: "ID of Incident associated with this Push (if any)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public incidentId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "alertId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Alert,
|
||||
title: "Alert",
|
||||
description: "Alert associated with this Push (if any)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return Alert;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "alertId" })
|
||||
public alert?: Alert = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: false,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Alert ID",
|
||||
description: "ID of Alert associated with this Push (if any)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public alertId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "scheduledMaintenanceId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: ScheduledMaintenance,
|
||||
title: "Scheduled Maintenance",
|
||||
description:
|
||||
"Scheduled Maintenance associated with this Push (if any)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return ScheduledMaintenance;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "scheduledMaintenanceId" })
|
||||
public scheduledMaintenance?: ScheduledMaintenance = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: false,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Scheduled Maintenance ID",
|
||||
description:
|
||||
"ID of Scheduled Maintenance associated with this Push (if any)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public scheduledMaintenanceId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "statusPageId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: StatusPage,
|
||||
title: "Status Page",
|
||||
description: "Status Page associated with this Push (if any)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return StatusPage;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "statusPageId" })
|
||||
public statusPage?: StatusPage = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: false,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Status Page ID",
|
||||
description: "ID of Status Page associated with this Push (if any)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public statusPageId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "statusPageAnnouncementId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: StatusPageAnnouncement,
|
||||
title: "Status Page Announcement",
|
||||
description:
|
||||
"Status Page Announcement associated with this Push (if any)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return StatusPageAnnouncement;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "statusPageAnnouncementId" })
|
||||
public statusPageAnnouncement?: StatusPageAnnouncement = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadPushLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: false,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Status Page Announcement ID",
|
||||
description:
|
||||
"ID of Status Page Announcement associated with this Push (if any)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public statusPageAnnouncementId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "deletedByUserId",
|
||||
type: TableColumnType.Entity,
|
||||
title: "Deleted by User",
|
||||
modelType: User,
|
||||
description:
|
||||
"Relation to User who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
cascade: false,
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "SET NULL",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "deletedByUserId" })
|
||||
public deletedByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Deleted by User ID",
|
||||
description:
|
||||
"User ID who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
}
|
||||
14
Common/Server/Services/PushNotificationLogService.ts
Normal file
14
Common/Server/Services/PushNotificationLogService.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { IsBillingEnabled } from "../EnvironmentConfig";
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/PushNotificationLog";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
if (IsBillingEnabled) {
|
||||
this.hardDeleteItemsOlderThanInDays("createdAt", 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
@@ -142,6 +142,7 @@ enum Permission {
|
||||
ReadSmsLog = "ReadSmsLog",
|
||||
ReadEmailLog = "ReadEmailLog",
|
||||
ReadCallLog = "ReadCallLog",
|
||||
ReadPushLog = "ReadPushLog",
|
||||
|
||||
CreateIncidentOwnerTeam = "CreateIncidentOwnerTeam",
|
||||
DeleteIncidentOwnerTeam = "DeleteIncidentOwnerTeam",
|
||||
@@ -3003,6 +3004,14 @@ export class PermissionHelper {
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.ReadPushLog,
|
||||
title: "Read Push Log",
|
||||
description: "This permission can read Push Notification Logs of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateMonitorProbe,
|
||||
title: "Create Monitor Probe",
|
||||
|
||||
6
Common/Types/PushNotification/PushStatus.ts
Normal file
6
Common/Types/PushNotification/PushStatus.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
enum PushStatus {
|
||||
Success = "Success",
|
||||
Error = "Error",
|
||||
}
|
||||
|
||||
export default PushStatus;
|
||||
59
Dashboard/src/Pages/Alerts/View/NotificationLogsPush.tsx
Normal file
59
Dashboard/src/Pages/Alerts/View/NotificationLogsPush.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import Columns from "Common/UI/Components/ModelTable/Columns";
|
||||
import Pill from "Common/UI/Components/Pill/Pill";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import PushStatus from "Common/Types/PushNotification/PushStatus";
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import Filter from "Common/UI/Components/ModelFilter/Filter";
|
||||
import DropdownUtil from "Common/UI/Utils/Dropdown";
|
||||
|
||||
const AlertPushLogs: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const columns: Columns<PushNotificationLog> = [
|
||||
{ field: { title: true }, title: "Title", type: FieldType.Text },
|
||||
{ field: { deviceType: true }, title: "Device Type", type: FieldType.Text, hideOnMobile: true },
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.DateTime },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Text, getElement: (item: PushNotificationLog): ReactElement => {
|
||||
if (item["status"]) {
|
||||
return (
|
||||
<Pill isMinimal={false} color={item["status"] === PushStatus.Success ? Green : Red} text={item["status"] as string} />
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
} },
|
||||
];
|
||||
|
||||
const filters: Array<Filter<PushNotificationLog>> = [
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.Date },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Dropdown, filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(PushStatus) },
|
||||
];
|
||||
|
||||
return (
|
||||
<ModelTable<PushNotificationLog>
|
||||
modelType={PushNotificationLog}
|
||||
id="alert-push-logs-table"
|
||||
name="Push Logs"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
showViewIdButton={true}
|
||||
userPreferencesKey="alert-push-logs-table"
|
||||
query={{ projectId: ProjectUtil.getCurrentProjectId()!, alertId: modelId }}
|
||||
selectMoreFields={{ statusMessage: true, body: true }}
|
||||
cardProps={{ title: "Push Logs", description: "Push notifications sent for this alert." }}
|
||||
noItemsMessage="No Push logs for this alert."
|
||||
showRefreshButton={true}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertPushLogs;
|
||||
@@ -131,6 +131,16 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Push Logs",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.ALERT_VIEW_PUSH_LOGS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Bell}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Alert Notes">
|
||||
|
||||
59
Dashboard/src/Pages/Incidents/View/NotificationLogsPush.tsx
Normal file
59
Dashboard/src/Pages/Incidents/View/NotificationLogsPush.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import Columns from "Common/UI/Components/ModelTable/Columns";
|
||||
import Pill from "Common/UI/Components/Pill/Pill";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import PushStatus from "Common/Types/PushNotification/PushStatus";
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import Filter from "Common/UI/Components/ModelFilter/Filter";
|
||||
import DropdownUtil from "Common/UI/Utils/Dropdown";
|
||||
|
||||
const IncidentPushLogs: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const columns: Columns<PushNotificationLog> = [
|
||||
{ field: { title: true }, title: "Title", type: FieldType.Text },
|
||||
{ field: { deviceType: true }, title: "Device Type", type: FieldType.Text, hideOnMobile: true },
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.DateTime },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Text, getElement: (item: PushNotificationLog): ReactElement => {
|
||||
if (item["status"]) {
|
||||
return (
|
||||
<Pill isMinimal={false} color={item["status"] === PushStatus.Success ? Green : Red} text={item["status"] as string} />
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
} },
|
||||
];
|
||||
|
||||
const filters: Array<Filter<PushNotificationLog>> = [
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.Date },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Dropdown, filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(PushStatus) },
|
||||
];
|
||||
|
||||
return (
|
||||
<ModelTable<PushNotificationLog>
|
||||
modelType={PushNotificationLog}
|
||||
id="incident-push-logs-table"
|
||||
name="Push Logs"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
showViewIdButton={true}
|
||||
userPreferencesKey="incident-push-logs-table"
|
||||
query={{ projectId: ProjectUtil.getCurrentProjectId()!, incidentId: modelId }}
|
||||
selectMoreFields={{ statusMessage: true, body: true }}
|
||||
cardProps={{ title: "Push Logs", description: "Push notifications sent for this incident." }}
|
||||
noItemsMessage="No Push logs for this incident."
|
||||
showRefreshButton={true}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default IncidentPushLogs;
|
||||
@@ -131,6 +131,16 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Push Logs",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.INCIDENT_VIEW_PUSH_LOGS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Bell}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Incident Notes">
|
||||
|
||||
104
Dashboard/src/Pages/Settings/PushLog.tsx
Normal file
104
Dashboard/src/Pages/Settings/PushLog.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import PageComponentProps from "../PageComponentProps";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import Filter from "Common/UI/Components/ModelFilter/Filter";
|
||||
import Columns from "Common/UI/Components/ModelTable/Columns";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import Pill from "Common/UI/Components/Pill/Pill";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import DropdownUtil from "Common/UI/Utils/Dropdown";
|
||||
import React, { Fragment, FunctionComponent, ReactElement, useState } from "react";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import PushStatus from "Common/Types/PushNotification/PushStatus";
|
||||
|
||||
const PushLogs: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [text, setText] = useState<string>("");
|
||||
const [title, setTitle] = useState<string>("");
|
||||
|
||||
const filters: Array<Filter<PushNotificationLog>> = [
|
||||
{ field: { _id: true }, title: "Log ID", type: FieldType.ObjectID },
|
||||
{ field: { title: true }, title: "Title", type: FieldType.Text },
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.Date },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Dropdown, filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(PushStatus) },
|
||||
];
|
||||
|
||||
const columns: Columns<PushNotificationLog> = [
|
||||
{ field: { title: true }, title: "Title", type: FieldType.Text },
|
||||
{ field: { deviceType: true }, title: "Device Type", type: FieldType.Text, hideOnMobile: true },
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.DateTime },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Text, getElement: (item: PushNotificationLog): ReactElement => {
|
||||
if (item["status"]) {
|
||||
return (
|
||||
<Pill isMinimal={false} color={item["status"] === PushStatus.Success ? Green : Red} text={item["status"] as string} />
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
} },
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<ModelTable<PushNotificationLog>
|
||||
modelType={PushNotificationLog}
|
||||
id="push-logs-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
name="Push Logs"
|
||||
userPreferencesKey="push-logs-table"
|
||||
query={{ projectId: ProjectUtil.getCurrentProjectId()! }}
|
||||
selectMoreFields={{ body: true, statusMessage: true }}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "View Body",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
icon: IconProp.List,
|
||||
onClick: async (item: PushNotificationLog, onCompleteAction: VoidFunction) => {
|
||||
setText(item["body"] as string);
|
||||
setTitle("Body");
|
||||
setShowModal(true);
|
||||
onCompleteAction();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "View Status Message",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
icon: IconProp.Error,
|
||||
onClick: async (item: PushNotificationLog, onCompleteAction: VoidFunction) => {
|
||||
setText(item["statusMessage"] as string);
|
||||
setTitle("Status Message");
|
||||
setShowModal(true);
|
||||
onCompleteAction();
|
||||
},
|
||||
},
|
||||
]}
|
||||
isViewable={false}
|
||||
cardProps={{ title: "Push Logs", description: "Logs of all the Push notifications sent by this project in the last 30 days." }}
|
||||
noItemsMessage={"Looks like no Push notifications were sent by this project in the last 30 days."}
|
||||
showRefreshButton={true}
|
||||
filters={filters}
|
||||
columns={columns}
|
||||
/>
|
||||
|
||||
{showModal && (
|
||||
<ConfirmModal
|
||||
title={title}
|
||||
description={text}
|
||||
onSubmit={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
submitButtonText="Close"
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default PushLogs;
|
||||
@@ -343,6 +343,15 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
},
|
||||
icon: IconProp.Email,
|
||||
},
|
||||
{
|
||||
link: {
|
||||
title: "Push Logs",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PUSH_LOGS] as Route,
|
||||
),
|
||||
},
|
||||
icon: IconProp.Bell,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -61,6 +61,16 @@ const AnnouncementSideMenu: FunctionComponent<ComponentProps> = (
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Push Logs",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.ANNOUNCEMENT_VIEW_PUSH_LOGS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Bell}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Advanced">
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import PageComponentProps from "../../../PageComponentProps";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import Columns from "Common/UI/Components/ModelTable/Columns";
|
||||
import Pill from "Common/UI/Components/Pill/Pill";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import PushStatus from "Common/Types/PushNotification/PushStatus";
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import Filter from "Common/UI/Components/ModelFilter/Filter";
|
||||
import DropdownUtil from "Common/UI/Utils/Dropdown";
|
||||
|
||||
const AnnouncementPushLogs: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const columns: Columns<PushNotificationLog> = [
|
||||
{ field: { title: true }, title: "Title", type: FieldType.Text },
|
||||
{ field: { deviceType: true }, title: "Device Type", type: FieldType.Text, hideOnMobile: true },
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.DateTime },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Text, getElement: (item: PushNotificationLog): ReactElement => {
|
||||
if (item["status"]) {
|
||||
return (
|
||||
<Pill isMinimal={false} color={item["status"] === PushStatus.Success ? Green : Red} text={item["status"] as string} />
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
} },
|
||||
];
|
||||
|
||||
const filters: Array<Filter<PushNotificationLog>> = [
|
||||
{ field: { createdAt: true }, title: "Sent at", type: FieldType.Date },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Dropdown, filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(PushStatus) },
|
||||
];
|
||||
|
||||
return (
|
||||
<ModelTable<PushNotificationLog>
|
||||
modelType={PushNotificationLog}
|
||||
id="announcement-push-logs-table"
|
||||
name="Push Logs"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
showViewIdButton={true}
|
||||
userPreferencesKey="announcement-push-logs-table"
|
||||
query={{ projectId: ProjectUtil.getCurrentProjectId()!, statusPageAnnouncementId: modelId }}
|
||||
selectMoreFields={{ statusMessage: true, body: true }}
|
||||
cardProps={{ title: "Push Logs", description: "Push notifications sent for this announcement." }}
|
||||
noItemsMessage="No Push logs for this announcement."
|
||||
showRefreshButton={true}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnnouncementPushLogs;
|
||||
@@ -61,6 +61,16 @@ const AnnouncementSideMenu: FunctionComponent<ComponentProps> = (
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Push Logs",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.ANNOUNCEMENT_VIEW_PUSH_LOGS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Bell}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Advanced">
|
||||
|
||||
@@ -42,6 +42,11 @@ const AlertViewNotificationLogsCall: LazyExoticComponent<
|
||||
> = lazy(() => {
|
||||
return import("../Pages/Alerts/View/NotificationLogsCall");
|
||||
});
|
||||
const AlertViewNotificationLogsPush: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/Alerts/View/NotificationLogsPush");
|
||||
});
|
||||
|
||||
const AlertsWorkspaceConnectionSlack: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
@@ -339,6 +344,17 @@ const AlertsRoutes: FunctionComponent<ComponentProps> = (
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(PageMap.ALERT_VIEW_PUSH_LOGS)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<AlertViewNotificationLogsPush
|
||||
{...props}
|
||||
pageRoute={RouteMap[PageMap.ALERT_VIEW_PUSH_LOGS] as Route}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</PageRoute>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -55,6 +55,11 @@ const IncidentViewNotificationLogsCall: LazyExoticComponent<
|
||||
> = lazy(() => {
|
||||
return import("../Pages/Incidents/View/NotificationLogsCall");
|
||||
});
|
||||
const IncidentViewNotificationLogsPush: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/Incidents/View/NotificationLogsPush");
|
||||
});
|
||||
const IncidentViewDelete: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
@@ -418,6 +423,17 @@ const IncidentsRoutes: FunctionComponent<ComponentProps> = (
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_VIEW_PUSH_LOGS)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<IncidentViewNotificationLogsPush
|
||||
{...props}
|
||||
pageRoute={RouteMap[PageMap.INCIDENT_VIEW_PUSH_LOGS] as Route}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</PageRoute>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -214,6 +214,10 @@ const SettingsEmailLog: LazyExoticComponent<FunctionComponent<ComponentProps>> =
|
||||
lazy(() => {
|
||||
return import("../Pages/Settings/EmailLog");
|
||||
});
|
||||
const SettingsPushLog: LazyExoticComponent<FunctionComponent<ComponentProps>> =
|
||||
lazy(() => {
|
||||
return import("../Pages/Settings/PushLog");
|
||||
});
|
||||
const SettingsNotifications: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
@@ -572,6 +576,18 @@ const SettingsRoutes: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_PUSH_LOGS)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<SettingsPushLog
|
||||
{...props}
|
||||
pageRoute={RouteMap[PageMap.SETTINGS_PUSH_LOGS] as Route}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(
|
||||
PageMap.SETTINGS_NOTIFICATION_SETTINGS,
|
||||
|
||||
@@ -187,6 +187,9 @@ const AnnouncementViewSmsLogs: LazyExoticComponent<FunctionComponent<ComponentPr
|
||||
const AnnouncementViewCallLogs: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy(() => {
|
||||
return import("../Pages/StatusPages/Announcements/View/NotificationLogsCall");
|
||||
});
|
||||
const AnnouncementViewPushLogs: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy(() => {
|
||||
return import("../Pages/StatusPages/Announcements/View/NotificationLogsPush");
|
||||
});
|
||||
const AnnouncementViewDelete: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy(() => {
|
||||
return import("../Pages/StatusPages/Announcements/View/Delete");
|
||||
});
|
||||
@@ -294,6 +297,17 @@ const StatusPagesRoutes: FunctionComponent<ComponentProps> = (
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(PageMap.ANNOUNCEMENT_VIEW_PUSH_LOGS)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<AnnouncementViewPushLogs
|
||||
{...props}
|
||||
pageRoute={RouteMap[PageMap.ANNOUNCEMENT_VIEW_PUSH_LOGS] as Route}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(PageMap.ANNOUNCEMENT_VIEW_DELETE)}
|
||||
element={
|
||||
|
||||
@@ -152,6 +152,11 @@ export function getSettingsBreadcrumbs(path: string): Array<Link> | undefined {
|
||||
"Settings",
|
||||
"Call Logs",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_PUSH_LOGS, [
|
||||
"Project",
|
||||
"Settings",
|
||||
"Push Logs",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_EMAIL_LOGS, [
|
||||
"Project",
|
||||
"Settings",
|
||||
|
||||
@@ -391,6 +391,12 @@ enum PageMap {
|
||||
SETTINGS_SMS_LOGS = "SETTINGS_SMS_LOGS",
|
||||
SETTINGS_EMAIL_LOGS = "SETTINGS_EMAIL_LOGS",
|
||||
SETTINGS_CALL_LOGS = "SETTINGS_CALL_LOGS",
|
||||
SETTINGS_PUSH_LOGS = "SETTINGS_PUSH_LOGS",
|
||||
|
||||
// Push Logs in resource views
|
||||
INCIDENT_VIEW_PUSH_LOGS = "INCIDENT_VIEW_PUSH_LOGS",
|
||||
ALERT_VIEW_PUSH_LOGS = "ALERT_VIEW_PUSH_LOGS",
|
||||
ANNOUNCEMENT_VIEW_PUSH_LOGS = "ANNOUNCEMENT_VIEW_PUSH_LOGS",
|
||||
}
|
||||
|
||||
export default PageMap;
|
||||
|
||||
@@ -119,6 +119,7 @@ export const StatusPagesRoutePath: Dictionary<string> = {
|
||||
[PageMap.ANNOUNCEMENT_VIEW_EMAIL_LOGS]: `announcements/${RouteParams.ModelID}/notification-logs/email`,
|
||||
[PageMap.ANNOUNCEMENT_VIEW_SMS_LOGS]: `announcements/${RouteParams.ModelID}/notification-logs/sms`,
|
||||
[PageMap.ANNOUNCEMENT_VIEW_CALL_LOGS]: `announcements/${RouteParams.ModelID}/notification-logs/call`,
|
||||
[PageMap.ANNOUNCEMENT_VIEW_PUSH_LOGS]: `announcements/${RouteParams.ModelID}/notification-logs/push`,
|
||||
[PageMap.ANNOUNCEMENT_VIEW_DELETE]: `announcements/${RouteParams.ModelID}/delete`,
|
||||
[PageMap.STATUS_PAGE_VIEW]: `${RouteParams.ModelID}`,
|
||||
[PageMap.STATUS_PAGE_VIEW_BRANDING]: `${RouteParams.ModelID}/branding`,
|
||||
@@ -165,6 +166,7 @@ export const IncidentsRoutePath: Dictionary<string> = {
|
||||
[PageMap.INCIDENT_VIEW_EMAIL_LOGS]: `${RouteParams.ModelID}/notification-logs/email`,
|
||||
[PageMap.INCIDENT_VIEW_SMS_LOGS]: `${RouteParams.ModelID}/notification-logs/sms`,
|
||||
[PageMap.INCIDENT_VIEW_CALL_LOGS]: `${RouteParams.ModelID}/notification-logs/call`,
|
||||
[PageMap.INCIDENT_VIEW_PUSH_LOGS]: `${RouteParams.ModelID}/notification-logs/push`,
|
||||
[PageMap.INCIDENT_VIEW_DELETE]: `${RouteParams.ModelID}/delete`,
|
||||
[PageMap.INCIDENT_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`,
|
||||
[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`,
|
||||
@@ -184,6 +186,7 @@ export const AlertsRoutePath: Dictionary<string> = {
|
||||
[PageMap.ALERT_VIEW_EMAIL_LOGS]: `${RouteParams.ModelID}/notification-logs/email`,
|
||||
[PageMap.ALERT_VIEW_SMS_LOGS]: `${RouteParams.ModelID}/notification-logs/sms`,
|
||||
[PageMap.ALERT_VIEW_CALL_LOGS]: `${RouteParams.ModelID}/notification-logs/call`,
|
||||
[PageMap.ALERT_VIEW_PUSH_LOGS]: `${RouteParams.ModelID}/notification-logs/push`,
|
||||
[PageMap.ALERT_VIEW_DELETE]: `${RouteParams.ModelID}/delete`,
|
||||
[PageMap.ALERT_VIEW_DESCRIPTION]: `${RouteParams.ModelID}/description`,
|
||||
[PageMap.ALERT_VIEW_ROOT_CAUSE]: `${RouteParams.ModelID}/root-cause`,
|
||||
@@ -216,6 +219,7 @@ export const SettingsRoutePath: Dictionary<string> = {
|
||||
[PageMap.SETTINGS_SMS_LOGS]: "sms-logs",
|
||||
[PageMap.SETTINGS_EMAIL_LOGS]: "email-logs",
|
||||
[PageMap.SETTINGS_CALL_LOGS]: "call-logs",
|
||||
[PageMap.SETTINGS_PUSH_LOGS]: "push-logs",
|
||||
[PageMap.SETTINGS_APIKEYS]: `api-keys`,
|
||||
[PageMap.SETTINGS_APIKEY_VIEW]: `api-keys/${RouteParams.ModelID}`,
|
||||
[PageMap.SETTINGS_TELEMETRY_INGESTION_KEYS]: `telemetry-ingestion-keys`,
|
||||
|
||||
Reference in New Issue
Block a user