mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Add Workspace Notification Summary API and Service
- Implemented WorkspaceNotificationSummaryAPI to handle notification summary requests. - Created WorkspaceNotificationSummaryService for business logic related to notification summaries. - Added enums for WorkspaceNotificationSummaryItem and WorkspaceNotificationSummaryType to define summary items and types. - Developed a cron job to send workspace notification summaries at regular intervals. - Enhanced error handling and logging for summary sending process.
This commit is contained in:
@@ -13,6 +13,7 @@ import SettingsProbes from "./Pages/Settings/Probes/Index";
|
||||
import SettingsAIAgents from "./Pages/Settings/AIAgents/Index";
|
||||
import SettingsLlmProviders from "./Pages/Settings/LlmProviders/Index";
|
||||
import SendEmail from "./Pages/SendEmail/Index";
|
||||
import MoreEmail from "./Pages/More/Email";
|
||||
import Users from "./Pages/Users/Index";
|
||||
import PageMap from "./Utils/PageMap";
|
||||
import RouteMap from "./Utils/RouteMap";
|
||||
@@ -155,6 +156,11 @@ const App: () => JSX.Element = () => {
|
||||
path={RouteMap[PageMap.SEND_EMAIL]?.toString() || ""}
|
||||
element={<SendEmail />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.MORE_EMAIL]?.toString() || ""}
|
||||
element={<MoreEmail />}
|
||||
/>
|
||||
</Routes>
|
||||
</MasterPage>
|
||||
);
|
||||
|
||||
@@ -2,10 +2,7 @@ import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import NavBar, {
|
||||
MoreMenuItem,
|
||||
NavItem,
|
||||
} from "Common/UI/Components/Navbar/NavBar";
|
||||
import NavBar, { NavItem } from "Common/UI/Components/Navbar/NavBar";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const DashboardNavbar: FunctionComponent = (): ReactElement => {
|
||||
@@ -29,23 +26,17 @@ const DashboardNavbar: FunctionComponent = (): ReactElement => {
|
||||
icon: IconProp.Settings,
|
||||
route: RouteUtil.populateRouteParams(RouteMap[PageMap.SETTINGS] as Route),
|
||||
},
|
||||
];
|
||||
|
||||
const moreMenuItems: MoreMenuItem[] = [
|
||||
{
|
||||
title: "Send Email",
|
||||
description: "Send announcement emails to all registered users.",
|
||||
icon: IconProp.Email,
|
||||
id: "more-nav-bar-item",
|
||||
title: "More",
|
||||
icon: IconProp.More,
|
||||
route: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SEND_EMAIL] as Route,
|
||||
),
|
||||
activeRoute: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SEND_EMAIL] as Route,
|
||||
RouteMap[PageMap.MORE_EMAIL] as Route,
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return <NavBar items={navItems} moreMenuItems={moreMenuItems} />;
|
||||
return <NavBar items={navItems} />;
|
||||
};
|
||||
|
||||
export default DashboardNavbar;
|
||||
|
||||
301
App/FeatureSet/AdminDashboard/src/Pages/More/Email.tsx
Normal file
301
App/FeatureSet/AdminDashboard/src/Pages/More/Email.tsx
Normal file
@@ -0,0 +1,301 @@
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import MoreSideMenu from "./SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
import Page from "Common/UI/Components/Page/Page";
|
||||
import Card from "Common/UI/Components/Card/Card";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import Alert, { AlertType } from "Common/UI/Components/Alerts/Alert";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import { APP_API_URL } from "Common/UI/Config";
|
||||
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import Modal from "Common/UI/Components/Modal/Modal";
|
||||
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
|
||||
const MoreEmail: FunctionComponent = (): ReactElement => {
|
||||
const [isSendingTest, setIsSendingTest] = useState<boolean>(false);
|
||||
const [isSendingAll, setIsSendingAll] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [success, setSuccess] = useState<string>("");
|
||||
const [showConfirmModal, setShowConfirmModal] = useState<boolean>(false);
|
||||
const [showTestModal, setShowTestModal] = useState<boolean>(false);
|
||||
const [testEmail, setTestEmail] = useState<string>("");
|
||||
const [testError, setTestError] = useState<string>("");
|
||||
const [testSuccess, setTestSuccess] = useState<string>("");
|
||||
const [pendingSubject, setPendingSubject] = useState<string>("");
|
||||
const [pendingMessage, setPendingMessage] = useState<string>("");
|
||||
const [currentFormValues, setCurrentFormValues] = useState<JSONObject>({
|
||||
subject: "",
|
||||
message: "",
|
||||
});
|
||||
|
||||
const sendTestEmail: () => Promise<void> = async (): Promise<void> => {
|
||||
setIsSendingTest(true);
|
||||
setTestError("");
|
||||
setTestSuccess("");
|
||||
|
||||
try {
|
||||
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.post({
|
||||
url: URL.fromString(APP_API_URL.toString()).addRoute(
|
||||
"/notification/broadcast-email/send-test",
|
||||
),
|
||||
data: {
|
||||
subject: pendingSubject,
|
||||
message: pendingMessage,
|
||||
testEmail: testEmail,
|
||||
},
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
if (response.isFailure()) {
|
||||
throw new Error("Failed to send test email.");
|
||||
}
|
||||
|
||||
setTestSuccess("Test email sent successfully. Please check your inbox.");
|
||||
} catch (err) {
|
||||
setTestError(API.getFriendlyMessage(err));
|
||||
} finally {
|
||||
setIsSendingTest(false);
|
||||
}
|
||||
};
|
||||
|
||||
const sendToAllUsers: () => Promise<void> = async (): Promise<void> => {
|
||||
setIsSendingAll(true);
|
||||
setError("");
|
||||
setSuccess("");
|
||||
|
||||
try {
|
||||
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.post({
|
||||
url: URL.fromString(APP_API_URL.toString()).addRoute(
|
||||
"/notification/broadcast-email/send-to-all-users",
|
||||
),
|
||||
data: {
|
||||
subject: pendingSubject,
|
||||
message: pendingMessage,
|
||||
},
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
if (response.isFailure()) {
|
||||
throw new Error("Failed to send emails.");
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
setSuccess(
|
||||
`Emails sent successfully. Total users: ${data["totalUsers"]}, Sent: ${data["sentCount"]}, Errors: ${data["errorCount"]}`,
|
||||
);
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
} finally {
|
||||
setIsSendingAll(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Send Announcement Email"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "More",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.MORE_EMAIL] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Send Email",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.MORE_EMAIL] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<MoreSideMenu />}
|
||||
>
|
||||
<Card
|
||||
title="Send Announcement Email"
|
||||
description="Compose an announcement email to send to all registered users. You can send a test email first to preview how it looks."
|
||||
>
|
||||
{success ? (
|
||||
<Alert
|
||||
type={AlertType.SUCCESS}
|
||||
title={success}
|
||||
className="mb-4"
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<BasicForm
|
||||
id="send-email-form"
|
||||
name="Send Announcement Email"
|
||||
isLoading={isSendingAll}
|
||||
error={error || ""}
|
||||
submitButtonText="Send to All Users"
|
||||
maxPrimaryButtonWidth={true}
|
||||
submitButtonStyleType={ButtonStyleType.DANGER}
|
||||
onChange={(values: JSONObject) => {
|
||||
setCurrentFormValues(values as JSONObject);
|
||||
}}
|
||||
initialValues={{
|
||||
subject: "",
|
||||
message: "",
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
subject: true,
|
||||
},
|
||||
title: "Subject",
|
||||
description: "The subject line of the announcement email.",
|
||||
placeholder: "Enter email subject",
|
||||
required: true,
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
message: true,
|
||||
},
|
||||
title: "Message",
|
||||
description:
|
||||
"The body of the announcement email. This will be displayed in a branded OneUptime email template.",
|
||||
placeholder: "Enter your announcement message here...",
|
||||
required: true,
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
},
|
||||
]}
|
||||
onSubmit={async (values: JSONObject) => {
|
||||
const subject: string = String(values["subject"] || "").trim();
|
||||
const message: string = String(values["message"] || "").trim();
|
||||
|
||||
if (!subject || !message) {
|
||||
setSuccess("");
|
||||
setError("Please fill in all fields.");
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingSubject(subject);
|
||||
setPendingMessage(message);
|
||||
setShowConfirmModal(true);
|
||||
}}
|
||||
footer={
|
||||
<div className="mt-3">
|
||||
<Button
|
||||
title="Send Test Email"
|
||||
buttonStyle={ButtonStyleType.LINK}
|
||||
onClick={() => {
|
||||
const subject: string = String(
|
||||
currentFormValues["subject"] || "",
|
||||
).trim();
|
||||
const message: string = String(
|
||||
currentFormValues["message"] || "",
|
||||
).trim();
|
||||
|
||||
if (!subject || !message) {
|
||||
setError(
|
||||
"Please fill in subject and message before sending a test.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setError("");
|
||||
setPendingSubject(subject);
|
||||
setPendingMessage(message);
|
||||
setTestEmail("");
|
||||
setTestError("");
|
||||
setTestSuccess("");
|
||||
setShowTestModal(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{showTestModal ? (
|
||||
<Modal
|
||||
title="Send Test Email"
|
||||
description="Enter an email address to send a test of this announcement."
|
||||
onClose={() => {
|
||||
setShowTestModal(false);
|
||||
}}
|
||||
submitButtonText="Send Test"
|
||||
isLoading={isSendingTest}
|
||||
onSubmit={() => {
|
||||
if (!testEmail.trim()) {
|
||||
setTestError("Please enter a test email address.");
|
||||
return;
|
||||
}
|
||||
sendTestEmail().catch(() => {});
|
||||
}}
|
||||
error={testError}
|
||||
>
|
||||
{testSuccess ? (
|
||||
<Alert
|
||||
type={AlertType.SUCCESS}
|
||||
title={testSuccess}
|
||||
className="mb-4"
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<label
|
||||
htmlFor="test-email-input"
|
||||
className="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Test Email Address
|
||||
</label>
|
||||
<input
|
||||
id="test-email-input"
|
||||
type="email"
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm border p-2"
|
||||
placeholder="test@example.com"
|
||||
value={testEmail}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTestEmail(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{showConfirmModal ? (
|
||||
<ConfirmModal
|
||||
title="Confirm Send to All Users"
|
||||
description="Are you sure you want to send this announcement email to all registered users? This action cannot be undone."
|
||||
submitButtonText="Yes, Send to All Users"
|
||||
onSubmit={async () => {
|
||||
setShowConfirmModal(false);
|
||||
await sendToAllUsers();
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowConfirmModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default MoreEmail;
|
||||
28
App/FeatureSet/AdminDashboard/src/Pages/More/SideMenu.tsx
Normal file
28
App/FeatureSet/AdminDashboard/src/Pages/More/SideMenu.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import SideMenu from "Common/UI/Components/SideMenu/SideMenu";
|
||||
import SideMenuItem from "Common/UI/Components/SideMenu/SideMenuItem";
|
||||
import SideMenuSection from "Common/UI/Components/SideMenu/SideMenuSection";
|
||||
import React, { ReactElement } from "react";
|
||||
|
||||
const MoreSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
return (
|
||||
<SideMenu>
|
||||
<SideMenuSection title="Communication">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Send Email",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.MORE_EMAIL] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Email}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
</SideMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default MoreSideMenu;
|
||||
@@ -25,6 +25,8 @@ enum PageMap {
|
||||
SETTINGS_DATA_RETENTION = "SETTINGS_DATA_RETENTION",
|
||||
|
||||
SEND_EMAIL = "SEND_EMAIL",
|
||||
|
||||
MORE_EMAIL = "MORE_EMAIL",
|
||||
}
|
||||
|
||||
export default PageMap;
|
||||
|
||||
@@ -41,6 +41,8 @@ const RouteMap: Dictionary<Route> = {
|
||||
),
|
||||
|
||||
[PageMap.SEND_EMAIL]: new Route(`/admin/send-email`),
|
||||
|
||||
[PageMap.MORE_EMAIL]: new Route(`/admin/more/email`),
|
||||
};
|
||||
|
||||
export class RouteUtil {
|
||||
|
||||
@@ -28,6 +28,7 @@ import MonitorAPI from "Common/Server/API/MonitorAPI";
|
||||
import ShortLinkAPI from "Common/Server/API/ShortLinkAPI";
|
||||
import StatusPageAPI from "Common/Server/API/StatusPageAPI";
|
||||
import WorkspaceNotificationRuleAPI from "Common/Server/API/WorkspaceNotificationRuleAPI";
|
||||
import WorkspaceNotificationSummaryAPI from "Common/Server/API/WorkspaceNotificationSummaryAPI";
|
||||
import StatusPageDomainAPI from "Common/Server/API/StatusPageDomainAPI";
|
||||
import StatusPageSubscriberAPI from "Common/Server/API/StatusPageSubscriberAPI";
|
||||
import UserCallAPI from "Common/Server/API/UserCallAPI";
|
||||
@@ -2057,6 +2058,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new WorkspaceNotificationRuleAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new WorkspaceNotificationSummaryAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new FileAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import React, {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
} from "react";
|
||||
import WorkspaceType, {
|
||||
getWorkspaceTypeDisplayName,
|
||||
} from "Common/Types/Workspace/WorkspaceType";
|
||||
import WorkspaceNotificationSummary from "Common/Models/DatabaseModels/WorkspaceNotificationSummary";
|
||||
import WorkspaceNotificationSummaryType from "Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
|
||||
import WorkspaceNotificationSummaryItem from "Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem";
|
||||
import API from "Common/Utils/API";
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
import { ErrorFunction } from "Common/Types/FunctionTypes";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import EmptyResponseData from "Common/Types/API/EmptyResponse";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { APP_API_URL } from "Common/UI/Config";
|
||||
import { ShowAs } from "Common/UI/Components/ModelTable/BaseModelTable";
|
||||
import { ModalWidth } from "Common/UI/Components/Modal/Modal";
|
||||
import RecurringFieldElement from "Common/UI/Components/Events/RecurringFieldElement";
|
||||
import RecurringViewElement from "Common/UI/Components/Events/RecurringViewElement";
|
||||
import Recurring from "Common/Types/Events/Recurring";
|
||||
import FormValues from "Common/UI/Components/Forms/Types/FormValues";
|
||||
import { CustomElementProps } from "Common/UI/Components/Forms/Types/Field";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
|
||||
export interface ComponentProps {
|
||||
workspaceType: WorkspaceType;
|
||||
summaryType: WorkspaceNotificationSummaryType;
|
||||
}
|
||||
|
||||
const WorkspaceSummaryTable: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [showTestModal, setShowTestModal] = React.useState<boolean>(false);
|
||||
const [isTestLoading, setIsTestLoading] = React.useState<boolean>(false);
|
||||
const [testError, setTestError] = React.useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [testSummary, setTestSummary] = React.useState<
|
||||
WorkspaceNotificationSummary | undefined
|
||||
>(undefined);
|
||||
const [showTestSuccessModal, setShowTestSuccessModal] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
type TestSummaryFunction = (summaryId: ObjectID) => Promise<void>;
|
||||
|
||||
const testSummaryFn: TestSummaryFunction = async (
|
||||
summaryId: ObjectID,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
setIsTestLoading(true);
|
||||
setTestError(undefined);
|
||||
|
||||
const response: HTTPResponse<EmptyResponseData> | HTTPErrorResponse =
|
||||
await API.get({
|
||||
url: URL.fromString(APP_API_URL.toString()).addRoute(
|
||||
`/workspace-notification-summary/test/${summaryId.toString()}`,
|
||||
),
|
||||
data: {},
|
||||
});
|
||||
|
||||
if (response.isSuccess()) {
|
||||
setIsTestLoading(false);
|
||||
setShowTestModal(false);
|
||||
setShowTestSuccessModal(true);
|
||||
}
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
setIsTestLoading(false);
|
||||
} catch (err) {
|
||||
setTestError(API.getFriendlyErrorMessage(err as Exception));
|
||||
setIsTestLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const allSummaryItems: Array<WorkspaceNotificationSummaryItem> =
|
||||
Object.values(WorkspaceNotificationSummaryItem);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ModelTable<WorkspaceNotificationSummary>
|
||||
modelType={WorkspaceNotificationSummary}
|
||||
query={{
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
summaryType: props.summaryType,
|
||||
workspaceType: props.workspaceType,
|
||||
}}
|
||||
userPreferencesKey={`workspace-summary-table-${props.summaryType}-${props.workspaceType}`}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "Test Summary",
|
||||
buttonStyleType: ButtonStyleType.OUTLINE,
|
||||
icon: IconProp.Play,
|
||||
onClick: async (
|
||||
item: WorkspaceNotificationSummary,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction,
|
||||
) => {
|
||||
try {
|
||||
setTestSummary(item);
|
||||
setShowTestModal(true);
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
singularName={`${props.summaryType} Summary`}
|
||||
pluralName={`${props.summaryType} Summaries`}
|
||||
id={`workspace-summary-table-${props.summaryType}`}
|
||||
name={`Settings > ${props.summaryType} Workspace Summaries`}
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
createEditModalWidth={ModalWidth.Large}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: `${props.summaryType} - ${getWorkspaceTypeDisplayName(props.workspaceType)} Summary`,
|
||||
description: `Configure recurring ${props.summaryType.toLowerCase()} summary reports to be sent to ${getWorkspaceTypeDisplayName(props.workspaceType)} channels.`,
|
||||
}}
|
||||
showAs={ShowAs.List}
|
||||
noItemsMessage={"No summary rules found."}
|
||||
onBeforeCreate={(values: WorkspaceNotificationSummary) => {
|
||||
values.summaryType = props.summaryType;
|
||||
values.projectId = ProjectUtil.getCurrentProjectId()!;
|
||||
values.workspaceType = props.workspaceType;
|
||||
|
||||
// Set initial nextSendAt based on recurring interval
|
||||
if (values.recurringInterval) {
|
||||
const recurring: Recurring = Recurring.fromJSON(
|
||||
values.recurringInterval,
|
||||
);
|
||||
values.nextSendAt = Recurring.getNextDateInterval(
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
recurring,
|
||||
);
|
||||
}
|
||||
|
||||
// Parse channel names from comma-separated string
|
||||
if (
|
||||
values.channelNames &&
|
||||
typeof values.channelNames === "string"
|
||||
) {
|
||||
values.channelNames = (values.channelNames as unknown as string)
|
||||
.split(",")
|
||||
.map((name: string) => {
|
||||
return name.trim();
|
||||
})
|
||||
.filter((name: string) => {
|
||||
return name.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure summaryItems is an array
|
||||
if (!values.summaryItems) {
|
||||
values.summaryItems = allSummaryItems;
|
||||
}
|
||||
|
||||
if (!values.isEnabled) {
|
||||
values.isEnabled = true;
|
||||
}
|
||||
|
||||
return Promise.resolve(values);
|
||||
}}
|
||||
onBeforeEdit={(values: WorkspaceNotificationSummary) => {
|
||||
// Parse channel names from comma-separated string
|
||||
if (
|
||||
values.channelNames &&
|
||||
typeof values.channelNames === "string"
|
||||
) {
|
||||
values.channelNames = (values.channelNames as unknown as string)
|
||||
.split(",")
|
||||
.map((name: string) => {
|
||||
return name.trim();
|
||||
})
|
||||
.filter((name: string) => {
|
||||
return name.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Recalculate nextSendAt if interval changed
|
||||
if (values.recurringInterval) {
|
||||
const recurring: Recurring = Recurring.fromJSON(
|
||||
values.recurringInterval,
|
||||
);
|
||||
values.nextSendAt = Recurring.getNextDateInterval(
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
recurring,
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve(values);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Summary Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
stepId: "basic",
|
||||
placeholder: "Weekly Incident Summary",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
stepId: "basic",
|
||||
title: "Description",
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: false,
|
||||
placeholder:
|
||||
"Weekly summary of incidents sent to the #ops channel.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
channelNames: true,
|
||||
},
|
||||
stepId: "basic",
|
||||
title: "Channel Names",
|
||||
description:
|
||||
"Comma-separated list of channel names to post the summary to (e.g., #incidents, #ops-summary).",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "#incidents-summary",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isEnabled: true,
|
||||
},
|
||||
stepId: "basic",
|
||||
title: "Enabled",
|
||||
description: "Enable or disable this recurring summary.",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
recurringInterval: true,
|
||||
},
|
||||
title: "Recurring Interval",
|
||||
description: "How often should this summary be sent?",
|
||||
fieldType: FormFieldSchemaType.CustomComponent,
|
||||
required: true,
|
||||
stepId: "schedule",
|
||||
getCustomElement: (
|
||||
value: FormValues<WorkspaceNotificationSummary>,
|
||||
props: CustomElementProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<RecurringFieldElement
|
||||
error={props.error}
|
||||
onChange={(recurring: Recurring) => {
|
||||
props.onChange(recurring);
|
||||
}}
|
||||
initialValue={
|
||||
value.recurringInterval
|
||||
? Recurring.fromJSON(value.recurringInterval)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
numberOfDaysOfData: true,
|
||||
},
|
||||
title: "Number of Days of Data",
|
||||
description:
|
||||
"How many days of historical data should be included in each summary?",
|
||||
fieldType: FormFieldSchemaType.Number,
|
||||
required: true,
|
||||
stepId: "schedule",
|
||||
placeholder: "7",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
summaryItems: true,
|
||||
},
|
||||
title: "Items to Include",
|
||||
description:
|
||||
"Select which items to include in the summary report.",
|
||||
fieldType: FormFieldSchemaType.MultiSelectDropdown,
|
||||
required: true,
|
||||
stepId: "content",
|
||||
dropdownOptions: allSummaryItems.map(
|
||||
(item: WorkspaceNotificationSummaryItem) => {
|
||||
return {
|
||||
label: item,
|
||||
value: item,
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
]}
|
||||
formSteps={[
|
||||
{
|
||||
title: "Basic",
|
||||
id: "basic",
|
||||
},
|
||||
{
|
||||
title: "Schedule",
|
||||
id: "schedule",
|
||||
},
|
||||
{
|
||||
title: "Content",
|
||||
id: "content",
|
||||
},
|
||||
]}
|
||||
showRefreshButton={true}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
type: FieldType.Text,
|
||||
title: "Summary Name",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isEnabled: true,
|
||||
},
|
||||
type: FieldType.Boolean,
|
||||
title: "Enabled",
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Summary Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
noValueMessage: "-",
|
||||
title: "Description",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isEnabled: true,
|
||||
},
|
||||
title: "Enabled",
|
||||
type: FieldType.Boolean,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
recurringInterval: true,
|
||||
},
|
||||
title: "Recurring Interval",
|
||||
type: FieldType.Element,
|
||||
getElement: (
|
||||
value: WorkspaceNotificationSummary,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<RecurringViewElement
|
||||
value={value.recurringInterval as Recurring}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
numberOfDaysOfData: true,
|
||||
},
|
||||
title: "Days of Data",
|
||||
type: FieldType.Number,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastSentAt: true,
|
||||
},
|
||||
noValueMessage: "Never",
|
||||
title: "Last Sent",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showTestModal && testSummary ? (
|
||||
<ConfirmModal
|
||||
title={`Test Summary`}
|
||||
error={testError}
|
||||
description={`Test the summary "${testSummary.name}" by sending it to ${getWorkspaceTypeDisplayName(props.workspaceType)} now.`}
|
||||
submitButtonText={"Send Test Summary"}
|
||||
onClose={() => {
|
||||
setShowTestModal(false);
|
||||
setTestSummary(undefined);
|
||||
setTestError(undefined);
|
||||
}}
|
||||
isLoading={isTestLoading}
|
||||
onSubmit={async () => {
|
||||
if (!testSummary.id) {
|
||||
return;
|
||||
}
|
||||
await testSummaryFn(testSummary.id!);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{showTestSuccessModal ? (
|
||||
<ConfirmModal
|
||||
title={
|
||||
testError ? `Test Failed` : `Test Summary Sent Successfully`
|
||||
}
|
||||
error={testError}
|
||||
description={`Test summary sent successfully. You should now see the summary in ${getWorkspaceTypeDisplayName(props.workspaceType)}.`}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
submitButtonText={"Close"}
|
||||
onSubmit={async () => {
|
||||
setShowTestSuccessModal(false);
|
||||
setTestSummary(undefined);
|
||||
setShowTestModal(false);
|
||||
setTestError("");
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkspaceSummaryTable;
|
||||
@@ -1,5 +1,7 @@
|
||||
import WorkspaceType from "Common/Types/Workspace/WorkspaceType";
|
||||
import WorkspaceNotificationRuleTable from "../../Components/Workspace/WorkspaceNotificationRulesTable";
|
||||
import WorkspaceSummaryTable from "../../Components/Workspace/WorkspaceSummaryTable";
|
||||
import WorkspaceNotificationSummaryType from "Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
|
||||
import PageComponentProps from "../PageComponentProps";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import NotificationRuleEventType from "Common/Types/Workspace/NotificationRules/EventType";
|
||||
@@ -70,6 +72,21 @@ const AlertsTeamsPage: FunctionComponent<
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Summary",
|
||||
children: (
|
||||
<>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.MicrosoftTeams}
|
||||
summaryType={WorkspaceNotificationSummaryType.Alert}
|
||||
/>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.MicrosoftTeams}
|
||||
summaryType={WorkspaceNotificationSummaryType.AlertEpisode}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import WorkspaceType from "Common/Types/Workspace/WorkspaceType";
|
||||
import WorkspaceNotificationRuleTable from "../../Components/Workspace/WorkspaceNotificationRulesTable";
|
||||
import WorkspaceSummaryTable from "../../Components/Workspace/WorkspaceSummaryTable";
|
||||
import WorkspaceNotificationSummaryType from "Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
|
||||
import PageComponentProps from "../PageComponentProps";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import NotificationRuleEventType from "Common/Types/Workspace/NotificationRules/EventType";
|
||||
@@ -100,6 +102,21 @@ When you react with a pin emoji, OneUptime will automatically save the message c
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Summary",
|
||||
children: (
|
||||
<>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.Slack}
|
||||
summaryType={WorkspaceNotificationSummaryType.Alert}
|
||||
/>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.Slack}
|
||||
summaryType={WorkspaceNotificationSummaryType.AlertEpisode}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import WorkspaceType from "Common/Types/Workspace/WorkspaceType";
|
||||
import WorkspaceNotificationRuleTable from "../../Components/Workspace/WorkspaceNotificationRulesTable";
|
||||
import WorkspaceSummaryTable from "../../Components/Workspace/WorkspaceSummaryTable";
|
||||
import WorkspaceNotificationSummaryType from "Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
|
||||
import PageComponentProps from "../PageComponentProps";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import NotificationRuleEventType from "Common/Types/Workspace/NotificationRules/EventType";
|
||||
@@ -70,6 +72,21 @@ const IncidentsTeamsPage: FunctionComponent<
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Summary",
|
||||
children: (
|
||||
<>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.MicrosoftTeams}
|
||||
summaryType={WorkspaceNotificationSummaryType.Incident}
|
||||
/>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.MicrosoftTeams}
|
||||
summaryType={WorkspaceNotificationSummaryType.IncidentEpisode}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import WorkspaceType from "Common/Types/Workspace/WorkspaceType";
|
||||
import WorkspaceNotificationRuleTable from "../../Components/Workspace/WorkspaceNotificationRulesTable";
|
||||
import WorkspaceSummaryTable from "../../Components/Workspace/WorkspaceSummaryTable";
|
||||
import WorkspaceNotificationSummaryType from "Common/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
|
||||
import PageComponentProps from "../PageComponentProps";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import NotificationRuleEventType from "Common/Types/Workspace/NotificationRules/EventType";
|
||||
@@ -101,6 +103,21 @@ When you react with a pin emoji, OneUptime will automatically save the message c
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Summary",
|
||||
children: (
|
||||
<>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.Slack}
|
||||
summaryType={WorkspaceNotificationSummaryType.Incident}
|
||||
/>
|
||||
<WorkspaceSummaryTable
|
||||
workspaceType={WorkspaceType.Slack}
|
||||
summaryType={WorkspaceNotificationSummaryType.IncidentEpisode}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -232,6 +232,7 @@ import WorkspaceUserAuthToken from "./WorkspaceUserAuthToken";
|
||||
import WorkspaceProjectAuthToken from "./WorkspaceProjectAuthToken";
|
||||
import WorkspaceSetting from "./WorkspaceSetting";
|
||||
import WorkspaceNotificationRule from "./WorkspaceNotificationRule";
|
||||
import WorkspaceNotificationSummary from "./WorkspaceNotificationSummary";
|
||||
|
||||
import OnCallDutyPolicyUserOverride from "./OnCallDutyPolicyUserOverride";
|
||||
import MonitorFeed from "./MonitorFeed";
|
||||
@@ -490,6 +491,7 @@ const AllModelTypes: Array<{
|
||||
|
||||
WorkspaceSetting,
|
||||
WorkspaceNotificationRule,
|
||||
WorkspaceNotificationSummary,
|
||||
|
||||
MonitorFeed,
|
||||
|
||||
|
||||
784
Common/Models/DatabaseModels/WorkspaceNotificationSummary.ts
Normal file
784
Common/Models/DatabaseModels/WorkspaceNotificationSummary.ts
Normal file
@@ -0,0 +1,784 @@
|
||||
import Project from "./Project";
|
||||
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 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 { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
import WorkspaceType from "../../Types/Workspace/WorkspaceType";
|
||||
import WorkspaceNotificationSummaryType from "../../Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType";
|
||||
import WorkspaceNotificationSummaryItem from "../../Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem";
|
||||
import Permission from "../../Types/Permission";
|
||||
import TableBillingAccessControl from "../../Types/Database/AccessControl/TableBillingAccessControl";
|
||||
import { PlanType } from "../../Types/Billing/SubscriptionPlan";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import Recurring from "../../Types/Events/Recurring";
|
||||
import NotificationRuleCondition from "../../Types/Workspace/NotificationRules/NotificationRuleCondition";
|
||||
import FilterCondition from "../../Types/Filter/FilterCondition";
|
||||
|
||||
@EnableDocumentation()
|
||||
@TenantColumn("projectId")
|
||||
@TableAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.DeleteWorkspaceNotificationSummary,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableBillingAccessControl({
|
||||
create: PlanType.Growth,
|
||||
read: PlanType.Growth,
|
||||
update: PlanType.Growth,
|
||||
delete: PlanType.Growth,
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/workspace-notification-summary"))
|
||||
@Entity({
|
||||
name: "WorkspaceNotificationSummary",
|
||||
})
|
||||
@TableMetadata({
|
||||
tableName: "WorkspaceNotificationSummary",
|
||||
singularName: "Workspace Notification Summary",
|
||||
pluralName: "Workspace Notification Summaries",
|
||||
icon: IconProp.ChartBar,
|
||||
tableDescription:
|
||||
"Recurring summary reports for incidents and alerts sent to Slack or Microsoft Teams",
|
||||
})
|
||||
class WorkspaceNotificationSummary extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
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: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
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: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Summary Name",
|
||||
description: "Name of the Summary Rule",
|
||||
required: true,
|
||||
unique: false,
|
||||
type: TableColumnType.LongText,
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
unique: false,
|
||||
nullable: false,
|
||||
})
|
||||
public name?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Summary Description",
|
||||
description: "Description of the Summary Rule",
|
||||
required: false,
|
||||
unique: false,
|
||||
type: TableColumnType.LongText,
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
unique: false,
|
||||
nullable: true,
|
||||
})
|
||||
public description?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Workspace Type",
|
||||
description: "Type of Workspace - Slack, Microsoft Teams, etc.",
|
||||
required: true,
|
||||
unique: false,
|
||||
type: TableColumnType.LongText,
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
unique: false,
|
||||
nullable: false,
|
||||
})
|
||||
public workspaceType?: WorkspaceType = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Summary Type",
|
||||
description:
|
||||
"Type of summary - Incident, Alert, Incident Episode, or Alert Episode",
|
||||
required: true,
|
||||
unique: false,
|
||||
type: TableColumnType.ShortText,
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
unique: false,
|
||||
nullable: false,
|
||||
})
|
||||
public summaryType?: WorkspaceNotificationSummaryType = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.JSON,
|
||||
title: "Recurring Interval",
|
||||
description: "How often should the summary be sent?",
|
||||
required: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.JSON,
|
||||
nullable: false,
|
||||
transformer: Recurring.getDatabaseTransformer(),
|
||||
})
|
||||
public recurringInterval?: Recurring = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.Number,
|
||||
title: "Number of Days of Data",
|
||||
description: "How many days of data to include in the summary",
|
||||
required: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Number,
|
||||
nullable: false,
|
||||
default: 7,
|
||||
})
|
||||
public numberOfDaysOfData?: number = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.JSON,
|
||||
title: "Channel Names",
|
||||
description: "List of channel names to post the summary to",
|
||||
required: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.JSON,
|
||||
nullable: false,
|
||||
})
|
||||
public channelNames?: Array<string> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Team Name",
|
||||
description: "Microsoft Teams team name (only for Microsoft Teams)",
|
||||
required: false,
|
||||
unique: false,
|
||||
type: TableColumnType.LongText,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
unique: false,
|
||||
nullable: true,
|
||||
})
|
||||
public teamName?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.JSON,
|
||||
title: "Summary Items",
|
||||
description: "Checklist of items to include in the summary",
|
||||
required: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.JSON,
|
||||
nullable: false,
|
||||
})
|
||||
public summaryItems?: Array<WorkspaceNotificationSummaryItem> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.JSON,
|
||||
title: "Filters",
|
||||
description: "Filter conditions for which items to include in the summary",
|
||||
required: false,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.JSON,
|
||||
nullable: true,
|
||||
})
|
||||
public filters?: Array<NotificationRuleCondition> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
title: "Filter Condition",
|
||||
description: "How to combine filters - Any or All",
|
||||
required: false,
|
||||
unique: false,
|
||||
type: TableColumnType.ShortText,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
unique: false,
|
||||
nullable: true,
|
||||
})
|
||||
public filterCondition?: FilterCondition = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.Date,
|
||||
title: "Next Send At",
|
||||
description: "When the next summary should be sent",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
})
|
||||
public nextSendAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.Date,
|
||||
title: "Last Sent At",
|
||||
description: "When the last summary was sent",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
})
|
||||
public lastSentAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.Boolean,
|
||||
title: "Enabled",
|
||||
description: "Is this summary rule enabled?",
|
||||
required: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Boolean,
|
||||
nullable: false,
|
||||
default: true,
|
||||
})
|
||||
public isEnabled?: boolean = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "createdByUserId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: User,
|
||||
title: "Created by User",
|
||||
description:
|
||||
"Relation to User who created this object (if this object was created by a User)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "SET NULL",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "createdByUserId" })
|
||||
public createdByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Created by User ID",
|
||||
description:
|
||||
"User ID who created this object (if this object was created by a User)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public createdByUserId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@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: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateWorkspaceNotificationSummary,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadWorkspaceNotificationSummary,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditWorkspaceNotificationSummary,
|
||||
],
|
||||
})
|
||||
@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;
|
||||
}
|
||||
|
||||
export default WorkspaceNotificationSummary;
|
||||
55
Common/Server/API/WorkspaceNotificationSummaryAPI.ts
Normal file
55
Common/Server/API/WorkspaceNotificationSummaryAPI.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import UserMiddleware from "../Middleware/UserAuthorization";
|
||||
import WorkspaceNotificationSummaryService, {
|
||||
Service as WorkspaceNotificationSummaryServiceType,
|
||||
} from "../Services/WorkspaceNotificationSummaryService";
|
||||
import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
NextFunction,
|
||||
} from "../Utils/Express";
|
||||
import Response from "../Utils/Response";
|
||||
import BaseAPI from "./BaseAPI";
|
||||
import CommonAPI from "./CommonAPI";
|
||||
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
||||
import WorkspaceNotificationSummary from "../../Models/DatabaseModels/WorkspaceNotificationSummary";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
|
||||
export default class WorkspaceNotificationSummaryAPI extends BaseAPI<
|
||||
WorkspaceNotificationSummary,
|
||||
WorkspaceNotificationSummaryServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(
|
||||
WorkspaceNotificationSummary,
|
||||
WorkspaceNotificationSummaryService,
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
`${new this.entityType().getCrudApiPath()?.toString()}/test/:workspaceNotificationSummaryId`,
|
||||
UserMiddleware.getUserMiddleware,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
try {
|
||||
const databaseProps: DatabaseCommonInteractionProps =
|
||||
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
||||
|
||||
await this.service.testSummary({
|
||||
summaryId: new ObjectID(
|
||||
req.params["workspaceNotificationSummaryId"] as string,
|
||||
),
|
||||
props: databaseProps,
|
||||
projectId: databaseProps.tenantId!,
|
||||
testByUserId: databaseProps.userId!,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -198,6 +198,7 @@ import WorkspaceUserAuthTokenService from "./WorkspaceUserAuthTokenService";
|
||||
import WorkspaceSettingService from "./WorkspaceSettingService";
|
||||
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
||||
import WorkspaceNotificationLogService from "./WorkspaceNotificationLogService";
|
||||
import WorkspaceNotificationSummaryService from "./WorkspaceNotificationSummaryService";
|
||||
import OnCallDutyPolicyUserOverrideService from "./OnCallDutyPolicyUserOverrideService";
|
||||
|
||||
import MonitorLogService from "./MonitorLogService";
|
||||
@@ -416,6 +417,7 @@ const services: Array<BaseService> = [
|
||||
WorkspaceSettingService,
|
||||
WorkspaceNotificationRuleService,
|
||||
WorkspaceNotificationLogService,
|
||||
WorkspaceNotificationSummaryService,
|
||||
|
||||
ProjectSCIMLogService,
|
||||
StatusPageSCIMLogService,
|
||||
|
||||
1147
Common/Server/Services/WorkspaceNotificationSummaryService.ts
Normal file
1147
Common/Server/Services/WorkspaceNotificationSummaryService.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -771,6 +771,11 @@ enum Permission {
|
||||
EditWorkspaceNotificationRule = "EditWorkspaceNotificationRule",
|
||||
ReadWorkspaceNotificationRule = "ReadWorkspaceNotificationRule",
|
||||
|
||||
CreateWorkspaceNotificationSummary = "CreateWorkspaceNotificationSummary",
|
||||
DeleteWorkspaceNotificationSummary = "DeleteWorkspaceNotificationSummary",
|
||||
EditWorkspaceNotificationSummary = "EditWorkspaceNotificationSummary",
|
||||
ReadWorkspaceNotificationSummary = "ReadWorkspaceNotificationSummary",
|
||||
|
||||
// Alert Episode Permissions
|
||||
CreateAlertEpisode = "CreateAlertEpisode",
|
||||
DeleteAlertEpisode = "DeleteAlertEpisode",
|
||||
@@ -1389,6 +1394,43 @@ export class PermissionHelper {
|
||||
group: PermissionGroup.Settings,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateWorkspaceNotificationSummary,
|
||||
title: "Create Workspace Notification Summary",
|
||||
description:
|
||||
"This permission can create workspace notification summaries for this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
group: PermissionGroup.Settings,
|
||||
},
|
||||
{
|
||||
permission: Permission.DeleteWorkspaceNotificationSummary,
|
||||
title: "Delete Workspace Notification Summary",
|
||||
description:
|
||||
"This permission can delete workspace notification summaries of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
group: PermissionGroup.Settings,
|
||||
},
|
||||
{
|
||||
permission: Permission.EditWorkspaceNotificationSummary,
|
||||
title: "Edit Workspace Notification Summary",
|
||||
description:
|
||||
"This permission can edit workspace notification summaries of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
group: PermissionGroup.Settings,
|
||||
},
|
||||
{
|
||||
permission: Permission.ReadWorkspaceNotificationSummary,
|
||||
title: "Read Workspace Notification Summary",
|
||||
description:
|
||||
"This permission can read workspace notification summaries of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
group: PermissionGroup.Settings,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateIncidentStateTimeline,
|
||||
title: "Create Incident State Timeline",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
enum WorkspaceNotificationSummaryItem {
|
||||
TotalCount = "Total Count",
|
||||
ListWithLinks = "List with Links",
|
||||
WhoAcknowledged = "Who Acknowledged",
|
||||
WhoResolved = "Who Resolved",
|
||||
TimeToAcknowledge = "Time to Acknowledge",
|
||||
TimeToResolve = "Time to Resolve",
|
||||
ResourcesAffected = "Resources Affected",
|
||||
SeverityBreakdown = "Severity Breakdown",
|
||||
StateBreakdown = "State Breakdown",
|
||||
}
|
||||
|
||||
export default WorkspaceNotificationSummaryItem;
|
||||
@@ -0,0 +1,8 @@
|
||||
enum WorkspaceNotificationSummaryType {
|
||||
Incident = "Incident",
|
||||
Alert = "Alert",
|
||||
IncidentEpisode = "Incident Episode",
|
||||
AlertEpisode = "Alert Episode",
|
||||
}
|
||||
|
||||
export default WorkspaceNotificationSummaryType;
|
||||
66
Worker/Jobs/WorkspaceNotificationSummary/SendSummary.ts
Normal file
66
Worker/Jobs/WorkspaceNotificationSummary/SendSummary.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Recurring from "Common/Types/Events/Recurring";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import WorkspaceNotificationSummaryService from "Common/Server/Services/WorkspaceNotificationSummaryService";
|
||||
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import WorkspaceNotificationSummary from "Common/Models/DatabaseModels/WorkspaceNotificationSummary";
|
||||
|
||||
RunCron(
|
||||
"WorkspaceNotificationSummary:SendSummary",
|
||||
{
|
||||
schedule: EVERY_MINUTE,
|
||||
runOnStartup: false,
|
||||
},
|
||||
async () => {
|
||||
const summariesToSend: Array<WorkspaceNotificationSummary> =
|
||||
await WorkspaceNotificationSummaryService.findAllBy({
|
||||
query: {
|
||||
isEnabled: true,
|
||||
nextSendAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
nextSendAt: true,
|
||||
recurringInterval: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const summary of summariesToSend) {
|
||||
try {
|
||||
// Update nextSendAt first to prevent double-sends
|
||||
const nextSendAt: Date = Recurring.getNextDate(
|
||||
summary.nextSendAt!,
|
||||
summary.recurringInterval!,
|
||||
);
|
||||
|
||||
await WorkspaceNotificationSummaryService.updateOneById({
|
||||
id: summary.id!,
|
||||
data: {
|
||||
nextSendAt: nextSendAt,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
await WorkspaceNotificationSummaryService.sendSummary({
|
||||
summaryId: summary.id!,
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`WorkspaceNotificationSummary:SendSummary: Sent summary ${summary.id?.toString()}`,
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`WorkspaceNotificationSummary:SendSummary: Error sending summary ${summary.id?.toString()}`,
|
||||
);
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -110,6 +110,9 @@ import "./Jobs/StatusPageOwners/SendOwnerAddedNotification";
|
||||
// Status Page Reports
|
||||
import "./Jobs/StatusPage/SendReportsToSubscribers";
|
||||
|
||||
// Workspace Notification Summaries
|
||||
import "./Jobs/WorkspaceNotificationSummary/SendSummary";
|
||||
|
||||
// User Notifications Log
|
||||
import "./Jobs/UserOnCallLog/ExecutePendingExecutions";
|
||||
import "./Jobs/UserOnCallLog/TimeoutStuckExecutions";
|
||||
|
||||
Reference in New Issue
Block a user